Main Page
 The gatekeeper of reality is
 quantified imagination.

Stay notified when site changes by adding your email address:

Your Email:

Bookmark and Share
Email Notification
Project "ASP.Net 3.5 Web Services & .Net AJAX"
    Purpose
The purpose of this project is to demonstrate how to create a .Net 3.5 web service from scratch. .Net AJAX will be used in conjunction with JScript 8.0 and one ASP.NET web page. No web page post-backs will be done (since .Net AJAX and JScript will be used). Other things, such as working with console applications are also covered in this project.

How I Approach This Project
As a web researcher/developer I had the opportunity to get rolling with .Net 3.5 months before it was released. Because of this I've developed a variety of ASP.NET 3.5 web applications; this is among one of them which was taken out of a larger context. So, if you see me mention something that seems odd (such as the web service communicating with a command-line (console) application, breaking up StdOut of that application and stuffing that into the web service response) you can skip it if you want; but it may be handy to know for the sake of knowing how it's done under the .Net 3.5 umbrella.


Typically I conduct research and development on a test web server (Windows-based in this case) and conduct project builds on the test web server. When everything is working as scoped, I'll do a publish to a separate production web server. If you happen to create and build your projects on your local computer, please keep that in mind when I mention network paths (which .Net 3.5 actually can work with).

What Will You Need?
You will need Visual Studio 2008, Microsoft Office Word 2007 and the location you'll build at (in my case the web server) will need to be able to run .Net 3.5 applications. If you are planning on creating, building and experimenting with this project (by following the steps below) you will also need a third-party component; this open source component is an application which creates PDF data from a web page or from html code contained in a static file. It is called <HTMLDoc> and you can get it for a variety of web server platforms here.

Let's Begin
To begin with, you will need to download the following three Microsoft Office Word 2007 docx files. Each file contains complete source code for a specific part of this project.
  1. PDFCreate_aspx.docx - ASP.NET web page source code
  2. ajaxPDFJScript.docx - JScript source code for communicating with the web service
  3. PDFAjaxWebService_vb.docx - ASP.NET 3.5 web service source code
Please follow the step by step guide below if web services and .Net AJAX under .Net 3.5 is a fairly new subject to you. Otherwise you may prefer to skip to the next section.

STEP 1

(Enlarge)
Open Visual Studio 2008.
STEP 2

(Enlarge)
Under the File Menu select "New Web Site".
STEP 3

(Enlarge)
  1. Under "New Web Site" select ASP.NET Web Site.
  2. Specify Location as "File System".
  3. Specify Language as "Visual Basic".
  4. Type in the network path to the web server, a web site on that web server, and a folder that the project or "ASP.NET Web Site" will reside at.
STEP 4

(Enlarge)
You will notice that a default web page has been created (Default.aspx). We are going to rename the default web page.

Under Solution Explorer, right-click on "Default.aspx".
STEP 5

(Enlarge)
From the pop-up menu, select "Rename".
STEP 6

(Enlarge)
Rename the filename to "PDFCreate.aspx".
STEP 7

(Enlarge)
  1. Open "PDFCreate_aspx.docx" (the source code file mentioned at the top of this web page) with Microsoft Office Word 2007.
  2. Highlight and copy the contents of the page.
  3. Highlight the source code section of "PDFCreate.aspx" starting at <html> and ending after </html>.
  4. Paste the contents you had copied. This will remove the default code that was generated with what is contained in the docx file.
STEP 8

(Enlarge)
Now we are going to create a folder to place the JScript into.

Under Solution Explorer, right-click on ""\\webserverA\siteB\folder".

From the pop-up menu select "New Folder".
STEP 9

(Enlarge)
Specify the folder name as "Javascript".
STEP 10

(Enlarge)
Now we are going to add a Jscript file inside of the folder you just created.
  1. Right-click on the folder "Javascript" and select "Add New Item" from the pop-up menu.
  2. Select JScript File.
  3. Under Name, specify "ajaxPDFJScript.js".
  4. Under Language, select "Visual Basic".
  5. Click on the button labeled "Add".
STEP 11

(Enlarge)
Now we have a blank source code page to work with.
STEP 12

