ost developers are now quite comfortable developing XML Web services. Visual Studio has a project template to create a new Web service project, but you don’t need to create a separate project; you can add an XML Web service to any existing Web application developed in .NET. To do that, right-click on the project item in the Solution Explorer and select the “Add Web Reference” item from the popup menu. Enter the URL of the Web service into the Address field at the top of the Add Web Reference dialog. Visual Studio automatically creates the files required to use the Web service and adds them to your project. After you add a Web reference, you’ll see the Web service appear in the Solution Explorer, similar to the project shown in Figure 1.
|Figure 1: This sample project has a default page (default.aspx) and an XML Web service (api.asmx).|
As a real-world example, Amazon’s Web services (AWS) give other Web sites and desktop applications access to Amazon’s lists of books, CDs etc., and let them use Amazon’s search engine to search for items using specified query parameters. Amazon has essentially outsourced Web page development by providing Web services to the partner Web sites.
The gist of this article is that you can offer Web services to friendly developers, and use the same code to populate your own Web site.
Create a Web Service
When you create an XML Web service, you make the service publicly available by creating a file with the extension asmx. For example, the sample project contains a Web service file called api.asmx. The asmx files themselves usually contain little code, just a reference to a code-behind module and a class in that module. In the sample project the code-behind class is called “api.”
When you create a new Web service in Visual Studio, the Web service template generates the asmx file and a code-behind module automatically. The code-behind module contains a sample function decorated with the
Public Function HelloWorld() As String return "Hello World" End Function
There have been a number of good articles on DevX about using attributes, so I won’t repeat them here; it’s sufficient to say that the WebMethod attribute instructs the ASP.NET runtime to build a SOAP interface around the class and also causes it to generate the sample pages seen when you navigate to the Web service in your browser. You can find the HelloWorld function in the api class in the sample project. The api class inherits from System.Web.Services.WebService.
The point to note here is that adding the WebMethod attribute to a method doesn’t prevent you from using it in other ways. For example, you can still call the HelloWorld method from your own Web pages in the usual way?by creating an instance of the api class, calling the function, and assigning the return value to a server-side label. If you have downloaded the sample project and set the startup document to default.aspx, compile and run the project to see the results, which look like Figure 2.
|Figure 2: The default.aspx file looks like this when loaded into a browser.|
To provide some benchmarks, the page contains some quick and dirty performance counter code that displays the elapsed times for the method calls.
Caching Web Services
In addition to the simplistic HelloWorld Web service, the class exposes a second Web service called ListCurrencies, which loads the XML file currency.xml into a DataSet, and returns the result. Here’s the method code:
_ Public Function ListCurrencies() As DataSet Dim ds As New DataSet("CurrencyList") ds.ReadXml(Server.MapPath("currency.xml")) Return ds End Function
In the section of the Web Form marked “Example 2”, the Button2_click event handler code creates an instance of the Web service as an ordinary class, and sets the DataSource property of the control Dropdownlist1 to the result of the ListCurrencies method. So when you click the “ListCurrencies” button the page calls the ListCurrencies method and populates the dropdown list (see Figure 3).
|Figure 3: When you click the “ListCurrencies” button, the page calls the ListCurrencies method and populates the drop-down list.|
Because any code that you run repeatedly consumes server resources, always consider caching the output when possible. The output of the ListCurrenciesCached method exposed by the api class illustrates how you can cache Web services. The api class inherits System.Web.Services.WebService, so it automatically “knows” about the current Context. The HttpContext class exposes a Cache property that returns an instance of a useful, high performance caching class that can hold objects in memory and retrieve them using simple name-value pairs?just as you use the HttpApplication object to store global data such as database connection strings or mail server information.
The difference between using the Cache object and the Application object is that you can tell the HttpContext.Cache to invalidate specific cached items on a regular schedule, at a specific time, or whenever a file changes. The code below checks to see if there is an object already in the cache with the key “CurrencyList”. If there is, the code casts the cached object back to a DataSet and returns it. If the object isn’t in the cache, the code calls the ListCurrencies method to obtain the DataSet, and then places the result in the cache using a file dependency. If any process updates the file while the data is cached, the cache flushes the object during the next request, and replaces it with the new data.
Public Function listCurrenciesCached() As DataSet If Not Context.Cache("CurrencyList") Is Nothing Then Return CType(Context.Cache("CurrencyList"), _ DataSet) Else Dim CurFile As String = _ Server.MapPath("currency.xml") Dim ds As New DataSet() ds = ListCurrencies() Context.Cache.Insert("CurrencyList", ds, _ New System.Web.Caching.CacheDependency _ (CurFile), _ Now.AddHours(1), Nothing) Return ds End If
The Web service is the “right” tier in which to do this caching?it is on the web server, in front of the Business Logic and Data Access code, but still available to any web page / User Interface that wants to use it.
Although this example is trivial the concept applies equally well to more complicated scenarios. Consider a case where you want to provide such a list based on the contents of a database table whose contents may change on a daily basis as new product lines are added. You would not want to query the database every time someone visits the page. You could arrange for the database to write an xml file whenever someone updates the table (The SQL Server web wizard makes this trivial) and then the above method would always present up-to date information, while the load on the database is hugely reduced.
For a shop site, while you would probably not put your entire product catalog into the cache, you might cache a subset of the product tables, perhaps “Department,” “Aisle,” and “Shelf,” and then select from the database depending on the cached “DepartmentID,” “AisleID,” and “ShelfID” values.
Reusing Web Service Code
Here’s another example of using Web services in both local and remote mode.
In a hypothetical online book store, it’s logical to provide “search by author” (see Figure 4) functionality both within the site and externally?and it’s not logical to write the search functionality twice.
|Figure 4: The sample application exposes a Web service that lists books by author.|
The page in Figure 4 uses the api class’ ListBooksByAuthor method, which accepts an author’s name as input and returns a DataSet. The DataSet format matches Amazon’s Web service description (WSDL) because I used their Web service to get some real world data. You can find the XML document containing the data for that DataSet in the sample project as the file petzold.xml. The Web Form Authors.aspx contains a populateGrid() function which creates an instance of the api class, and then sets the DataSource property of a grid control to the result of the ListBooksByAuthor() method, thus binding the grid to the DataSet.
Private Sub PopulateGrid() Dim oSearch As New api() With DataGrid1 .DataSource = oSearch.listBooksByAuthor _ ("Charles Petzold").Tables("Details") .DataBind() End With End Sub
When you click the button labeled “Books by Charles Petzold,” the page sets the grid’s DataSource property to the PopulateGrid method, which populates the grid with exactly the same data as external partners see by using the Web service (see Figure 5). Of course, making both internal and external (Web service) method results identical may not always be what you want. You can easily tailor the result by including a required partner ID parameter in the Web service method call, letting you differentiate between internal and external callers.
|Figure 5: The Web service listBooksByAuthor returns the same dataset whether you run it as a local method or as a Web service.|
By presenting and consuming your own search, list, and other methods as both local methods that your application uses, and as XML Web services, other sites can use those services to present data on your behalf?and you don’t have to do any extra work to make that happen. Better yet, you can add new functionality without having to maintain code in two places.