Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Create Asynchronous Web Services with WSE v2.0 and ASP.NET SimpleHandlerFactory

0.00/5 (No votes)
23 Dec 2004 1  
This article describes another way to implement asychronous webservices using ASP.NET and WSE v2.0.

Introduction

This article explains how to use Web Service Enhancements version 2.0 and the ASP.NET built in Handler to create a custom one way asynchronous Web Service. The major purpose of this exercise is to demonstrate a quick and dirty way to implement a �Remote Procedure Call� modeled one way and a normal two way web service. It is by no means a recommended way to implement a production web service being utilized in a Business to Business (B2B) e-commerce application and this article will explain why.

Overview

With Service Oriented Architecture being the future paradigm in Software Design patterns, it is necessary to discuss what ASP.NET 1.1 and WSE v2.0 gives today in order to prepare for tomorrow�s Indigo and other wave of SOA systems. Let�s get started by explaining what WSE is, then we�ll venture into the world of ASP.NET and its technologies, specifically HTTP Handlers. Afterwards, we�ll dive into a decent depth of WSE and how to implement the web service.

WSE v2.0 Introduction

WSE stands for Web Service Enhancements. To keep this intro quick and simple, it�s a .NET Shared Component that can be downloaded and installed into current versions of the .NET Framework. Currently, it�s in version 2.0 of its official release, with its next version probably to be released fairly soon. Behind the scenes, the component is implemented through many different namespaces and classes within that allow a developer to add Industry security Standards; to create industry SOAP message formats that allow easier interop between different systems such as Java Web Services, IBM web services and BEA Web Services; and to apply other community standards normally referred to as a set of specifications called WS-* (WS-Security, WS-I, WS-Addressing, WS-Messaging, etc�). WSE is a Soap Extension that manipulates SOAP messages on both the client side and the server side if implemented in both.

ASP.NET v1.1 HTTP Handler Introduction

ASP.NET is Microsoft�s successor to ASP, it�s a dynamic way to generate web pages back to a Web Browser such as Internet Explorer, FireFox and Netscape, using Microsoft. NET. With the advent of the concept of dynamic web pages came the birth of Web Services. A web service is a component that you can access using the HTTP model to send data and commands, normally called messages, to it. If you were to take the abstract of Web Services, it would yield you into the world of Service Oriented Architecture (SOA). ASP.NET Web Services are one way to implement Services in SOA. To keep to the point here, ASP.NET Web Services normally use XML as its format to send and receive messages. Specifically speaking, it uses a SOAP XML format. ASP.NET makes it easy for developers to use this concept to create web services. It has a technology called "ASMX" that allows these SOAP messages to be automatically parsed and generated to access the remote component. ASP.NET implements this and other technologies like this through the use of HTTP Handlers and Soap Extensions (see machine.config snippet).

An ASP.NET HTTP Handler would be equivalent to what an IIS Filter could do (for those C++ developers out there.) The HTTP Handler has the job of handling HTTP Requests and HTTP Responses that come in and out of ASP.NET. It can modify the Response/Request, by adding to it, removing from it, or totally creating a new object of data. An HTTP Handler is usually registered to ASP.NET by the HTTP verb (GET, POST, PUT, etc.), the path (*-all, *.asmx- files ending in .asmx, etc.), and the component that contains the code to execute to modify or parse the Response/Requests (.NET Assembly). This setting is set through an Application�s configuration file. For more robust custom implementations, an ASP.NET HTTP Handler may also need to be registered to IIS, see Watermark Website Images At Runtime by KingLion.

ASP.NET SimpleHandlerFactory

ASP.NET already comes with some HTTP Handlers and HTTP Handler Factories. A HttpHandlerFactory is a class whose job is to create instances of Handlers based off of some logical coded criteria. To create a HttpHandlerFactory, create a class that implements the IHttpHandlerFactory. This class has two methods to implement.

IHttpHandler GetHandler(HttpContext currentcontext, string reqType,
    string url, string pathTranslated);
void ReleaseHandler(System.Web.IHttpHandler handlerToRelease);

These factories are registered the same way a HttpHandler is registered, thus I won�t spend too much time on those. We have HttpHandlers that deal with security, such as the FormsAuthenticationHandler. We also have some handlers that deal with restricting access to certain types of files such as the HttpForbiddenHandler. There is also, the all important PageHandler that handles ASP.NET Web Form Pages and the WebServiceHandler that handles .asmx pages (ASP.NET web services). There is also a SimpleHandlerFactory that is mapped/registered to .ashx file extensions. We also have the ability to create our own custom HTTP Handler mapped to our own extensions. (see machine.config snippet)