(Enlarge)
  1. Open "ajaxPDFJScript.docx" (the source code file mentioned at the top of this web page) with Microsoft Office Word 2007.
  2. Highlight and copy the contents of the page.
  3. Paste the code you highlighted into the blank JScript page.
STEP 13

(Enlarge)
Now we are going to create the web service.

Under Solution Explorer, right-click on ""\\webserverA\siteB\folder".

From the pop-up menu select "Add New Item".
STEP 14

(Enlarge)
  1. Select "Web Service".
  2. Under Name specify "PDFAjaxWebService.asmx".
  3. Under Language specify "Visual Basic".
  4. Check the option "Place code in separate file".
STEP 15

(Enlarge)
The source code page, containing generic source code should now be displayed.

Remove the generic source code.
STEP 16

(Enlarge)
  1. Open "PDFAjaxWebService_vb.docx" (the source code file mentioned at the top of this web page) with Microsoft Office Word 2007.
  2. Highlight and copy the contents of the page.
  3. Paste the code you highlighted into the web service's vb page.
STEP 17

(Enlarge)
Congratulations, all the pieces are now in place!

Now let's perform a build.

Under the Build menu, select "Build Web Site".
STEP 18

(Enlarge)
The build should succeed and you can now begin experimenting with PDFCreate.aspx by typing in the URL of that web page in your web browser and studying the output. If <HTMLDoc> is not installed and configured properly on the web server, the web service will not work.


Overview: How .Net Web Services & .Net AJAX Work
Think of a web service as a web application sitting on a web site. When it is called (commonly by passing a XML/SOAP message to it)...similar to someone submitting a web form and having that data received by a web application (via a post-back or pointing to a different ASPX web page)...it will handle the request and, instead of creating a web page you would see in your browser, generates a XML/SOAP message response. Your web application, which sent the XML/SOAP message, must be able to process that response. This is commonly done using the "AJAX" concept.

But stop right there. There is a difference between "AJAX" and ".Net AJAX" which I will explain. Let's say you have created a simple web page (simple.html) outside of Visual Studio. Let's say you are a hard-core developer and created the web page in Notepad and you have it on a Apache web server. In this case you cannot drag-n-drop your way to adding "AJAX" to the web page. What you do, in this case, is to rely on the Javascript DOM, which is incorporated into web browsers, and create your own "AJAX" to send XML/SOAP data to a web service.

The Traditional "AJAX"
The resultant "AJAX" code you would write using Javascript would look similar to what is shown below. This code snippet demonstrates one way to send XML/SOAP messages to a web service.
function sendDataAsXML_SOAP() {
var req_params = "", url = "", number = 0, type = "";
/* Configure Parameters */
url = "http://www.somesite.com/folder/webService.asmx";
number = 21;
type = "newest";
req_params = "<?xml version=\"1.0\" encoding=\"utf-8\"?><soap:Envelope xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\"><soap:Body><ajaxDataRequest xmlns=\"serviceAjax\">";
req_params = req_params + "<articleNumber>" + number + "</articleNumber>";
req_params = req_params + "<articleType>" + type + "</articleType>";
req_params = req_params + "</ajaxDataRequest></soap:Body></soap:Envelope>";
/* Send XML/SOAP Request To Web Service Using Browser's Javascript DOM */
try { ajax_request = new XMLHttpRequest(); }
catch (trymicrosoft) {
	try { ajax_request = new ActiveXObject("Msxml2.XMLHTTP"); }
	catch (othermicrosoft) {
		try { ajax_request = new ActiveXObject("Microsoft.XMLHTTP"); }
		catch (failed) { ajax_request = false; }
			       }
		     }
ajax_request.open("POST", url, true);
ajax_request.setRequestHeader("Content-Type", "text/xml;charset=utf-8");
ajax_request.onreadystatechange = receiveXML_SOAPData;
ajax_request.send(req_params);
}

