devxlogo

Synchronize Your Databases with .NET Web Services (Part I)

Synchronize Your Databases with .NET Web Services (Part I)

he Web browser/Web server combination used today was originally designed to deliver HTML, which implicitly contains display information as well as data. But this doesn’t mean that the Web server must deliver HTML. When a page contains an element, or a hyperlink to a ZIP file, the Web server delivers the content in its native format. OK, so the content might be UU-encoded for transmission across the Internet, but it certainly isn’t HTML when it arrives.

Delivering Simple Text Strings
As an example of a non-HTML information delivery, assume that a server uses one of the timeserver resources on the Internet to accurately maintain its internal clock. Therefore you can easily deliver the current time value from the server to any client upon request by generating a response containing the server’s current time as a simple string. In ASP.NET, for example, you can use:

   <%@Page Language="VB" %> 

The data stream returned from the Web Server contains the usual HTTP headers required for communication between server and client, but the content of the returned “page” is just something like 10:25:07. A Web browser will display this, but any other application that references the page can use the text string as required.

Author’s Note: Notice that the preceding code snippet sets the MIME type (using the ContentType property in ASP.NET) to tell the browser what to expect. The obvious choice is to use “text/text” as the MIME type value, but that confuses some browsers because they then try and use the file extension (.aspx) to decide what to do with the content. Using “text/html” effectively tells the browser just to display it, even though there are no or elements in the content. As you can see, the Internet provides everything required for the basic mechanism of making server calls and returning responses.

But the World Wants XML!
But simple text strings are not generally the most useful type of data. Looking at the issues in a more general way, what’s really needed is a way of delivering information that:

  • Has a structure defined through some universal standard
  • Can represent complex data types, and not just simple string values
  • Allows the recipient to identify the structure and data types within the content
  • Supports validation of the content for both structure and permitted content

All these features have, in fact, long been on the agenda of both the Internet standards bodies (such as the W3C) and of those developers and corporations whose life revolves around data interchange. The result today is the evolving standards for Web services, as supported by most of the major players in the IT world?such as IBM, Microsoft and Sun. You can glean some idea of the huge amount of work going on in the Web services arena here.

Delivering a DataSet
It goes without saying that the obvious choice of format for data exposed by a Web site or application is one that best suits the data itself. Anything much beyond a single value, such as a string, requires a data structure that can store multiple items; therefore some kind of rowset or recordset seems the ideal solution. The columns can contain items of different data types, such as integers, strings, real numbers, currency values, etc. There can also be one or more rows, providing the required flexibility for exposing all kinds of data.

For those of us who live in the Microsoft .NET world, the ideal container for a rowset is the .NET DataSet class. DataSets can contain one or more tables (rowsets), the metadata that defines features such as primary and foreign keys, default values, etc., and the relationships between the tables. The DataSet class is also optimized for reading and writing content as XML using Microsoft’s proprietary diffgram format.

You can easily expose a DataSet object from a Web site or application using ASP.NET Web services, and thereby provide the best opportunity for any client to use the data in the way that they choose. And, best of all, clients that don’t understand .NET and the DataSet diffgram format can still use the data by treating it as a “normal” XML document.

Two Ways to Access ASP.NET Web Services
Because of the hive of activity in the Web services world, Microsoft chose to include Web services technology in their release of the .NET Framework a couple of years ago. You create a Web service by building a Class file that has the .asmx file extension. The Web server (IIS) maps this to the ASP.NET engine, which compiles and executes the class to expose the functionality it contains to the current request and response objects.

Effectively, this means that you can “call” a Web service using a standard HTTP request, and get back an XML-formatted string as the response (see Figure 1).

An Example?Exposing Site Traffic Data
The author’s site serves as an example of exposing data via Web services. Figure 3 shows the main Web service page. You can see that there are four public methods available. Each returns a diffgram containing the relevant information.

Building a Web Service
In essence, all you need to do in .NET to create a Web service is to decorate the Class declaration with the WebService attribute, and then mark each method that you want to expose with the WebMethod attribute. To expose the method via your Web server, create an .asmx page containing a WebService page directive, supplying the language and class name of the Web service implementation as shown below.

   <%@WebService Language="VB" Class="PublicIISLogs"%>

