ASP.NET

A Web Service in ASP.NET

ASP.NET handles Web services with a limited amount of programming effort. Remember how the ASP.NET pipeline architecture works. Requests coming from clients end up at the server's port 80. ASP.NET Web services live in a file type named with the extension .asmx. If the server is running ASP.NET, IIS routes the request for files with the ASMX extension to ASP.NET, where they're handled like any other request.

ASP.NET includes an attribute named [WebMethod] that maps a SOAP request and its response to a real method in a class. To make the service work, you simply derive a class from System.Web.Services.Service and expose methods using the [WebMethod]. When the request comes through, the target class will be "bound" to the .asmx endpoint. As with normal page execution, the current HttpContext is always available. In addition, ASP.NET automates WSDL generation, and Microsoft provides tools to automate generating client-side proxies given the WSDL.

The following example illustrates a Web service that retrieves quotes from the quotes collection we saw in Tutorial 11 ("Databinding"), Tutorial 14 ("Application Data Caching"), and Tutorial 17 ("The Application and HTTP Modules"). This example will expose the quotes collection via a set of methods expressed as a Web service.

Write an ASP.NET Web Service

  1. Create a new Web site project. Name the project WebServicesORama. Make it an HTTP site that uses IIS.

  2. Rename the class from Service to QuoteService. Rename the code file from Service.cs to QuoteService.cs. Rename the ASMX file from Service.asmx to QuoteService.asmx.

  3. After Visual Studio is done, you'll get a stubbed-out Web service that looks like this:

    using System;
    using System.Web;
    using System.Web.Services;
    using System.Web.Services.Protocols;
    [WebService(Namespace = "http://tempuri.org/"")]
    [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
    public class QuoteService : System.Web.Services.WebService
    {
        public Service () {
    
        }
    
        [WebMethod]
        public string HelloWorld() {
            return "Hello World";
        }
    }
    

    You'll also get an ASMX file. The Web service handler (named ASMX, with "M" standing for method) works very much like the ASPX page handlers and the ASHX custom handlers. When clients surf to the ASMX page, IIS redirects the request to the ASP.NET ISAPI DLL. Once there, ASP.NET compiles the code associated with the ASMX file and runs it just as it would any other HTTP handler. Here's what the ASMX file looks like. There's not much here. Most of the code lies within the accompanying code file.

    <%@ WebService Language="C#"
    CodeBehind="~/App_Code/Service.cs" %>
    
  4. Surf to the QuoteService.asmx file to see what a default GET renders:

    Graphic

    By default, ASP.NET renders the names of the available methods when you just GET the ASMX file. Notice that the HelloWorld method (provided by Visual Studio) is exposed. If you want to try running the HelloWorld method, you can click on the HelloWorld link, which renders a new page with a button you can click to invoke the method.

    Graphic
  5. Before adding any code, click the Service Description link. The Web service will send back the WSDL for the site. You can page through it to see what WSDL looks like. This data is not meant for human consumption, but rather for client proxy generators (which we'll examine shortly).

    Graphic
  6. To have some quotes to expose as Web methods, import the QuotesCollection from Tutorial 14. The project name is UseDataCaching. Highlight the App_Code node within the solution explorer. Select Web Site | Add Existing Item from the main menu and find the file QuotesCollection.cs. In addition to importing the QuotesCollection.cs file, grab the QuotesCollection.xml and QuotesCollection.xsd files from the UseDataCaching\App_Data directory and place them in the App_Data directory for this project.

  7. Write a method to load the QuotesCollection. Check first to see if the QuotesCollection is in the cache. If not, create a QuotesCollection object and load it using the quotescollection.xml and quotescollection.xsd files. Load the quotes into the application cache during the construction of the QuoteService class. When you add the data to the cache, build a dependency upon the quotescollection.xml file. One of the Web methods we'll add will modify the XML file, so we'll want to flush it from the cache when it's updated.

    using System;
    using System.Web;
    using System.Data;
    using System.Web.Services;
    using System.Web.Services.Protocols;
    using System.Web.Caching;
    
    [WebService(Namespace = "http://tempuri.org/")]
    [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
    public class QuoteService : System.Web.Services.WebService
    {
    
       QuotesCollection LoadQuotes()
       {
          QuotesCollection quotesCollection;
    
          HttpContext ctx = HttpContext.Current;
          quotesCollection = (QuotesCollection)ctx.Cache["quotesCollection"];
          if (quotesCollection == null)
          {
             quotesCollection = new QuotesCollection();
             String strAppPath = Server.MapPath("");
    
             String strFilePathXml =
                strAppPath +
                "\\app_data\\QuotesCollection.xml";
             String strFilePathSchema =
                strAppPath +
                "\\app_data\\QuotesCollection.xsd";
    
             quotesCollection.ReadXmlSchema(strFilePathSchema);
             quotesCollection.ReadXml(strFilePathXml);
    
             CacheDependency cacheDependency =
                new CacheDependency(strFilePathXml);
    
             ctx.Cache.Insert("quotesCollection",
                      quotesCollection,
                      cacheDependency,
                      Cache.NoAbsoluteExpiration,
                      Cache.NoSlidingExpiration,
                      CacheItemPriority.Default,
                      null);
          }
          return quotesCollection;
       }
    
        public QuoteService() {
        }
    
        [WebMethod]
        public string HelloWorld() {
            return "Hello World";
        }
    }
    
  8. Now write a method that gets a random quote from the table and send it back to the client. The QuotesCollection class inherits from the DataTable class, which is a collection of DataRows. Unfortunately, returning a DataRow from a Web method doesn't work because DataRow doesn't have a default constructor. So instead, add a new struct to the Web service that wraps the quote data. That is, a struct that contains strings for the quote, the originator's first name, and the originator's last name.

    Name the method for fetching a quote GetAQuote. Have GetAQuote load the quotes using LoadQuotes. The GetAQuote method should generate a number between zero and the number of rows in the QuotesCollection, fetch that row from the table, wrap the data in a Quote structure, and return it to the client. Be sure to adorn the GetAQuote method with the [WebMethod] attribute.

    using System;
    using System.Web;
    using System.Data;
    using System.Web.Services;
    using System.Web.Services.Protocols;
    using System.Web.Caching;
    
     public struct Quote
     {
       public String _strQuote;
       public String _strOriginatorLastName;
       public String _strOriginatorFirstName;
    
       public Quote(String strQuote,
                    String strOriginatorLastName,
                    String strOriginatorFirstName)
       {
           _strQuote = strQuote;
          _strOriginatorLastName = strOriginatorLastName;
          _strOriginatorFirstName = strOriginatorFirstName;
       }
    }
    
    [WebService(Namespace = "http://tempuri.org/")]
    [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
    public class QuoteService : System.Web.Services.WebService
    {
    
    
       QuotesCollection LoadQuotes()
       {
          QuotesCollection quotesCollection;
    
          HttpContext ctx = HttpContext.Current;
          quotesCollection = (QuotesCollection)ctx.Cache["quotesCollection"];
          if (quotesCollection == null)
          {
             quotesCollection = new QuotesCollection();
             String strAppPath = Server.MapPath("");
    
             String strFilePathXml =
                strAppPath + "\\app_data\\QuotesCollection.xml";
             String strFilePathSchema =
                strAppPath + "\\app_data\\QuotesCollection.xsd";
             quotesCollection.ReadXmlSchema(strFilePathSchema);
             quotesCollection.ReadXml(strFilePathXml);
    
             CacheDependency cacheDependency =
                new CacheDependency(strFilePathXml);
    
             ctx.Cache.Insert("quotesCollection",
                      quotesCollection,
                      cacheDependency,
                      Cache.NoAbsoluteExpiration,
                      Cache.NoSlidingExpiration,
                      CacheItemPriority.Default,
                      null);
          }
          return quotesCollection;
       }
    
        public QuoteService () {
        }
        [WebMethod]
        public string HelloWorld() {
            return "Hello World";
        }
    
       [WebMethod]
       public Quote GetAQuote()
       {
          QuotesCollection quotesCollection = this.LoadQuotes();
          int nNumQuotes = quotesCollection.Rows.Count;
          Random random = new Random();
          int nQuote = random.Next(nNumQuotes);
          DataRow dataRow = quotesCollection.Rows[nQuote];
          Quote quote = new Quote((String)dataRow["Quote"],
                            (String)dataRow["OriginatorLastName"],
                            (String)dataRow["OriginatorFirstName"]);
          return quote;
        }
    
    }
    
  9. Finally, add two more methods: one to add a quote to the QuotesCollection and another to fetch all the quotes. Name the method for adding quotes AddQuote. AddQuote should take a Quote structure as a parameter and use it to create a new row in the QuotesCollection. AddQuote should reserialize the XML and XSD files.

    GetAllQuotes should load the quotes from the cache, place the QuotesCollection in a DataSet, and return a DataSet. Use a DataSet because it marshals very cleanly back to the client. Be sure to adorn the methods with the [] attribute.

    using System;
    using System.Web;
    using System.Data;
    using System.Web.Services;
    using System.Web.Services.Protocols;
    using System.Web.Caching;
    
    public struct Quote
    {
       public String _strQuote;
       public String _strOriginatorLastName;
       public String _strOriginatorFirstName;
       public Quote(String strQuote,
                    String strOriginatorLastName,
                    String strOriginatorFirstName)
       {
           _strQuote = strQuote;
          _strOriginatorLastName = strOriginatorLastName;
          _strOriginatorFirstName = strOriginatorFirstName;
       }
    }
    
    [WebService(Namespace = "http://tempuri.org/")]
    [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
    public class QuoteService : System.Web.Services.WebService
    {
    
       QuotesCollection LoadQuotes()
       {
          QuotesCollection quotesCollection;
    
          HttpContext ctx = HttpContext.Current;
          quotesCollection = (QuotesCollection)ctx.Cache["quotesCollection"];
          if (quotesCollection == null)
          {
             quotesCollection = new QuotesCollection();
             String strAppPath = Server.MapPath("");
    
             String strFilePathXml =
                strAppPath + "\\app_data\\QuotesCollection.xml";
             String strFilePathSchema =
                strAppPath + "\\app_data\\QuotesCollection.xsd";
    
             quotesCollection.ReadXmlSchema(strFilePathSchema);
             quotesCollection.ReadXml(strFilePathXml);
    
             CacheDependency cacheDependency =
                new CacheDependency(strFilePathXml);
    
             ctx.Cache.Insert("quotesCollection",
                      quotesCollection,
                      cacheDependency,
                      Cache.NoAbsoluteExpiration,
                      Cache.NoSlidingExpiration,
                      CacheItemPriority.Default,
                      null);
          }
          return quotesCollection;
        }
    
        public QuoteService () {
    
        }
    
        [WebMethod]
        public string HelloWorld() {
            return "Hello World";
        }
    
       [WebMethod]
       public Quote GetAQuote()
       {
          QuotesCollection quotesCollection = this.LoadQuotes();
          int nNumQuotes = quotesCollection.Rows.Count;
    
          Random random = new Random();
          int nQuote = random.Next(nNumQuotes);
          DataRow dataRow = quotesCollection.Rows[nQuote];
          Quote quote = new Quote((String)dataRow["Quote"],
                            (String)dataRow["OriginatorLastName"],
                            (String)dataRow["OriginatorFirstName"]);
          return quote;
       }
    [WebMethod]
       public void AddQuote(Quote quote)
       {
          QuotesCollection quotesCollection = this.LoadQuotes();
    
          DataRow dr = quotesCollection.NewRow();
          dr[0] = quote._strQuote;
          dr[1] = quote._strOriginatorLastName;
          dr[2] = quote._strOriginatorFirstName;
          quotesCollection.Rows.Add(dr);
    
          String strAppPath = Server.MapPath("");
          String strFilePathXml =
             strAppPath + "\\app_data\\QuotesCollection.xml";
          String strFilePathSchema =
             strAppPath + "\\app_data\\QuotesCollection.xsd";
    
          quotesCollection.WriteXmlSchema(strFilePathSchema);
          quotesCollection.WriteXml(strFilePathXml);
       }
    
       [WebMethod]
       public DataSet GetAllQuotes()
       {
          QuotesCollection quotesCollection = LoadQuotes();
          DataSet dataSet = new DataSet();
          dataSet.Tables.Add(quotesCollection);
          return dataSet;
       }
    
    }
    

You now have a Web service that will deliver quotes to the client upon request. You can surf to the ASMX page and try out the methods if you want to see them work. However, the real power lies in writing clients against the Web service so the client can consume the Web Service programmatically.