But you would also need a way to read the XML/SOAP response (see the reference to "receiveXML_SOAPData" above?) so you would need to have a function to parse the response such as the following.
var xmlCount = 0; xmlName = new Array(); xmlValue = new Array();
function receiveXML_SOAPData() {
	if (ajax_request.readyState == 4) {
		if (ajax_request.status == 200) {
	/* Parse The Response Data */
	captureXML(ajax_request.responseText);
	/* Alert The Response Data */
	for (x = 0; x < xmlName.length; x++) {
					      alert(xmlName[x] + " - " + xmlValue[x]);
					     }
						}
					  }
}
function captureXML(xmlStream) {
/* XML put into a string is passed to this function as xmlStream */
var items, xmlTree, xmlDoc;
var x_name = "", x_value = "";
var xmlBrowseType = 0, x_drop = 0, x_count = 0;
if (window.ActiveXObject) {
			   /* Internet Explorer */
			   xmlBrowseType = 1;
			   xmlDoc = new ActiveXObject("MSXML2.DomDocument");
			   xmlDoc.loadXML(xmlStream);
			  }
else {
      /* Mozilla / Firefox */
      xmlBrowseType = 2;
      var parser = new DOMParser();
      xmlDoc = parser.parseFromString(xmlStream.replace(/\t/g,"").replace(/\v/g, ""), "text/xml");
     }
xmlTree = xmlDoc.documentElement;
if (xmlTree.hasChildNodes()) {
			      if (xmlBrowseType == 1) {
						       /* Internet Explorer */
						       y_count = 0;
						       do {
							   x_drop = 0; y_drop = 0; x_count = 0;
							   try {
								x_name = xmlTree.childNodes[x_count].nodeName;
								x_value = xmlTree.getElementsByTagName(x_name)[y_count].childNodes[y_count].nodeValue;
							       }
							   catch(onerror) { x_drop = 1; y_drop = 1; }
							   do {
							       try {
								    x_name = xmlTree.childNodes[x_count].nodeName;
								    x_value = xmlTree.getElementsByTagName(x_name)[y_count].childNodes[y_count].nodeValue;
								   }
							       catch(onerror) { x_drop = 1; x_value = null; }
							       xmlSave(x_name, x_value); x_name = ""; x_value = "";
							       x_count = x_count + 1;
							      } while (x_drop == 0);
							   y_count = y_count + 1;
							  } while (y_drop == 0);
						       }
			      else {
				    /* Mozilla / Firefox */
				    y_count = 0;
				    do {
					x_drop = 0; y_drop = 0; x_count = 0;
					try {
					     x_name = xmlTree.childNodes[x_count].nodeName;
					     x_value = xmlTree.childNodes[x_count].childNodes[y_count].nodeValue;
					    }
					catch(onerror) { x_drop = 1; y_drop = 1; }
					do {
					    try {
						 x_name = xmlTree.childNodes[x_count].nodeName;
						 x_value = xmlTree.childNodes[x_count].childNodes[y_count].nodeValue;
						}
					    catch(onerror) { x_drop = 1; x_value = null; }
					    xmlSave(x_name, x_value); x_name = ""; x_value = "";
					    x_count = x_count + 1;
					   } while (x_drop == 0);
					y_count = y_count + 1;
				       } while (y_drop == 0);
				   }
			}
}


Your code footprint will be very small and you can get very finite with what you may be developing. At the same time, however, you will have to contend with javascript errors which may arise and be quite knowledgeable with the Javascript DOM and subtle differences between web browsers.

Let's move to ".Net AJAX"
Depending on what you are doing you may, infact, have to incorporate something such as what was already covered into your .Net project. From the perspective of the .Net 3.5 project that is being discussed on this page, you don't. Infact, .Net 3.5 automatically creates all of the AJAX code for you, under the .Net umbrella. However, because it does this for you automatically, you must learn the rules that .Net will allow you to use with its auto-generated AJAX interface functions. You don't really need to understand how XML/SOAP really works either; which is good and bad depending on what may surface with a task you may end up working on in the future.

