使用 WSE v2.0 和 ASP.NET SimpleHandlerFactory 创建异步 Web 服务






4.65/5 (7投票s)
2004年12月23日
12分钟阅读

67810

762
本文介绍了使用 ASP.NET 和 WSE v2.0 实现异步 Web 服务的另一种方法。
引言
本文介绍了如何使用 Web Service Enhancements 版本 2.0 和 ASP.NET 内置的 Handler 来创建自定义的单向异步 Web 服务。此练习的主要目的是演示一种快速简便的方法来实现一个模仿“远程过程调用”的单向 Web 服务和一个普通的双向 Web 服务。这绝不是推荐用于企业对企业 (B2B) 电子商务应用程序的生产 Web 服务实现的最佳方式,本文将解释原因。
概述
随着面向服务架构 (SOA) 成为软件设计模式的未来范例,有必要讨论 ASP.NET 1.1 和 WSE v2.0 目前能提供什么,以便为未来的 Indigo 和其他 SOA 系统浪潮做好准备。让我们从解释 WSE 是什么开始,然后进入 ASP.NET 及其技术领域,特别是 HTTP Handlers。之后,我们将深入探讨 WSE 的细节以及如何实现 Web 服务。
WSE v2.0 简介
WSE 代表 Web Service Enhancements。为了保持简介的快速和简单,它是一个可以下载并安装到当前 .NET Framework 版本中的 .NET 共享组件。目前,它是其正式发布的 2.0 版本,下一个版本可能很快就会发布。在后台,该组件通过内部的许多不同命名空间和类来实现,这些命名空间和类允许开发人员添加行业安全标准;创建行业 SOAP 消息格式,以便在 Java Web 服务、IBM Web 服务和 BEA Web 服务等不同系统之间更容易地进行互操作;并应用通常称为 WS-*(WS-Security, WS-I, WS-Addressing, WS-Messaging 等)的一组规范的社区标准。WSE 是一个 SOAP 扩展,它在客户端和服务器端(如果两者都实现了)操纵 SOAP 消息。
ASP.NET v1.1 HTTP Handler 简介
ASP.NET 是 Microsoft 对 ASP 的继任者,它是一种使用 Microsoft .NET 为 Web 浏览器(如 Internet Explorer、FireFox 和 Netscape)动态生成网页的方式。随着动态网页概念的出现,Web 服务应运而生。Web 服务是一种可以通过 HTTP 模型访问的组件,用于向其发送数据和命令,通常称为消息。如果你抽象 Web 服务,它会把你带入面向服务架构 (SOA) 的世界。ASP.NET Web 服务是实现 SOA 中服务的一种方式。为了切合主题,ASP.NET Web 服务通常使用 XML 作为发送和接收消息的格式。具体来说,它使用 SOAP XML 格式。ASP.NET 使开发人员可以轻松地使用这一概念来创建 Web 服务。它拥有一项称为“ASMX”的技术,该技术允许自动解析和生成这些 SOAP 消息以访问远程组件。ASP.NET 通过使用 HTTP Handlers 和 SOAP 扩展(参见 machine.config 代码片段)来实现此和其他类似技术。
ASP.NET HTTP Handler 相当于 IIS Filter 所能做的事情(对于那些 C++ 开发者来说)。HTTP Handler 的任务是处理进出 ASP.NET 的 HTTP 请求和 HTTP 响应。它可以修改响应/请求,通过添加、删除或完全创建一个新的数据对象。HTTP Handler 通常通过 HTTP 动词 (GET, POST, PUT 等)、路径 (*-all, *.asmx - 以 .asmx 结尾的文件等) 以及包含要执行的代码以修改或解析响应/请求的组件 (.NET 程序集) 来注册到 ASP.NET。此设置通过应用程序的配置文件来设置。对于更健壮的自定义实现,ASP.NET HTTP Handler 可能还需要注册到 IIS,请参阅 KingLion 的“在运行时为网站图像添加水印”。
ASP.NET SimpleHandlerFactory
ASP.NET 已经自带了一些 HTTP Handlers 和 HTTP Handler Factories。HttpHandlerFactory
是一个类,其工作是根据某些逻辑编码标准来创建 Handler 实例。要创建 HttpHandlerFactory
,请创建一个实现 IHttpHandlerFactory
的类。这个类有两个方法需要实现。
IHttpHandler GetHandler(HttpContext currentcontext, string reqType,
string url, string pathTranslated);
void ReleaseHandler(System.Web.IHttpHandler handlerToRelease);
这些工厂的注册方式与 HttpHandler
的注册方式相同,因此我不会花太多时间介绍。我们有处理安全性的 HttpHandler
,例如 FormsAuthenticationHandler
。我们也有一些处理限制对特定文件类型访问的 Handler,例如 HttpForbiddenHandler
。还有最重要的 PageHandler
,它处理 ASP.NET Web Form 页面;以及处理 .asmx 页面的 WebServiceHandler
(ASP.NET Web 服务)。还有一个 SimpleHandlerFactory
,它被映射/注册到 .ashx 文件扩展名。我们还可以创建自己的自定义 HTTP Handler 并将其映射到自己的扩展名。(参见 machine.config 代码片段)
如何创建 SimpleHandlerFactory 可以使用的 HttpHandler
SimpleHandlerFactory
允许开发人员像创建 ASP.NET 页面一样创建 HTTP Handler,请查看下面的示例。
在 VS.NET 中,创建一个类库项目,添加对 System.Web.Dll 的引用,创建一个类,例如: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
}
}
}
}
接下来的部分是可选的,如果你愿意,可以创建一个 Page.ashx 文件。但这不是必需的。
Page1.ashx:
<%@ WebHandler Language="”c#”" class=”TimeStampRequests.TimeStampReq” %>
你也可以使用代码隐藏,或者像 ASP.NET Web Form 页面一样将源代码包含在同一个文件中。
接下来的部分是在 Web.config 文件中注册 Simple Handler,如下所示:
<system.web>
<httpHandlers>
<add verb=”*” path=”*.ashx”
type=”TimeStampRequests.TimeStampReq, TimeStampRequest”/>
</httpHandlers>
</system.web>
代码解释如下:创建一个类并实现 IHttpHandler
接口。这个接口有一个必须实现的方法和一个属性。该方法称为 ProcessMessage
。这是其签名:
void ProcessMessage(HttpContext context)
当 ASP.NET 工作进程将请求或响应移交给处理时,就会调用此方法。它将当前的 Web 环境数据以 HttpContext
对象的形式传递给此方法。此 HttpContext
对象可用于读取服务器变量、Session 数据、响应特定数据、请求数据以及应用程序对象池中的数据。
下一个实现是名为 IsReusable
的属性,其签名如下:
bool IsReusable { get; }
此属性决定是否要在请求之间维护状态。一旦实现了此方法和属性,HttpHandlerFactory
就可以创建 Handler 的实例,并让 ASP.NET 工作进程调用它来处理请求或响应。
有一个 Microsoft Webcast(www.microsoft.com/webcasts,www.dasblonde.net)以及 CodeProject 的一篇文章“在运行时为网站图像添加水印”(作者:KingLeon),其中使用了 HTTP Handlers 来创建图像水印。这是利用你自己的自定义 Handler 和 SimpleHandlerFactory
的基本蓝图。还有其他更复杂的创建自定义 Handler 的方法,但步骤大同小异。内置的 SimpleHandlerFactory
将在每次向 ASP.NET 虚拟目录应用程序发出 .ashx 页面的请求时创建你的 Handler 实例。然后你的代码将执行其 ProcessRequest
方法并运行。
Machine.config 文件、HttpHandlers 和 HttpHandlerFactories
这是 .NET 1.1 的 Machine.config 文件的一个代码片段,再次显示了一些可用的 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"/>
深入 WSE 和 HttpHandlers
现在,关于 HTTP Handler 的内容就到这里,那么 WSE 和创建异步 Web 服务呢?
WSE v2.0 包含一些类,允许基于 WS-Addressing、WS-Messaging 和其他 WS-* 规范(请参阅 www.w3c.org、www.ws-i.org)发送标准化的 SOAP 消息。特别是,我们将关注 WSE 的通信实现,即 SoapSenders
/SoapReceivers
和 SoapClients
/SoapServices
。
这些类中最有趣的部分是 SoapService
类继承自 SoapReceiver
类。SoapReceiver
类实现了 IHttpHandler
接口。这意味着,如果你创建一个继承自 SoapReceiver
或 SoapService
类的类,它就可以与 SimpleHandlerFactory
一起使用,让你处理请求或响应。如果请求/响应在其正文部分有一个 SOAP 消息,那么你就有效地实现了一个 Web 服务,而这正是 WebServiceHandler
的工作方式。
让我们看看是否能实现这个目标,下面是代码:
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);
}
}
}
现在,让我们注册它。这是配置文件:
<?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>
一旦我们将 <system.web>
部分添加到我们的 web.config 文件中,我们的 Web 服务就可以通过 .ashx 文件扩展名进行访问。
代码解释
让我们来谈谈代码。我们首先创建一个名为 AsyncWebSvc
的类。我们让这个类继承自 SoapReceiver
类。SoapReceiver
类已经实现了 ProcessMessage
,所以我们不必担心它,除非我们想这样做。换句话说,我们可以覆盖基类的实现并在其中添加我们自己的代码,如代码所示。SoapReceiver
类位于 Microsoft.Web.Services2.Messaging
命名空间中,它执行异步 SOAP 解析。这个类解析 HttpRequest
的正文部分,查找 SOAP 消息。找到后,它会调用 Receive
方法,并将 SOAP 消息的 XML 格式传递进来,以便开发人员进行操作和解析以执行某些操作。这就足够了。目前,如果我们在这里停止,并且不需要发送响应回去,这就是我们所说的单向异步 Web 服务。
如果我们想发送响应回去,WSE 使用 SoapSender
和 SoapClient
类来发送 SOAP 消息。要使用 WSE 发送消息,我们必须首先创建要发送的消息。使用 WSE,我们创建一个 SoapEnvelope
类,它代表要发送的 SOAP 消息。这个 SoapEnvelope
类为开发人员提供了对原始 SOAP XML 格式的编程控制,可以精确地控制要包装在消息正文中的数据。WSE 负责格式化,使其消息符合 SOAP XML WS-* 社区标准。代码展示了一种创建 SoapEnvelope
并使用 SoapSender
类实例发送它的方法。唯一需要解释的另一部分是:我们要将消息发送到哪里?
用于发送和接收 SOAP 消息的客户端应用程序
对于标准的异步双向 Web 服务,SoapSender
类实例是通过传递 Uri
地址来创建的(基本上,这是一个非特定于 HTTP 协议的 URL 地址,本质上更通用)。在 WSE 2.0 中,Uri
地址被封装在一个名为 EndpointReference
的类中。之所以这样做,是因为 WS-Addressing 和 WS-Messaging 标准。客户端应用程序,无论是另一个 Web 服务、ASP.NET 应用程序还是 Windows 窗体应用程序,都可以创建一个侦听端口来侦听 SOAP 响应。这是 Windows 窗体客户端应用程序的代码,它为自己创建了一个 EndpointReference
,然后使用 WSE 2.0 调用此 Web 服务并异步获得响应。
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;
}
}
}
客户端代码的解释请参见注释。
缺点和注意事项
这种做法不建议用于主要的公共网站,仅适用于内部使用或快速解决问题。那么为什么这种解决方案不被推荐用于生产环境呢?为什么它真的只适用于内部通信模式?当 Web 服务进入行业时,其主要的成功模式之一是其互操作性设计。这意味着,能够将 SOAP 消息从 ASP.NET Microsoft Web 服务发送到 BEA 系统的 Web Logic Web 服务,然后通过 IBM 的 Web Sphere 系统,最终由 Java Web 服务客户端应用程序访问并响应回 ASP.NET Web 服务。从概念上讲,它工作得很好。然而,在实践中,直到你为每个要通过的系统调整 SOAP 格式,它才真正起作用。现在有了 WSE,这不再是一个问题。只要其他系统也遵循社区标准,互操作性就是无缝的。允许这种互操作性的部分是合同,有时称为服务级别协议 (SLA)。这些合同通常由业务流程定义,规定在不同系统之间传输的每个 SOAP 消息中包含什么内容。这些合同通过处理 Web 服务的 XML Schema 实现,也称为 Web 服务描述语言 (WSDL) 文件。WSDL 定义了 SOAP 格式和 SOAP 消息正文格式。它基本上告诉你 SOAP 消息中可以包含什么,不可以包含什么。
那么,在这些练习中,我直到现在都没有提到任何关于 WSDL 文件的事情。因此,如果你想将这些示例用作 B2B 流程,你就需要定义符合互操作性标准的 WSDL 文件。现在,这不是一个问题,因为它只是一个 XML Schema 文件。然而,在计算机行业中,并非有很多人了解 WSDL 文件的每一个方面。不建议你从头开始设计一个,因为可能会出错的事情太多了。这是不推荐将此模式用于 B2B Web 服务电子商务应用程序的主要原因。另一个缺点是,使用此方法,ASP.NET 引擎无法像对“ASMX”技术那样为你的应用程序生成 WSDL 文件。因此,你甚至无法使用 VS.NET 中的“添加 Web 引用”向导来创建客户端应用程序。最后一点,如前所述,Web 引用向导和 WSDL.exe 工具在这里将不起作用,并且在使用此方法时,WSDL 生成的代理类的 SOAP 封装也不会让你免受 SOAP 消息原始细节的影响。所以,如果你害怕深入底层细节,这可能并不是一个好的设计模式。
祝您编码愉快!