How To Create a HttpHandler that SimpleHandlerFactory can use

The SimpleHandlerFactory allows a developer to create a HTTP Handler just like creating an ASP.NET web page, check it out:

In VS.NET, create a class library project, add a reference to System.Web.Dll, create a class such as this: Class1.cs

using System.Web;
using System.Web.SessionState;

namespace 
TimeStampRequests
{
/// <summary>

/// Summary description for 

Class1.
/// </summary>

  public class TimeStampReq : IHttpHandler , IRequiresSessionState 
  {
    public TimeStampReq(){}
  
    public void ProcessRequest(HttpContext ctxt)
    {
      //ctxt.Application - HttpApplicationState

      //ctxt.Server

      //ctxt.Session - SessionState 

      //     (Requires IRequiresSessionState implementation)

      //ctxt.Cache - Cache Object

      //ctxt.Request - HttpRequest 

      //ctxt.Response - HttpResponse

      //Perform any operation you want...

    }

    public bool IsReusable
    {
      get
      {
        return true; //No State between requests

      }
    }
  }
}

This next part is optional, you can create a Page.ashx file if you�d like. However, it's not necessary:

Page1.ashx:

<%@ WebHandler Language="�c#�" class=�TimeStampRequests.TimeStampReq� %>

You could also use code-behind, or include the source in the same file just like in ASP.NET Web Form Pages.

The next part is to register the Simple Handler in a Web Config file like this:

<system.web>
<httpHandlers>
<add verb=�*� path=�*.ashx�
    type=�TimeStampRequests.TimeStampReq, TimeStampRequest�/>
</httpHandlers>
</system.web>

The code explanation goes as follows, create a class and implement the IHttpHandler interface. This interface has one function and one property you must implement. The method is called ProcessMessage. Here is the signature:

void ProcessMessage(HttpContext context)

This method is called when the ASP.NET worker process hands off the Request or Response to be processed. It passes into this method the current Web environment data in the form of an object called HttpContext. This HttpContext object can be used to read Server variables, Session data, Response specific data, Request data and the Application object pool of data as well.

The next implementation is the property called IsReusable, its signature looks like this:

bool IsReusable { get; }

This property determines whether or not you want to maintain state between requests. Once you implement this method and property, a HttpHandlerFactory can create an instance of the Handler and let ASP.NET worker process call into it to process the Request or Response.

There was a Microsoft Web cast (www.microsoft.com/webcasts, www.dasblonde.net) as well as a CodeProject article "Watermark Website Images At Runtime" by KingLeon where HTTP Handlers were used to create watermarks on images. This is the basic blue print for utilizing your own custom built Handler using the SimpleHandlerFactory. There are other more complex ways to create a Custom handler, however, the steps don�t differ that much at all. The built in SimpleHandlerFactory will create an instance of your Handler when ever a request for a .ashx page comes to the ASP.NET virtual directory application. Your code will then execute its ProcessRequest method and run.

Machine.config File, HttpHandlers and HttpHandlerFactories

Here is a snippet of the Machine.config file for .NET 1.1, again showing some available HTTP Handlers:

<httpHandlers>
<add verb="*" path="*.vjsproj" 
        type="System.Web.HttpForbiddenHandler"/>
<add verb="*" path="*.java" 
        type="System.Web.HttpForbiddenHandler"/>
<add verb="*" path="*.jsl" 
       type="System.Web.HttpForbiddenHandler"/>
<!--<add verb="*" 
      path="*.vsdisco" 
      type="System.Web.Services.Discovery.DiscoveryRequestHandler, 
      System.Web.Services, Version=1.0.5000.0, Culture=neutral, 
      PublicKeyToken=b03f5f7f11d50a3a" validate="false"/>-->
<add verb="*" 
       path="trace.axd" type="System.Web.Handlers.TraceHandler"/>
<add 
       verb="*" path="*.aspx" type="System.Web.UI.PageHandlerFactory"/>
<add 
       verb="*" path="*.ashx" type="System.Web.UI.SimpleHandlerFactory"/>
<add 
       verb="*" path="*.asmx" 
       type="System.Web.Services.Protocols.WebServiceHandlerFactory, 
             System.Web.Services, Version=1.0.5000.0, Culture=neutral, 
             PublicKeyToken=b03f5f7f11d50a3a" validate="false"/>
