MSDN Magazine > Issues and Downloads > 2002 > August >  XML Files: Dynamic Discovery in .NET, Codebehin...
From the August 2002 issue of MSDN Magazine
MSDN Magazine
Dynamic Discovery in .NET, Codebehind, WebService Inheritance, and More
Aaron Skonnard XML Files Archive
Download the code for this article: XMLFiles0208.exe (49 KB)
Q Why did .vsdisco documents stop working with the final release of the Microsoft® .NET Framework?

A In the beta releases of the .NET Framework, the .vsdisco HTTP handler was automatically enabled, but for the final release the .NET team decided to disable it by default. If you open machine.config (from a path that looks something like c:\WINDOWS\Microsoft.NET\Framework\v1.0.3705\CONFIG\machine.config) and search for .vsdisco under /configuration/system.web/httpHandler, you'll see that the .vsdisco HTTP handler entry is commented out:
<!-- machine.config -->
•••
<httpHandlers>
<!--
<add verb="*" path="*.vsdisco" 
      type="System.Web.Services.Discovery.DiscoveryRequestHandler,
      System.Web.Services, Version=1.0.3300.0, Culture=neutral, 
      PublicKeyToken=b03f5f7f11d50a3a" validate="false"/>
-->
<add verb="*" path="*.aspx" 
      type="System.Web.UI.PageHandlerFactory"/>
      •••
      If you open the Microsoft Internet Information Services (IIS) management console and inspect the default application mappings that are configured by the .NET Framework installation, you'll see that the application mapping for .vsdisco documents is still in place (see Figure 1). This means that by default HTTP GET requests for .vsdisco documents are going to be handled by the static file handler (System.Web.StaticFileHandler), which returns the contents of the .vsdisco document, not the results of the dynamic discovery algorithm that searches for Web Service descriptions within the given vroot.

Figure 1 Application Mappings
Figure 1 Application Mappings

      To enable machine-wide dynamic discovery support, uncomment the .vsdisco httpHandler entry. If dynamic discovery should remain disabled at the machine level, you can turn it on at the vroot level by adding .vsdisco to the web.config file of the particular vroot, as shown here:
<!-- web.config -->
<configuration>
  <system.web>
    <httpHandlers>
      <add verb="*" path="*.vsdisco" 
       type="System.Web.Services.Discovery.DiscoveryRequestHandler,
       System.Web.Services, Version=1.0.3300.0, Culture=neutral, 
       PublicKeyToken=b03f5f7f11d50a3a" validate="false"/>
    </httpHandlers>
  </system.web>