Alternatively, you can create a new Web service file using Visual Studio, which automatically creates an .asmx page containing the WebService page directive and a class file in the project language and associates the two via the Codebehind attribute in the page directive:

   <%@ WebService Language="vb" Codebehind="Service1.asmx.vb"       Class="WebServiceTestApp2.test" %> 

Unlike ASP.NET Web forms (.aspx files), ASP.NET and Visual Studio do not import basic namespaces into Web service files by default, so at minimum, you’ll need to import the System, System.Web, and System.Web.Services namespaces. In addition, import the namespaces for any data access classes you use (the sample code uses both System.Data and System.Data.SqlClient). Don’t forget the System.Configuration namespace, which lets you access settings in the web.config file to extract a connection string or other configuration information. You may need other namespaces as well, such as System.Collections (if your code uses a collection class such as an ArrayList), or System.Xml to access XML documents:

   Imports System   Imports System.Data   Imports System.Data.SqlClient   Imports System.Web   Imports System.Web.Services   Imports System.Configuration

You need to decorate the Public Class statement with the WebService attribute and set its properties, using the rather odd syntax required for VB.NET. The Description property value should contain a brief description of the service, and set the Namespace value to something unique to your site and service that differentiates it from all other Web services on the Internet:

    _   Public Class PublicIISLogs          _      Public Function TrafficSummary() As DataSet         Dim oDS As New DataSet         ... code to fill DataSet as required ...         Return oDS      End Function      End Class
Author’s Note: The Namespace property value does not have to be a URI?it can be anything unique, such as a GUID. Also note that the class name in the page directive must match the name of the public class you define within the file or assembly that implements the Web service.

The WebService attribute added to the public class defines the Web service itself. You mark any Public methods within it (any Sub or Function in VB.NET) that you want to expose through the Web service using the WebMethod attribute as shown in the previous listing. You can add Description and other properties to the WebMethod declaration to control caching, ASP.NET session support, buffering and transaction support. Search the .NET SDK for “WebMethodAttribute Members” for more details.

The Sample Web Service
The sample PublicIISLogs Web service (see the sample code) exposes four Web methods, including one named TrafficSummary that returns data on the total traffic for our site over the past 52 weeks. Here’s the code for that method:

    _   Public Function TrafficSummary() As DataSet     Try       Dim sTableName As String = "WeekSummary"       Dim dFromDate As Date = DateTime.Now.AddMonths(-12)       Dim sSQL As String = GetSQLStatement( _          sTableName, dFromDate)       Return GetDataSet(sSQL, sTableName)     Catch oErr As Exception       Return GetErrorDataSet(oErr.Message)     End Try   End Function

You can see that it uses three other routines within the class file: GetSQLStatement, GetDataSet, and GetErrorDataSet. These are all declared as Private and aren’t available as Web service methods because they don’t have the WebMethod attribute. The GetSQLStatement method builds the rather complex SQL statement required to extract rows from the IISLogs database, using the table name and starting date values passed into this routine as parameters:

   Private Function GetSQLStatement(sTableName As String, _      dFromDate As Date) As String      Dim oCal As New System.Globalization.GregorianCalendar      Dim iYear As Integer = dFromDate.Year      Dim iWeek As Integer = oCal.GetWeekOfYear( _         dFromDate, Nothing, Nothing)         sSQL = "SELECT Year = MAX(TYearNumber), Week = _         MAX(TWeekNumber), " _         & "Hits = SUM(HitCount), KBytes = SUM(KBytes), " _         & "Sessions = SUM(Sessions) FROM " & sTableName _         & " WHERE ((TYearNumber = " & iYear.ToString() _         & " AND TWeekNumber > " & iWeek.ToString() _         & ") OR (TYearNumber > " & iYear.ToString() _         & ")) GROUP BY TYearNumber, TWeekNumber " _         & "ORDER BY TYearNumber, TWeekNumber"      Return sSQL   End Function