<add verb="*" 
        path="*.rem" 
        type="System.Runtime.Remoting.Channels.Http.HttpRemotingHandlerFactory, 
              System.Runtime.Remoting, Version=1.0.5000.0, Culture=neutral, 
              PublicKeyToken=b77a5c561934e089" validate="false"/>
<add verb="*" path="*.soap" 
         type="System.Runtime.Remoting.Channels.Http.HttpRemotingHandlerFactory, 
         System.Runtime.Remoting, Version=1.0.5000.0, Culture=neutral, 
         PublicKeyToken=b77a5c561934e089" validate="false"/>

Diving Into WSE with HttpHandlers

Now, enough with all this HTTP Handler stuff, what about WSE and making an Asynchronous Web Service?

WSE v2.0 has some classes that allow standardized SOAP messages to be sent based off of WS-Addressing, WS-Messaging and the rest of WS-* specifications (see www.w3c.org, www.ws-i.org). In particular, we�re going to focus on the communication implementations of WSE, SoapSenders /SoapReceivers and SoapClients /SoapServices.

The most interesting part about these classes is that a SoapService class inherits from a SoapReceiver class. A SoapReceiver class implements the IHttpHandler interface. This means that if you create a class inheriting from SoapReceiver or SoapService class, it can be used with the SimpleHandlerFactory to allow you to process the Request or Response. If the Request/Response has a SOAP message inside its body section, you have just effectively implemented a Web Service, which is exactly how WebServiceHandler works.

Let�s see if we can achieve this goal, here�s the code below:

using Microsoft.Web.Services2;
using Microsoft.Web.Services2.Messaging;
using Microsoft.Web.Services2.Addressing;

namespace WSE_AsynWebSvc
{
  public class AsyncWebSvc : SoapReceiver
  {
    public AsyncWebSvc(){}
    private EndpointReference epSendTo;

    protected override void Receive(SoapEnvelope envelope)
    {
      epSendTo = new EndpointReference(new 
        Uri(envelope.Context.Addressing.ReplyTo.Address.Value.ToString()));
      SoapEnvelope retEnv = new SoapEnvelope();
      retEnv.CreateBody();
      retEnv.SetBodyObject("success");
      retEnv.Context.Addressing.To = new To(epSendTo.Address);
      retEnv.Context.Addressing.Action = 
             new Action(epSendTo.Address.Value.ToString() );
      retEnv.Context.Addressing.ReplyTo = new ReplyTo(epSendTo);
      SoapSender ssend = new SoapSender(epSendTo);
      ssend.Send(retEnv);
    }
    //Here's our IHttHandler ProcessMessage implementation

    //SoapReceiver already implements this function for us anyway

    //We can override the SoapReceivers implementation


    public override void ProcessMessage(SoapEnvelope message)
    {
      base.ProcessMessage (message);
    }
  }
}

Now, let�s register it. Here�s the configuration file:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
   <configSections>
     <section name="microsoft.web.services2" 
            type="Microsoft.Web.Services2.Configuration.WebServicesConfiguration, 
                  Microsoft.Web.Services2, Version=2.0.0.0, Culture=neutral, 
                  PublicKeyToken=31bf3856ad364e35" />
   </configSections>
<system.web>
  <httpHandlers>
     <add type="WSE_AsncWebSvc.AsyncWebSvc" path="*.ashx" verb="*" />
  </httpHandlers>
</system.web>
<microsoft.web.services2>
  <diagnostics>
    <trace enabled="true" input="InputTrace.webinfo" output="OutputTrace.webinfo" />
    <detailedErrors enabled="true" />
  </diagnostics>
</microsoft.web.services2>
</configuration>

Once we add the <system.web> section to our web.config file, our Web service can be accessed through the .ashx file extension.

Code Explanation

Let�s talk about the code. We first create a class called AsyncWebSvc. We derive this class from the SoapReceiver class. The SoapReceiver class already handles the implementation of ProcessMessage, so we don�t have to worry about that unless we want to. In other words, we could override the base�s implementation and add our own code here as shown in the code. The SoapReceiver class is found within the Microsoft.Web.Services2.Messaging namespace that performs asynchronous SOAP parsing. This class parses the HttpRequest body section and looks for a SOAP message. When one is found, it makes a call into the Receive method passing in this SOAP message XML format to allow developers to manipulate and parse to perform some action. This is all you need to do. Currently, if we stop here, and we don�t need to send a response back, this is what we�d call a One-way Asynchronous web service.