For example, .Net automatically creates an embeds an AXD javascript reference to your ASPX web page. Additionally, .Net will also embed the Javascript functions (all pre-configured) you may end up working with as shown below. Keep in mind, you can't modify any of it at the source code level because it is automatically generated when the ASPX web page is requested.
<script type="text/javascript">
//<![CDATA[
var PDFAjaxWebService=function() {
PDFAjaxWebService.initializeBase(this);
this._timeout = 0;
this._userContext = null;
this._succeeded = null;
this._failed = null;
}
PDFAjaxWebService.prototype={
_get_path:function() {
 var p = this.get_path();
 if (p) return p;
 else return PDFAjaxWebService._staticInstance.get_path();},
ajaxPDFGenerator:function(httpURL,httpURLVirtual,rawTxt,succeededCallback, failedCallback, userContext) {
/// <param name="httpURL" type="String">System.String</param>
/// <param name="httpURLVirtual" type="String">System.String</param>
/// <param name="rawTxt" type="String">System.String</param>
/// <param name="succeededCallback" type="Function" optional="true" mayBeNull="true"></param>
/// <param name="failedCallback" type="Function" optional="true" mayBeNull="true"></param>
/// <param name="userContext" optional="true" mayBeNull="true"></param>
return this._invoke(this._get_path(), 'ajaxPDFGenerator',false,{httpURL:httpURL,httpURLVirtual:httpURLVirtual,rawTxt:rawTxt},succeededCallback,failedCallback,userContext); }}
PDFAjaxWebService.registerClass('PDFAjaxWebService',Sys.Net.WebServiceProxy);
PDFAjaxWebService._staticInstance = new PDFAjaxWebService();
PDFAjaxWebService.set_path = function(value) {
PDFAjaxWebService._staticInstance.set_path(value); }
PDFAjaxWebService.get_path = function() { 
/// <value type="String" mayBeNull="true">The service url.</value>return PDFAjaxWebService._staticInstance.get_path();}
PDFAjaxWebService.set_timeout = function(value) {
PDFAjaxWebService._staticInstance.set_timeout(value); }
PDFAjaxWebService.get_timeout = function() { 
/// <value type="Number">The service timeout.</value>
return PDFAjaxWebService._staticInstance.get_timeout(); }
PDFAjaxWebService.set_defaultUserContext = function(value) { 
PDFAjaxWebService._staticInstance.set_defaultUserContext(value); }
PDFAjaxWebService.get_defaultUserContext = function() { 
/// <value mayBeNull="true">The service default user context.</value>
return PDFAjaxWebService._staticInstance.get_defaultUserContext(); }
PDFAjaxWebService.set_defaultSucceededCallback = function(value) { 
 PDFAjaxWebService._staticInstance.set_defaultSucceededCallback(value); }
PDFAjaxWebService.get_defaultSucceededCallback = function() { 
/// <value type="Function" mayBeNull="true">The service default succeeded callback.</value>
return PDFAjaxWebService._staticInstance.get_defaultSucceededCallback(); }
PDFAjaxWebService.set_defaultFailedCallback = function(value) { 
PDFAjaxWebService._staticInstance.set_defaultFailedCallback(value); }
PDFAjaxWebService.get_defaultFailedCallback = function() { 
/// <value type="Function" mayBeNull="true">The service default failed callback.</value>
return PDFAjaxWebService._staticInstance.get_defaultFailedCallback(); }
PDFAjaxWebService.set_path("/folder/PDFAjaxWebService.asmx");
PDFAjaxWebService.ajaxPDFGenerator= function(httpURL,httpURLVirtual,rawTxt,onSuccess,onFailed,userContext) {
/// <param name="httpURL" type="String">System.String</param>
/// <param name="httpURLVirtual" type="String">System.String</param>
/// <param name="rawTxt" type="String">System.String</param>
/// <param name="succeededCallback" type="Function" optional="true" mayBeNull="true"></param>
/// <param name="failedCallback" type="Function" optional="true" mayBeNull="true"></param>
/// <param name="userContext" optional="true" mayBeNull="true"></param>
PDFAjaxWebService._staticInstance.ajaxPDFGenerator(httpURL,httpURLVirtual,rawTxt,onSuccess,onFailed,userContext); }
var gtc = Sys.Net.WebServiceProxy._generateTypedConstructor;
if (typeof(XMLPDFDataBlock) === 'undefined') {
var XMLPDFDataBlock=gtc("XMLPDFDataBlock");
XMLPDFDataBlock.registerClass('XMLPDFDataBlock');
}
//]]>
</script>
        <script type="text/javascript">
//<![CDATA[Sys.WebForms.PageRequestManager._initialize('ajaxClientComponentMgr', document.getElementById('form1'));
Sys.WebForms.PageRequestManager.getInstance()._updateControls([], [], [], 90);
//]]>
</script>

Speaking of .Net AJAX interface functions (removing the need for you to directly handle or package XML/SOAP messages), I'll discuss .Net's "JScript" next.

JScript Source Code
JScript is your means for interacting with all of the .Net AJAX interface functions (whom in turn actually perform the dirty work of XML/SOAP and communication). With our project, when you actually want to send data to a web service through .Net AJAX, you're going to need to identify the function that has been created for you for sending a XML/SOAP request to the web service.

First, let's find out a little regarding the web service in our project. We know that it is contained inside a public class labeled "PDFAjaxWebService". We also know that the web service function contained within that public class is labeled "ajaxPDFGenerator". Now, here's where it may get a little interesting for you.

When you examine the arguments that "ajaxPDFGenerator" accepts (3 arguments), you will find out that it does not coorespond to the number of arguments the .Net AJAX interface function expects -- the interface function, in this case, is "ajaxPDFGenerator:function(httpURL, httpURLVirtual, rawTxt, succeededCallback, failedCallback, userContext)". As you can surmise, you will need to provide a JScript function name for "succeededCallback" and "failedCallback". The last argument "userContext" is usually going to be "text/xml".

Now that you've identified the .Net AJAX interface function that you need to use in order to send a web service request using XML/SOAP, how do you actually call it in your JScript page? This is quite simple as something like:
PDFAjaxWebService.ajaxPDFGenerator(httpURL, httpURLVirtual, rawTxt, responseSuccessPDF, responseFailPDF, "text/xml");

The "httpURL", "httpURLVirtual" and "rawTxt" arguments is the actual data that will be sent to the web service. "responseSuccessPDF" and "responseFailPDF" are JScript functions that you create which handle the response of the web service, after that response data has been filtered through the .Net AJAX functions. You may end up defining these functions in your JScript page as something like:
function responseSuccessPDF(response) {
/* Handle response data which has been transformed from XML/SOAP into class hierarchy */
alert(response.xmlNodeNameA); // Substitute xmlNodeNameA with an actual XML node name which contains a value
}


The .Net 3.5 Web Service And Classes To Create XML/SOAP
On the main page of this web site, you may have seen that I mentioned you could set up the web service to send XML/SOAP data as its response without needing to deal with WSDL, XSL, XML or anything of that nature at all. Here's one way that it is done (.Net 3.5 in turn, will do all the dirty work for you). If you understand how classes work, that's all you need to know.

First, treat the web service function as a type of your your custom-defined class. In the case of the project covered on this page:
Public Function ajaxPDFGenerator(ByVal httpURL As String, ByVal httpURLVirtual As String, ByVal rawTxt As String) As XMLPDFDataBlock

Second (if you have not already), define your custom-defined class. In the case of the project covered on this page:
' Our "XML/SOAP" ClassPublic Class XMLPDFDataBlock
    ' The following basically allows you to have an unlimited number of "child nodes" inside what may be 
    ' considered the parent node "generationResultsBin".
    Private _generationResultsBin() As String
    Public Property generationResultsBin() As String()
        Get
            Return _generationResultsBin
        End Get
        Set(ByVal value() As String)
            _generationResultsBin = value
        End Set
    End Property
    ' This is the equivalent of a single XML node, which holds one value which is of the type "string".
    Private _generationResultsMsg As String
    Public Property generationResultsMsg() As String
        Get
            Return _generationResultsMsg
        End Get
        Set(ByVal value As String)
            _generationResultsMsg = value
        End Set
    End Property
    ' This is the equivalent of a single XML node, which holds one value which is of the type "string".
    Private _generationResultsURL As String
    Public Property generationResultsURL() As String
        Get
            Return _generationResultsURL
        End Get
        Set(ByVal value As String)
            _generationResultsURL = value
        End Set
    End Property
End Class

Third, let's return to the web service. Let's add the following code to it:
' Our custom-defined "XML/SOAP" class
Dim xmlResponse As New XMLPDFDataBlock
' Define an array (which can be infinite in size less message transport size restrictions of the XML/SOAP message itself) to populate with one-dimensional data
Dim generationResultsBin(2) As String
generationResultsBin(1) = "Child Node 1 Data"
generationResultsBin(2) = "Child Node 2 Data"
' Populate class data
With xmlResponse
	.generationResultsMsg = "This is a test"
	.generationResultsURL = "http://www.somesite.com/"
	.generationResultsBin = generationResultsBin
End With
' Return the data
Return xmlResponse

That's it! .Net 3.5 will translate that into XML/SOAP for you as a response to requests made of the web service. That was so easy, it's almost scary.

Finally, Dealing With A Console Application In .Net 3.5
I thought I had better cover this, since it is contained within the web service. To give you an overview, I had a need to invoke a console application (command-line style akin to WScript.Shell of ASP), and read the StdOut it would generate. In the simplified form I have included in the project covered on this page, I actually go further and chop up the binary StdOut stream into chunks (while this was not needed, it was done so that it could be illustrated how to add, theoretically, an infinite number of child nodes to the "generationResultsBin" property of our class -- so you could see how .Net 3.5 deals with handling a node it must create which has an unlimited number of child nodes in an XML/SOAP message).

Tongue-twisters aside, I'll continue. Here is an example of how to interact with a console application within .Net 3.5:
' Call htmldoc and have it return PDF stream blocks translated from the temporary file containing html code
	Dim localTempLocation As String = "\\webserverA\siteB\folder\temppdf.txt"
	Dim externalProcessPageVirtual As ProcessStartInfo = New ProcessStartInfo("htmldoc", "--quiet --webpage -t pdf " & localTempLocation)
	externalProcessPageVirtual.RedirectStandardOutput = True   ' Grab StdOut
	externalProcessPageVirtual.RedirectStandardError = True    ' Grab any errors (StdErr) that may come up if needed although not handled in this example
	externalProcessPageVirtual.CreateNoWindow = True           ' No need to create a window for this operation
	externalProcessPageVirtual.UseShellExecute = False         ' We want the raw output
	Dim runProcessPageVirtual As Process = Process.Start(externalProcessPageVirtual)
	runProcessPageVirtual.Start()
	Dim generationResultsBinTemp As String = ""
	generationResultsBinTemp = runProcessPageVirtual.StandardOutput.ReadToEnd

As the second part of that, I chop up the StandardOutput (aka StdOut) into chunks and place that into an array as shown below:
Try
	' Create a few child nodes for <generationResultsBin></generationResultsBin>
	Dim parseFinished As Integer = 0
	Dim lenBoundary As Integer = 2500       ' Maximum size of a child node before a new child node is created which holds a fragment of StandardOutput
	Dim maximumNodeSize As Integer = 30000  ' Maximum size of all child nodes to be transported.  If you want the to be able to transport a SOAP message much larger, you will need to make changes on the server, MaxRequestLength, etc.
	If generationResultsBinTemp.Length < maximumNodeSize Then
		maximumNodeSize = generationResultsBinTemp.Length
	End If
	Do Until parseFinished >= maximumNodeSize
		If parseFinished + lenBoundary >= maximumNodeSize Then
			ReDim Preserve generationResultsBin(UBound(generationResultsBin) + 1)
			generationResultsBin(UBound(generationResultsBin)) = Mid(generationResultsBinTemp, parseFinished + 1, maximumNodeSize)
		Else
			ReDim Preserve generationResultsBin(UBound(generationResultsBin) + 1)
			generationResultsBin(UBound(generationResultsBin)) = Mid(generationResultsBinTemp, parseFinished + 1, lenBoundary)
		End If
		parseFinished = parseFinished + lenBoundary
	Loop
Catch ex As Exception
	generationResultsMsg = Replace(ex.ToString, vbCrLf, "\n")
End Try
generationResultsURL = ""


About Joe
Find Out Now!