The method returns the completed SQL statement. The GetDataSet method uses this statement and the table name to populate a DataSet with the required results. The source database connection string comes from a key value named IISLogs within the web.config file:

   Private Function GetDataSet(sSQL As String, _      sTableName As String) As DataSet      Dim sConnect As String = _         ConfigurationSettings.AppSettings("IISLogs")      Dim oDS As New DataSet()      Dim oConn As New SqlConnection(sConnect)      Dim oDA As New SqlDataAdapter(sSQL, oConn)      oDA.Fill(oDS, sTableName)      Return oDS   End Function

The method returns the populated DataSet object.

Managing Error Situations and Returning Status Information
One interesting problem that arises regularly when dealing with a Web service involves managing errors. It’s easy enough to trap an exception in the ASP.NET code, and get details of the error that caused the exception to be raised. But how do you communicate error information to the client is a useful way?

If you’re passing back a simple string, such as the current time (as described at the start of this article), you could just trap the error and send back a string such as: “Sorry, an error occurred”, or even something more detailed like: “The remote time server failed to respond”. However, if the client is expecting a non-string data type then you can’t send back a string.

For example, if the Web service usually returns an integer value, you might be able to use a specific value (perhaps -1 or 999) to represent an error. This pre-supposes that the client and server can agree beforehand on what return values are valid, what values are treated as errors, and what error each value represents.