If we want to send a response back, WSE uses the SoapSender and SoapClient classes for sending SOAP messages. To send a message using WSE, we first must create the message to send. Using WSE, we create a SoapEnvelope class that represents the SOAP message to send. This SoapEnvelope class gives developers programmatic control over the raw SOAP XML format for exactly what data they want to wrap inside the body of the message. WSE has the task of formatting it so that the message adheres to the SOAP XML WS-* community standard. The code shows one way on how to create a SoapEnvelope and send it using a SoapSender class instance. The only other piece to explain is: where are we sending the message to anyway?

Client Application To Send And Receive SOAP messages

For a normal Asynchronous two way web service, the SoapSender class instance is created by passing in an Uri address (basically, a URL address not specifically based on HTTP protocols, it�s more generic in nature). In WSE 2.0, Uri addresses are wrapped up inside of a class called an EndpointReference. This is done this way because of the WS-Addressing and WS-Messaging standards. A client application, be it another Web Service or an ASP.NET application or even a Windows Form, can create a listening port to listen for SOAP responses. Here�s the code for a Windows Form client app, it creates a EndpointReference for itself and then makes a call into this web service using WSE 2.0 and gets a response back asynchronously:

using System;
using System.Windows.Forms;
using Microsoft.Web.Services2;
using Microsoft.Web.Services2.Addressing;
using Microsoft.Web.Services2.Messaging;

namespace UsingAsyncWebSvc
{
  //create a class that inherits from the base Form class

  public class Form1 : System.Windows.Forms.Form
  {

    //create some controls that display and execute 

    //the method on the web service

    internal System.Windows.Forms.Label label1;
    private System.Windows.Forms.Button button1;
    private System.ComponentModel.Container components = null;

    public Form1()
    {
      //Initialize the Controls and position them; 

      //set up event handlers etc�

      InitializeComponent();
    }
    //basic clean up code without using a destructor

    protected override void Dispose( bool disposing )
    {
      if( disposing )
      {
        if (components != null)
        {
          components.Dispose();
        }
      }
      base.Dispose( disposing );
    }

    //Method to initialize controls, positioning on form, colors and etc.

    private void InitializeComponent()
    {
      this.label1 = new System.Windows.Forms.Label();
      this.button1 = new System.Windows.Forms.Button();
      this.SuspendLayout();
      this.label1.Location = new System.Drawing.Point(16, 8);
      this.label1.Name = "label1";
      this.label1.Size = new System.Drawing.Size(264, 96);
      this.label1.TabIndex = 0;
      this.label1.Text = "label1";
      this.button1.Location = new System.Drawing.Point(200, 120);
      this.button1.Name = "button1";
      this.button1.TabIndex = 1;
      this.button1.Text = "button1";
      this.button1.Click += new System.EventHandler(this.button1_Click);
      this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
      this.ClientSize = new System.Drawing.Size(292, 150);
      this.Controls.Add(this.button1);
      this.Controls.Add(this.label1);
      this.Name = "Form1";
      this.Text = "Form1";
      this.ResumeLayout(false);
    }
    //Entry point for the Exe application

    [STAThread]
    static void Main()
    {
      Application.Run(new Form1());
    }
    //handles the button click event

    private void button1_Click(object sender, System.EventArgs e)
    {
      //Create a URI address and point it to the current client system name

      //use the tcp protocol; open port 3119; 

      //and a fake path of MyServiceReceive

      //the path equates to the Action in a Soap Message that has multiple

      //Operations it can perform

      Uri uriMe = new Uri("soap.tcp://" + System.Net.Dns.GetHostName() +
        ":3119/MyServiceReceive");
      EndpointReference epMe = new EndpointReference(uriMe);

      //To receive SoapMessage in WSE, we use a class that derives from

      //SoapReceiver and overrides the Receive Method.

      //Thus we create a ReceiveData Class instance and use this class

      ReceiveData receive = new ReceiveData();
      receive.frm = this;

      //We add this class along with the Uri pointing 

      //to where we plan to listen

      //to the AppDomain�s SoapReceivers collection of SoapReceivers


      SoapReceivers.Add(epMe, receive);

      //Now that we�re listening for responses let�s setup the 

      //SoapMessage and the SoapSender to send the message

      //to the Web service we created


      SoapSender sSend = new SoapSender();
      SoapEnvelope env = new SoapEnvelope ();

      //To pass data we must initialize the body of the SoapMessage

      env.CreateBody();

      //WS-Addressing specifies that we can re-route our SoapMessages

      //We can also apply a reply to address 

      //so that the Web Service can reply back

      //to the uri we give here.


      env.Context.Addressing.ReplyTo = new ReplyTo(uriMe);

      //We now tell our SoapMessage where it will be sent to

      Uri uriTo =new Uri("http://"+ System.Net.Dns.GetHostName() +
             "/MyAsyncWebSvc/MyService.ashx");
      EndpointReference epTo = new EndpointReference(uriTo);

      //We load some data into the body of the SoapMessage

      env.SetBodyObject("<data>dosomething</data>");

      //We set up Uri Address

      env.Context.Addressing.To = new To(epTo.Address );

      //We tell the Web Service which method of function we want to call

      env.Context.Addressing.Action = new Action("MyRequest");
      sSend.Destination = uriTo;

      //We send the message asynchronously

      sSend.Send(env);
      this.label1.Text = "Message Sent";
    }
  }

