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 HttpHandler
s 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
{
Class1.
public class TimeStampReq : IHttpHandler , IRequiresSessionState
{
public TimeStampReq(){}
public void ProcessRequest(HttpContext ctxt)
{
}
public bool IsReusable
{
get
{
return true;
}
}
}
}
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="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);
}
public override void ProcessMessage(SoapEnvelope message)
{
base.ProcessMessage (message);
}
}
}
Now, let�s register it. Here�s the configuration file:
="1.0" ="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
{
public class Form1 : System.Windows.Forms.Form
{
internal System.Windows.Forms.Label label1;
private System.Windows.Forms.Button button1;
private System.ComponentModel.Container components = null;
public Form1()
{
InitializeComponent();
}
protected override void Dispose( bool disposing )
{
if( disposing )
{
if (components != null)
{
components.Dispose();
}
}
base.Dispose( disposing );
}
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);
}
[STAThread]
static void Main()
{
Application.Run(new Form1());
}
private void button1_Click(object sender, System.EventArgs e)
{
Uri uriMe = new Uri("soap.tcp://" + System.Net.Dns.GetHostName() +
":3119/MyServiceReceive");
EndpointReference epMe = new EndpointReference(uriMe);
ReceiveData receive = new ReceiveData();
receive.frm = this;
SoapReceivers.Add(epMe, receive);
SoapSender sSend = new SoapSender();
SoapEnvelope env = new SoapEnvelope ();
env.CreateBody();
env.Context.Addressing.ReplyTo = new ReplyTo(uriMe);
Uri uriTo =new Uri("http://"+ System.Net.Dns.GetHostName() +
"/MyAsyncWebSvc/MyService.ashx");
EndpointReference epTo = new EndpointReference(uriTo);
env.SetBodyObject("<data>dosomething</data>");
env.Context.Addressing.To = new To(epTo.Address );
env.Context.Addressing.Action = new Action("MyRequest");
sSend.Destination = uriTo;
sSend.Send(env);
this.label1.Text = "Message Sent";
}
}
public class ReceiveData : SoapReceiver
{
public string msg = String.Empty;
public Form1 frm = null;
protected override void Receive(SoapEnvelope envelope)
{
msg = envelope.Body.OuterXml;
frm.label1.Text = msg;
}
}
public class MyHttpClient : SoapClient
{
public string msg = String.Empty;
public Form1 frm = null;
public MyHttpClient(EndpointReference dest) : base(dest)
{ }
[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!