</configuration>
You can just add this web.config file to any vroot that needs dynamic discovery (assuming the vroot hasn't removed the .vsdisco application mapping).
      It's reasonable to have the .vsdisco handler disabled by default since it allows clients to retrieve some of the details of a machine's Web Service configuration. Turning it on forces developers to make a decision before allowing clients to retrieve such information.

Q If I want to manage my Web Service's WSDL description, how can I make the .asmx handler return my static WSDL file?

A Even when you generate an ASP.NET page from a static WSDL document, the ASP.NET infrastructure continues dynamically generating a new WSDL document each time the ?wsdl query string is received. The dynamically generated WSDL document will be equivalent to the static WSDL document that served as the starting point for the class but it may not be exactly the same (and it won't include comments or other extensions that you may have added).
      You can ask the ASMX handler to import your static WSDL document by explicitly implementing an existing WSDL binding through a few customization attributes. First, you need to annotate the class declaration with the WebServiceBinding attribute to specify the name of the WSDL binding that this class implements (this is the name of the binding in the WSDL document) and supply the location of the WSDL document through the Location property. Then you need to annotate each WebMethod with the SoapDocumentMethod attribute and specify that it's part of the same binding implementation (through the Binding property). See Figure 2 for a complete example.
      Now if you issue an HTTP GET request against the .asmx endpoint, you should get a much simpler dynamically generated WSDL document that simply imports your static WSDL document through the WSDL import element:
<import namespace="urn:geometry" location="GeometryService.wsdl" />
WSDL tools such as wsdl.exe should be smart enough to deal with the import statement when generating proxies.

Q I'm really confused by the Codebehind attribute provided by ASP.NET for JIT compilation. It doesn't work like I'd expected.

A Three HTTP endpoint types come with JIT compilation semantics: .ashx, .asmx, and .aspx (see Figure 3). Each should have an ASP.NET directive at the top of the page. Here's an example of the @WebService directive in a .asmx page:
<%@ WebService language="C#" class="GeometryServiceImpl" %>
The directives typically require you to specify the target class as well as the implementation language, as you can see by default the code for the class is expected to be in the same file as the directive. If it is, the class is JIT compiled as you'd expect. Otherwise, what happens depends on the type of endpoint.
      With .ashx and .asmx files, the handler looks for an assembly in the vroot's bin directory or the Global Assembly Cache (GAC) that contains the referenced class. For these file types, you cannot separate the code from the directive and still take advantage of JIT compilation. Instead, you have to compile the assembly manually before testing the .ashx/.asmx endpoint.
      For .aspx files, the handler checks for a Src attribute on the @Page directive, as shown here:
<%@ Page Src="code.cs" ... %>
If it finds one, it JITs the referenced file. Otherwise, it assumes that you're going to manually compile the class derived from Page. In this case, you have to indicate the class name through the Inherits attribute, as shown here:
<%@ Page Inherits="MyPageDerivedClass" ... %>
Note that .aspx pages are the only ones that support the Src attribute, and therefore it's the only file type that supports JIT compilation of separate source files.
      The Codebehind attribute has nothing to do with JIT-compilation or even ASP.NET, for that matter. Codebehind is nothing more than a Visual Studio® .NET IDE-specific attribute used to associate .aspx or .asmx files with their underlying code. For example, when you create a Web application project in Visual Studio .NET, the Solution Explorer makes it look like it only created a single WebForm1.aspx file. This is only an illusion. If you press the Show All Files button on the Solution Explorer toolbar (see Figure 4), you'll see there is also a hidden source code file named WebForm1.aspx.cs. Opening it in Notepad reveals this @Page directive:
<%@ Page language="c#" Codebehind="WebForm1.aspx.cs" 
  AutoEventWireup="false" 
  Inherits="WebApplication1.WebForm1" %>
Notice that it uses the Codebehind attribute to figure out what source file goes with this .aspx page. Also, it uses the Inherits attribute as opposed to Src. Visual Studio .NET never uses the Src attribute by default, so you always have to compile your Visual Studio .NET Web application before testing.

Figure 4 Show All Files
Figure 4 Show All Files

      The situation is similar for Visual Studio .NET Web Service projects. After creating a new Web Service project, Visual Studio .NET actually creates two files (make sure you press Show All Files): Service1.asmx and Service1.asmx.cs. Opening Service1.asmx reveals the following:
<%@ WebService Language="c#" Codebehind="Service1.asmx.cs" 
  Class="WebService1.Service1" %>
Again, this means that you must compile the Web Service before attempting to use the .asmx endpoint.

Q Does an ASP.NET Web Service class have to derive from System.Web.Services.WebService?

A No. Deriving from System.Web.Services.WebService is a convenience for working with certain HttpContext properties. For example, the following class does not derive from WebService but still serves as an .asmx endpoint:
public class MathService {
  [System.Web.Services.WebMethod]
  public int Add(int n1, int n2) {
    return n1 + n2;
  }
}
      Since .asmx-bound classes are really an abstraction for the underlying HTTP handler, you do have access to the HttpContext object for the current HTTP request. You can access the current object through the HttpContext.Current static property. Once you get your hands on the HttpContext object, you have access to all of its properties just like Application, Session, Server, and User.
using System.Web;
using System.Web.Services;
public class MathService {
  [WebMethod]
  public int Add(int n1, int n2) {
    int sum = n1 + n2;
    HttpContext.Current.Application["last_sum"] = sum;
    return sum;
  }
}
      Deriving your class from System.Web.Services.WebService simply gives you easier access to these properties:
using System.Web;
using System.Web.Services;
public class MathService : WebService {
  [WebMethod]
  public int Add(int n1, int n2) {
    int sum = n1 + n2;
    Application["last_sum"] = sum;
    return sum;
  }
}

Q I want my ASP.NET Web Services to support only the SOAP protocol, not HTTP GET and POST. How do I disable them?

A ASP.NET supports several Web Service protocols by default. You can find these in machine.config under the XML path /configuration/system.web/webServices/protocols. Four are listed by default: HttpSoap, HttpGet, HttpPost, and Documentation. Simply comment out the add entries to disable them for the entire machine:
<!-- machine.config -->
<configuration>
  <system.web>
    <webServices>
      <protocols>
        <add name="HttpSoap"/> 
          <!-- disable for entire machine
          <add name="HttpPost"/> 
          <add name="HttpGet"/> 
          -->
        <add name="Documentation"/> 
       •••
</configuration>
      If you'd like to leave them enabled by default, but disabled for a certain vroot, use remove elements in the vroot's web.config:
<!-- web.config -->
<configuration>
  <system.web>
    <webServices>
      <protocols>
        <remove name="HttpPost"/> 
        <remove name="HttpGet"/> 
      •••
</configuration>
      The HttpGet and HttpPost protocols were designed to facilitate browser-based Web Service clients (they both expect HTML FORM-based input). For security, it's a good idea to disable them unless you explicitly need such support. There's just too much HTTP trickery available to Web hackers who want to create havoc.

Q Is it possible to customize the documentation page created when navigating to an .asmx endpoint?

A Yes. The default .asmx documentation is generated from an .aspx page that can be found with the other framework .config files (c:\WINDOWS\Microsoft.NET\Framework\v1.0.3705\CONFIG). The file name is DefaultWsdlHelpGenerator.aspx. Look in the file's Page_Load event to begin figuring out how it works. If you simply want to change the look of the page and add some descriptive text, you only need to modify the HTML template within the page without touching the code. But if you're really ambitious, you can write your own documentation generator completely from scratch.
      You can specify which .aspx file is used by the documentation handler. In machine.config under /configuration/system.web/webServices, you'll find the wsdlHelpGenerator element that allows you to specify an .aspx page. You can either modify the href attribute for the entire machine or you can override its value for a particular vroot in web.config, as shown here:
<!-- web.config -->
<configuration>
   <system.web>
      <webServices>
         <wsdlHelpGenerator href="MyDocs.aspx" />
      </webServices>
   </system.web>
</configuration>
Q Is there an easy way to test invoking SOAP-based Web Services?

A You must issue an HTTP POST request to test a SOAP-based Web Service. The HttpGet protocol makes it easy to do quick browser-based tests, but the only way to test the SOAP protocol is through an explicit SOAP POST. You can either write the code to programmatically issue the request (see System.Net) or you can use a utility that provides the functionality. I've always used a simple JScript® utility called post.js for this kind of testing; it requires MSXML 3.0 and provides the following functionality:
usage: post uri [options]
  -f inputFile
  -s inputString
  -o outputFile
  -h headerName headerValue
Notice that it allows you to specify arbitrary HTTP headers on the command line and you can post either a string or the contents of an entire file. This command line invokes an .asmx endpoint:
C:\temp>post http://localhost/geo/geometryservice.asmx 
       -h Content-Type "text/xml"
       -h SOAPAction "urn:geometry#CalcDistance" 
       -f req.xml
      The req.xml file contains the appropriate SOAP request message for the given operation. Note that when you're testing .asmx endpoints, you must supply the correct SOAPAction header along with a Content-Type header of text/xml. You can download post.js with the sample code for this column or from my Web site.

Q What's the easiest way to generate a SOAP fault within an ASP.NET WebMethod?

A Actually, you don't have to do anything. The WebMethod infrastructure automatically translates unhandled exceptions into Fault elements inside the SOAP response. Use the SoapException class when you want to explicitly generate a SOAP Fault:
public class MathService {
  [WebMethod]
  public int Divide(int n1, int n2) {
    if (n2 == 0)
      throw new SoapException("Cannot divide by 0",
                  SoapException.ClientFaultCode);
    return n1 + n2;
  }
}
Send questions and comments for Aaron to xmlfiles@microsoft.com.
Aaron Skonnard is an instructor/researcher at DevelopMentor, where he develops XML and Web Service-related courses. Aaron coauthored Essential XML Quick Reference (Addison-Wesley, 2001) and Essential XML (Addison-Wesley, 2000). Reach him at http://staff.develop.com/aarons/.

Page view tracker