  //This is our Clients Receiving communication to WSE v2.0

  //To receive a SoapMessage using WSE manually, we create

  // a class inheriting the SoapReceiver class


  public class ReceiveData : SoapReceiver
  {
    public string msg = String.Empty;
    public Form1 frm = null;

    //We Override the Receive function and parse the Soapmessage through its

    //SoapEnvelope class


    protected override void Receive(SoapEnvelope envelope)
    {
      msg = envelope.Body.OuterXml;
      frm.label1.Text = msg;
    }
  }

  //We could have also done the same thing using a class

  //inheriting from SoapClient such as this


  public class MyHttpClient : SoapClient
  {
    public string msg = String.Empty;
    public Form1 frm = null;

    //For SoapClient Classes we must initialize the base class

    // with an intializer passing in the EndpointReference that

    //points to this applications listening port


    public MyHttpClient(EndpointReference dest) : base(dest)
    { }

    //Utilizes the SoapMethodAttribute 

    //to set the SoapAction of a SoapMessage


    [SoapMethod("MyRequest")]
    public SoapEnvelope MyRequest(SoapEnvelope envelope)
    {
      SoapEnvelope response = 
         base.SendRequestResponse("MyRequest",envelope);
      msg = response.Body.OuterXml;

      frm.label1.Text = msg;
      return response;
    }
  }
}

See comments for explanation of client side code.

Disadvantages and Quirks

This practice is not recommended for public major web sites, only really for internal use or quick solutions to get something done. So why is this solution not a production recommended practice? Why is it really only to be used for internal communication patterns? When Web Services hit the industry, one of its major success patterns was its design for interoperability. This means the ability to send a SOAP message from a ASP.NET Microsoft Web Service to a BEA system's Web Logic web service which in turn could go through a IBM�s Web Sphere system, only to be accessed by a Java Web Service Client application and responded back to a ASP.NET web service. In its concept, it worked well. However, in its practicality, it didn�t work until you tweaked the SOAP format for each system you were sending it through. Now with WSE, this is not a problem. As long as the other systems also adhere to the community standard, interoperability is seamless. The piece that allows this interoperability is contracts, sometimes referred to as Service Level Agreements (SLA). These are basically contracts defined by business processes that define what�s going to be inside of each SOAP message going through disparate systems. These contracts are implemented through XML Schemas dealing with web services, also referred to as Web Service Description Language (WSDL) files. The WSDL defines the SOAP format and the SOAP message body format. It basically tells you what can and can not be inside of a SOAP message.

Well, nowhere in these exercises did I mention anything about WSDL files until now. Thus, if you want to use these examples as a B2B process, you would have to define WSDL files to fit the interoperability standard. Now, this is not a problem, because it's just an XML schema file. However, there are not a lot of "Don Box's" out in the computer industry who know every facet of a WSDL file. It is not recommended you design one from scratch; there are too many things that can go wrong. This is the main reason for not using this pattern with a B2B Web Service e-commerce application. Another downer is that using this method, the ASP.NET engine can not generate a WSDL file for your application like it can for the "ASMX" technology. Thus, you can�t even create a client application using the "Add Web Reference" wizard in VS.NET. One last note, the Web Reference wizard and the WSDL.exe utility won�t work here as mentioned earlier, nor will the SOAP Encapsulation of the WSDL generated proxy classes shield you from the raw details of a SOAP message when using this method. So if you are afraid of getting to low level, this may not be a good design pattern for you after all.

Happy coding!

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here