But in this example, the clients expect a DataSet, and so your choices are to send back a DataSet or the value Nothing (null in C#), to indicate a failure, but that’s not particularly expressive or useful. Instead, the sample code creates a DataSet containing a single table named Errors, which has a single column named Message. When an error occurs, this column contains the text error message returned to the client. The code calls the GetErrorDataSet function whenever any error occurs while executing the TrafficSummary method:

   Private Function GetErrorDataSet(sMessage As String) _      As DataSet      Try         Dim oDT As New DataTable("Errors")         oDT.Columns.Add("Message", _            System.Type.GetType("System.String"))         Dim oDR As DataRow = oDT.NewRow()         oDR("Message") = sMessage         oDT.Rows.Add(oDR)         Dim oDS As New DataSet("Error")         oDS.Tables.Add(oDT)         Return oDS      Catch         Return Nothing      End Try   End Function

Figure 4 shows an example of the XML returned when an error occurs.

Consuming Web Services
While the previous sections have blithely mentioned sending SOAP or HTTP requests to the server, how do you actually accomplish that task? Sending an HTTP request is easy?the client just has to send an HTTP GET or POST request to the Web service, specifying the method they want to execute and any parameters that method requires. The example Web service shown above takes no parameters, so a request looks like this:

   http://www.daveandal.net/traffic/trafficdata.asmx/LanguageSummary

When the Web service method does require parameters, you add them to the URL as a query string for GET requests, or place them into the body of a

for a POST request. For example, if the LanguageSummary method required parameters named Year and Week, a suitable GET request would be:

   http://www.daveandal.net/traffic/trafficdata.asmx/LanguageSummary?Year=2004&Week=6

Alternatively, you could use an HTML page containing the following

section to create a suitable POST request:

               Year:       Week :               
Author’s Note: You can use the two pages get-request.htm and post-request.htm in the simple-request folder of the examples to experiment with sending parameters to a Web service.

Sending SOAP Requests to a Web Service
Sending a SOAP request to a Web service is a little more complex, as you have to create the appropriate XML message in SOAP format. The next listing shows how you would form the request for the LanguageSummary method with the same two Year and Week parameters:

                        2004         6               

The request consists of a SOAP envelope, within which is the element. This contains an element that identifies the method required, and contains an element named after each of the required parameters with the parameter values as their content.

In general, the easiest way to construct a SOAP request is through an appropriate proxy object created on the client (as shown earlier in Figure 2). There are various ways that you can create this proxy, depending on the technology you are using to build the client.

The final sections of this article demonstrate how you can consume a Web service. The Web service file named trafficparameters.asmx in the webservices folder of the examples implements a single method named TrafficSummaryFromWeekYear, which accepts parameters that define the week and year to start from when returning the data from our database. Here are examples of ways that you can make a request to this Web service and use the returned data.

Building a Web Service Proxy with WSDL.exe
WSDL is the Web services Description Language, and is used to define the interface or contract for a Web service. You can view the WSDL for a Web service by clicking the Service Description link ASP.NET places in the default service description page that is generated when you access an .asmx file directly in the browser. You need the WSDL file to create a proxy. If you are accessing an ASP.NET Web service, you can get the WSDL file from it by appending ?WSDL to the URL as a query string (unless the Web service has disabled the default service page as discussed earlier in this article).

After you obtain the WSDL file, you can use the .NET Framework tool named WSDL.exe (installed by default in the C:Program FilesMicrosoft.NETSDK[version]Bin folder) to build a suitable proxy class for a Web service. The syntax and parameters for the tool are complex, but they’re fully described in the .NET SDK (search for “wsdl.exe”). However, for most scenarios, all you need is this simple command line:

   wsdl.exe  /language:lang  /out:class-file        URL-or-path-of-WSDL-file 

The default language is C#, but you can use the /language parameter to specify VB (Visual Basic .NET), JS (JScript .NET) or VJS (Visual J#). The /out parameter defines the path and name of the proxy class source file that WSDL.exe should generate, and the final parameter is the URL or physical path to the WSDL file that defines the Web service.

To make things easier, you’ll find a batch file (create-proxy.bat in the create-proxy folder) in the sample code that executes WSDL.exe with the appropriate parameters for the example files. Note that the batch file specifically selects the tool installed with version 1.1 of the Framework:

Editor’s Note: Enter the command lines shown in this section as single lines in a command window or batch file rather than wrapped as shown in this article. One space where each line break occurs at present.

   "C:Program FilesMicrosoft.NETSDKv1.1Binwsdl.exe"       /language:VB /out:IISLogProxy.vb       http://../webservices/trafficparameters.asmx?WSDL

The final step is to compile the new proxy source class file and place it into the bin folder so that you can access it from an ASP.NET page. The provided batch file does this as well, creating the compiled assembly named IISLogProxy.dll:

   C:WINNTMicrosoft.NETFrameworkv1.1.4322vbc       /out:..inIISLogProxy.dll       /t:library       /r:System.dll,       System.Web.dll, System.Data.dll, System.Xml.dll,       System.Web.Services.dll IISLogProxy.vb

Using the Web Service Proxy in ASP.NET
Having created an assembly that implements a proxy for the Web service, you can easily use the proxy in any other .NET application. The following example (aspnet-proxy-client.aspx in the aspnet-client folder) uses the proxy within an ASP.NET page. The page contains a server-side

with two text boxes for the parameter values (the week and year number) and a “submit” button; plus an ASP.NET DataGrid control:

      Starting from week:      year:    

The “submit” button simply executes a routine named ShowData. Because the assembly is in the bin folder of the ASP.NET application, you can reference it by importing the namespace (in this case the name of the public class within the assembly). Then, within the ShowData routine, create a new instance of the proxy class and call its methods.

The ShowData routine is listed below. The method it calls is named TrafficSummaryFromWeekYear, which takes the starting week and year as parameters. You get these values from the text boxes on the page, and bind the result?a DataSet object?to the ASP.NET DataGrid control to display the contents of the WeekSummary table:

   <%@Import Namespace="IISLogsParameters" %>      Sub ShowData(sender As Object, args As EventArgs)     Dim oWS As New IISLogsParameters()     dgr1.DataSource =         oWS.TrafficSummaryFromWeekYear(txtWeek.Text, _        txtYear.Text)     dgr1.DataMember = "WeekSummary"     dgr1.DataBind()   End Sub

Figure 5 shows the example page in use. It contains extra formatting attributes on the DataGrid to provide the results you see here, but functionally the code is as simple as we’ve just described.

Accessing a Web Service with the IE5 Web Service Behavior
The ASP.NET example provides a neat and simple solution to consuming a Web service, though it only works because ASP.NET understands the objects we’ve used?an ASP.NET Web service and a .NET DataSet. Other clients may not have the same level of support for .NET objects, but that doesn’t mean they can’t be persuaded to use a Web service.

For example, Internet Explorer has no concept of.NET, and the scripting languages it provides (VBScript and JScript) are not managed code languages. However, IE (version 5 and higher) can take advantage of a script behavior (a HyperText Component or HTC) that you can use to interact with a Web service from client-side script.

You can find more information and download the IE5 Web service behavior here. You place the behavior file (which is named webservice.htc) on your Web server and the client then fetches it when required.

The ie5-client.aspx page (in the ie5-client folder) demonstrates the use of this Web service behavior. You insert the behavior component into a page using the standard approach for HTCs, by defining it in a style attribute of an element using the special behaviour:url() selector. The example page attaches the behavior to the element that contains the page heading:

         Accessing a Web service using the IE5 Web service Behavior   

The remainder of the visible part of the page consists of a couple of text boxes for the week and year values, and a “submit” button that runs the client-side code (as the button is not within a

it will not cause a postback when clicked). There’s also a element that shows interactive status messages, and a

to display the results:

   Starting from week:         year:          

The code required to use the Web service behavior is not complex, but can become quite lengthy if you choose to handle errors properly. It also makes sense to load the results of the Web service method call asynchronously, so that the page can continue to operate (and display status details) as the Web service is located and accessed?which also requires additional code.

Basically, you start by calling the useService method of the component to establish the connection to the Web service. This downloads the WSDL service definition (you can see that “?WSDL” is appended to the URL as a query string), and establishes a “friendly name” used to refer to the Web service later in the code.

   htcWService.useService(      "../webservices/trafficparameters.asmx?WSDL",      "TrafficData");

After this, you can call the method(s) of the Web service using the “TrafficData” friendly name. The following code collects the week and year values from the text boxes on the page, and uses those as parameters to call the TrafficSummaryFromWeekYear method:

   var sWeek = document.all['txtWeek'].value;   var sYear = document.all['txtYear'].value;   var iCallID = htcWService.TrafficData.callService(dataLoaded,      "TrafficSummaryFromWeekYear", sWeek, sYear);

The first parameter to the callService method shown in the last line of the preceding code is the name of a delegate or event handler to execute when the component has finished downloading the SOAP message returned by the Web service. The next listing shows that dataLoaded delegate function. It first checks for an error, and if all is well, creates a new instance of the MSXML parser provided with IE5 (the code uses the free-threaded version because it has to perform an XSLT transformation on the content later for display):

   function dataLoaded(oResult) {     if(oResult.error) {       // ... display error details ...     }     else {       oXMLData = new ActiveXObject(          'MSXML2.FreeThreadedDOMDocument');       oXMLData.onreadystatechange = changeFunction;       oXMLData.validateOnParse = true;       oXMLData.async = true;       oXMLData.loadXML(oResult.raw.xml);     }   }

Again, the code uses asynchronous loading, this time by specifying the delegate named changeFunction to be executed each time the readystate property of the MSXML parser changes. Now you can start loading the parser with the XML document that lies within the SOAP envelope that the Web service behavior received from the Web service. The Web service behavior passes a “result” object to this callback function. You get at the “content” by accessing the raw.xml property of the result object. This XML document is, of course, the diffgram that represents the .NET DataSet.

As the MSXML parser initializes, extracts the XML document from the SOAP package, loads it, parses it and validates it, the readystate property of the parser changes at each stage, so the changeFunction method gets called repeatedly. It’s only when the readystate property reaches the value 4 that the process is complete. At this point you can check for an error, and if everything succeeded, call another routine that will display the XML document:

   function changeFunction() {     if (oXMLData.readyState == 4) {       if (oXMLData.parseError.errorCode != 0)        // ... display error details ...       else {         showData();       }     }   }

The problem is that IE5 doesn’t know what a DataSet is and so you can’t use techniques such as data binding to display it. As far as IE5 is concerned, the Web service return value is just an XML document with an inline schema, loaded into an instance of the MSXML parser. Fortunately, that means you can use any technique that works with XML documents to process the content.

The ShowData routine uses a simple XSLT stylesheet to transform the XML document into an HTML table for display. The transformation process loads the stylesheet into another instance of MSXML (you must use the free-threaded version for this approach to work), creates s XSLTemplate and Processor instances, and performs the transformation to return a string. The string is then inserted into the

element on the page:

   function showData() {        // create a new parser object instance and load stylesheet     var oXMLStyle = new ActiveXObject(        'MSXML2.FreeThreadedDOMDocument');     oXMLStyle.async = false;     oXMLStyle.load('style-dataset.xsl');     if (oXMLStyle.parseError.errorCode != 0) {       // ... display error message ...       return;     }        // create a new XSLTemplate object and set stylesheet     var oTemplate = new ActiveXObject('MSXML2.XSLTemplate');     oTemplate.stylesheet = oXMLStyle;        // create a processor and specify the XML parser to use     var oProc = oTemplate.createProcessor();     oProc.input = oXMLData;        // perform transformation and display results in the page     if (oProc.transform() == true)       divResult.innerHTML = oProc.output;     else       // ... display error message ...   }   //-->   

The result of the process is shown in Figure 6. You can see that the results are much the same as you get when accessing the Web service through a .NET proxy in an ASP.NET page, although the grid formatting is less garish in this example.

Accessing a Web Service as an XML Document
The previous two examples have demonstrated how you can access a Web service using SOAP. But this example (aspnet-xml-client.aspx in the aspnet-client folder) uses a technique that doesn’t rely on SOAP to access the Web service, or on using a .NET DataSet object to handle the results. It uses an ASP.NET page to access the Web service, but does so using the HTTP GET protocol rather than SOAP. Then it handles the results as an XML document, and not as a DataSet. The page contains basically the same controls as the preceding ASP.NET example, except that now there is an ASP.NET Xml server control instead of a DataGrid control:

      Starting from week:      year:    

When the user clicks the submit button, the code builds a string containing the URL of the Web service. As shown earlier, the URL must contain the method name and any parameter values, in the following format:

   server/path/webservice.asmx/method?param1=value¶m2=value

So the example specifies the trafficparameters.asmx Web service file, the TrafficSummaryFromWeekYear method, and adds as the query string the values extracted from the “week” and “year” text boxes. Then it creates a new XmlDocument instance and loads it with the returned XML . This time, the XML consists of the schema and data that represents the DataSet?there’s no SOAP envelope or other containing elements because you’re making an HTTP GET request this time, not a SOAP request:

   <%@Import Namespace="System.Xml" %>      Sub ShowData(sender As Object, args As EventArgs)     Dim sURL As String =      "http://localhost/SynchWebService/webservices/" & _     "trafficparameters.asmx/TrafficSummaryFromWeekYear?" & _     "Week=" & txtWeek.Text & "&Year=" & txtYear.Text     Dim oXmlDoc As New XmlDocument()     oXmlDoc.Load(sURL)     xmlResult.Document = oXmlDoc     xmlResult.TransformSource = "style-dataset.xsl"   End Sub

After loading the XML (it would, of course, be a good idea to use a Try…Catch construct here to trap any errors), you can take advantage of the clever ASP.NET Xml server control to display it. The Xml control takes the XML document, plus the XSLT stylesheet you specify as the TransformSource property, and performs the transformation. The result is inserted into the page returned to the client. Figure 7 shows the result. This example uses the same style sheet as in the preceding IE5 Web service behavior example, so it generates the same output.

devxblackblue

About Our Editorial Process

At DevX, we’re dedicated to tech entrepreneurship. Our team closely follows industry shifts, new products, AI breakthroughs, technology trends, and funding announcements. Articles undergo thorough editing to ensure accuracy and clarity, reflecting DevX’s style and supporting entrepreneurs in the tech sphere.

See our full editorial policy.

About Our Journalist

©2024 Copyright DevX - All Rights Reserved. Registration or use of this site constitutes acceptance of our Terms of Service and Privacy Policy.