65.9K
CodeProject 正在变化。 阅读更多。
Home

基于程序集的 Web 服务和使用反射的 JavaScript 代理

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.88/5 (16投票s)

2007年5月30日

CPOL

6分钟阅读

viewsIcon

70635

downloadIcon

341

ASP.NET 和 Ajax Web 服务,不是来自 .asmx 文件,而是来自使用一点反射的已编译程序集

引言

任何构建 .NET 应用程序框架、自定义控件或只是喜欢分离关注点的人,都会遇到我曾经遇到的同样问题。我希望我能帮助你们中的一些人,而无需像我一样费力,但让我先解释一下这个问题。这是一个相当长的故事,请耐心等待;本文的水平涵盖了许多不同领域的初级到高级。

问题所在

当您想开发具有 Javascript 回调功能的自定义控件时,可以使用 ICallBack 和 IScriptable 接口。这些接口通过使用 Javascript 实现从您的类进行调用以及向您的类进行返回调用,而无需实现更新面板或页面刷新。然而,ICallback 接口有一个问题:它没有很好的 JSON(反)序列化。它限制每个组件只能有一个函数,尽管实现更多是可能的,但它不是很易于管理。

应该如何

如果您曾经在 ASP.NET 中使用过 Web 服务,您就会看到这种机制的强大功能。当与 ASP.NET Ajax 1.0 结合使用时尤其如此,因为它会为您生成 Javascript 代理。对于那些没有使用过 ASP.NET Web 服务的人,这里有一个简短的回顾:您在项目中创建一个 ASMX 文件,用一个包含几个 Web 服务属性的类填充它,就完成了!无需创建 SOAP,无需 JSON(反)序列化,无需编写代理。您唯一需要做的就是将其注册到您的 <ScriptManager> 中,以便它生成 Javascript 代理。将它放在我们的自定义控件库中是不是很棒?

又一个问题

这种方法的最大问题是,此功能仅适用于 Web 目录中尚未编译的 ASMX 文件。您没有“开箱即用”的方法可以将此功能指向已编译的程序集。为了解决这个问题,我们首先看看当前功能是如何工作的。

默认情况下它如何工作

ASMX 文件通过 HTTPHandler 转换为 Web 服务和 Javascript 代理。此 HTTPHandler 设置在 config.xml 文件中。

<httpHandlers>
    <remove verb="*" path="*.asmx"/>
    <add verb="*" path="*.asmx" validate="false" 
        type="System.Web.Script.Services.ScriptHandlerFactory"/>
</httpHandlers>

您在这里看到的处理程序是在新的“支持 ASP.NET Ajax 的 Web 应用程序”中默认创建的。当请求扩展名为 ASMX 的文件(“*.asmx”)时,它会将该请求转发到 ScriptHandlerFactory。然后,脚本处理程序工厂会为您创建 Web 服务、Javascript 代理或 Javascript 方法处理程序。当您将 Web 服务添加到 ScriptManager 时,它会在您的网页上生成一个 <script src="Webserver.asmx/js"/> 。此类的问题在于它会检查 ASMX 文件,仅此而已。此类有一个更大的问题,即几乎所有内容都是 InternalPrivate。如果您不知道这意味着什么,它意味着扩展或更改任何内容都是不可能的。

反射它

好的,也许并非不可能。一切都编译成托管代码,借助反射,我们可以进入我们喜欢的每一个小角落。即使是私有构造函数也可以通过 .NET 框架中最强大的组件之一访问。关于反射的教程有很多,所以本文我将假设您了解基础知识。

开始解决方案

我们从一个新的空“支持 ASP.NET AJAX 的 Web 应用程序”开始,它附带 ASP.NET Ajax 1.0。然后我们添加第二个空的 C# 类库项目作为第二个项目,并在第一个项目中引用第二个项目。

AutoDiscoveryWebServiceHandlerFactory

为了实现我们自己的代码,我们将在 CustomControl 库中创建我们自己的 HttpHandlerFactory。我们将其命名为 AutoDiscoveryWebServiceHandlerFactory,实现接口 IHttpHandlerFactory。

public IHttpHandler GetHandler(HttpContext context, 
    string requestType, string url, string pathTranslated)

public void ReleaseHandler

然后在 Web 应用程序中,我们修改 config.xml 文件以将 ASMX 请求转发到我们自己的处理程序工厂。

<httpHandlers>
    <remove verb="*" path="*.asmx"/>
    <add verb="*" path="*.asmx" validate="false" 
        type="Devusion. Services.AutoDiscoveryWebServiceHandlerFactory"/>
</httpHandlers>

自定义 Web 服务

接下来我们创建我们自己的 Web 服务,而不是普通的 ASMX 文件,而是一个普通的 C# 类文件。

namespace Devusion.CustomControls
{
    [ScriptService]
    [WebService(Namespace = "http://www.devusion.nl/xmlns/customcontrols")]
    public class Service : WebService
    {
        [WebMethod]
        [ScriptMethod(UseHttpGet = true)]
        public string HelloWorldYou(string name)
        {
            return DateTime.Now.ToLongTimeString()+ ": Hello " + name;
        }
    }
}

WebServiceHandlerFactory

现在让我们编写代码,使调用 /Devusion.CustomControls.Service.asmx 成为可能。这将调用我们的 AutoDiscoveryWebServiceHandlerFactory.GetHandler 函数。在这里,我们必须将 URL 转换为类的类型。我们通过遍历已加载的程序集并检查 Web 服务属性来完成此操作。这应该进行优化以获得更好的性能。如果我们找不到匹配的类型,我们将返回 null。

private Type GetServiceType(string TypeName)
{
    // TODO: Caching mechanism for assembly checks
    foreach (
        Assembly loadedAssembly in AppDomain.CurrentDomain.GetAssemblies())
    {
        Type ClassType = loadedAssembly.GetType(TypeName);
        if (ClassType != null)
            return ClassType;
    }
    return null;
}

在 Gethandler 中,我们编写

// Try to get the type associated with the request (On a name to type basis)
Type WebServiceType = 
    this.GetServiceType(Path.GetFileNameWithoutExtension(pathTranslated));

// if we did not find any send it on to the 
// original ajax script service handler.
if (WebServiceType == null)
{
    // [REFLECTION] Get the internal class 
    // System.Web.Script.Services.ScriptHandlerFactory create it
    IHttpHandlerFactory ScriptHandlerFactory = 
        (IHttpHandlerFactory)System.Activator.CreateInstance(
        AjaxAssembly.GetType(
        "System.Web.Script.Services.ScriptHandlerFactory"));
    UsedHandlerFactory = ScriptHandlerFactory;
    return ScriptHandlerFactory.GetHandler(
        context, requestType, url, pathTranslated);
}

这里的代码将获取 WebServiceType。如果未找到类型,它将使用反射将请求转发到默认的 Ajax Framework ScriptHandlerFactory,因为这是一个内部类。几乎所有通常处理 Web 服务和 Javascript 代理的 ScriptHandlerFactory 功能都是私有的或内部的。请注意,您从现在开始将看到的大部分功能都是使用 Reflector.net 工具找到的。Web 服务处理程序可以通过 3 种不同的请求访问

  1. 正常 Web 服务方法请求 (/Devusion.CustomControls.Service.asmx?......)
  2. Javascript 代理生成请求 (/Devusion.CustomControls.Service.asmx/js 或 /jsdebug)
  3. Javascript 方法调用 (/Devusion.CustomControls.Service.asmx/method)

首先,我们检查是普通请求还是 Javascript 请求

// [REFLECTION] get the Handlerfactory : RestHandlerFactory
// (Handles Javascript proxy Generation and actions)
IHttpHandlerFactory JavascriptHandlerFactory = 
    (IHttpHandlerFactory)System.Activator.CreateInstance(
    AjaxAssembly.GetType("System.Web.Script.Services.RestHandlerFactory"));

// [REFLECTION] Check if the current request is a Javasacript request
// JavascriptHandlerfactory.IsRestRequest(context);
System.Reflection.MethodInfo IsScriptRequestMethod = 
    JavascriptHandlerFactory.GetType().GetMethod("IsRestRequest", 
    BindingFlags.Static | BindingFlags.NonPublic);
if ((bool)IsScriptRequestMethod.Invoke(null, new object[] { context }))
{
    // Javascript Request
}
else
{
    // Normal Request
}

然后对于 Javascript 请求,我们检查是代理请求还是方法请求

// Check and see if it is a Javascript Request or a request for a 
// Javascript Proxy.
bool IsJavascriptDebug = 
    string.Equals(context.Request.PathInfo, "/jsdebug", 
    StringComparison.OrdinalIgnoreCase);
bool IsJavascript = string.Equals(context.Request.PathInfo, "/js", 
    StringComparison.OrdinalIgnoreCase);
if (IsJavascript || IsJavascriptDebug)
{
    // Proxy Request
}
else
{
    // Method Request
}

现在来看这 3 种请求类型中的操作。

JavaScript 代理请求

在这里我们做了更多的反射;每个反射都在注释中进行了解释。我们使用 WebServiceType 创建一个 WebServiceData,它将包含创建 Web 服务所需的所有信息。我们直接在 GetClientProxyScript 方法中使用此对象,该方法是 WebServiceClientProxyGenerator 对象的一个方法。然后它返回 Javascript 代理。

// [REFLECTION] fetch the constructor for the WebServiceData Object
ConstructorInfo WebServiceDataConstructor = AjaxAssembly.GetType(
    "System.Web.Script.Services.WebServiceData").GetConstructor(
    BindingFlags.NonPublic | BindingFlags.Instance, null, 
    new Type[] { typeof(Type), typeof(bool) } , null);

// [REFLECTION] fetch the constructor for the WebServiceClientProxyGenerator
ConstructorInfo WebServiceClientProxyGeneratorConstructor = 
    AjaxAssembly.GetType(
    "System.Web.Script.Services.WebServiceClientProxyGenerator").GetConstructor(
    BindingFlags.NonPublic | BindingFlags.Instance, null, 
    new Type[] { typeof(string), typeof(bool) }, null);

// [REFLECTION] get the method from WebServiceClientProxy to 
// create the Javascript : GetClientProxyScript
MethodInfo GetClientProxyScriptMethod = 
    AjaxAssembly.GetType(
    "System.Web.Script.Services.ClientProxyGenerator").GetMethod(
    "GetClientProxyScript", BindingFlags.NonPublic | BindingFlags.Instance, 
    null, new Type[] { AjaxAssembly.GetType(
    "System.Web.Script.Services.WebServiceData") }, null);

// [REFLECTION] We invoke : 
// new WebServiceClientProxyGenerator(url,false).GetClientProxyScript(
new WebServiceData(WebServiceType));
string Javascript = (string)GetClientProxyScriptMethod.Invoke(
    WebServiceClientProxyGeneratorConstructor.Invoke(
    new Object[] { url, IsJavascriptDebug }), new Object[] 
    {
        WebServiceDataConstructor.Invoke(
            new object[] { WebServiceType, false })
    }
);

然后进行一点缓存,这几乎是直接从原始单元使用 Reflector 获取的。

// Check the assembly modified time and use it as caching http header
DateTime AssemblyModifiedDate = GetAssemblyModifiedTime(
    WebServiceType.Assembly);

// See "if Modified since" was requested in the http headers, 
// and check it with the assembly modified time
string s = context.Request.Headers["If-Modified-Since"];

DateTime TempDate;
if (((s != null) && DateTime.TryParse(s, out TempDate)) && (
    TempDate >= AssemblyModifiedDate))
{
    context.Response.StatusCode = 0x130;
    return null;
}

// Add HttpCaching data to the http headers
if (!IsJavascriptDebug && (
    AssemblyModifiedDate.ToUniversalTime() < DateTime.UtcNow))
{
    HttpCachePolicy cache = context.Response.Cache;
    cache.SetCacheability(HttpCacheability.Public);
    cache.SetLastModified(AssemblyModifiedDate);
}

然后我们需要通过 HTTPHandler 将 Javascript 代理字符串返回给客户端。

internal class JavascriptProxyHandler : IHttpHandler
{
    string Javascript = "";

    public JavascriptProxyHandler(string _Javascript)
    {
        Javascript = _Javascript;
    }
    bool IHttpHandler.IsReusable { get { return false; } }
    void IHttpHandler.ProcessRequest(HttpContext context)
    {
        context.Response.ContentType = "application/x-Javascript";
        context.Response.Write(this.Javascript);
    }
}

然后在 GetHandler 代码中创建并返回它

HttpHandler = new JavascriptProxyHandler(Javascript);
return HttpHandler;

Javascript 代理就这些了。我们来看看另外两个更简单的。

JavaScript 方法请求

当您使用 Javascript 代理中生成的方法时,它将通过 /methodname 访问 Web 服务。此处理程序与普通 Web 服务处理程序 (SOAP/XML) 不同的原因是,此接口需要在 Javascript JSON 和我们的 C# 方法之间进行转换。

IHttpHandler JavascriptHandler = 
    (IHttpHandler)System.Activator.CreateInstance(
    AjaxAssembly.GetType("System.Web.Script.Services.RestHandler"));

// [REFLECTION] fetch the constructor for the WebServiceData Object
ConstructorInfo WebServiceDataConstructor = AjaxAssembly.GetType(
    "System.Web.Script.Services.WebServiceData").GetConstructor(
    BindingFlags.NonPublic | BindingFlags.Instance, null, 
    new Type[] { typeof(Type), typeof(bool) } , null);

// [REFLECTION] get method : JavaScriptHandler.CreateHandler
MethodInfo CreateHandlerMethod = JavascriptHandler.GetType().GetMethod(
    "CreateHandler", BindingFlags.NonPublic | BindingFlags.Static, null, 
    new Type[] { AjaxAssembly.GetType(
    "System.Web.Script.Services.WebServiceData"), typeof(string) }, null);

// [REFLECTION] Invoke CreateHandlerMethod :
// HttpHandler = JavaScriptHandler.CreateHandler(WebServiceType,false);
HttpHandler = (IHttpHandler)CreateHandlerMethod.Invoke(
    JavascriptHandler, new Object[]
    {
        WebServiceDataConstructor.Invoke(
        new object[] { WebServiceType, false }), 
        context.Request.PathInfo.Substring(1)
    }
);
return HttpHandler;

我们创建 JavascriptHandler,并且像上一个处理程序一样,创建 WebServiceData。然后我们调用 CreateHandler,将 WebServiceData 作为参数,它返回正确的 HTTPHandler。

普通 Web 服务请求

普通请求更简单。该类是公共的,只有接收 WebServiceData 而不是文件路径的函数是私有的。我们使用此函数获取另一个 HttpHandler 并返回它。

// Remember the used factory for later in ReleaseHandler
IHttpHandlerFactory WebServiceHandlerFactory = new WebServiceHandlerFactory();
UsedHandlerFactory = WebServiceHandlerFactory;

// [REFLECTION] Get the method CoreGetHandler
MethodInfo CoreGetHandlerMethod = UsedHandlerFactory.GetType().GetMethod(
    "CoreGetHandler", BindingFlags.NonPublic | BindingFlags.Instance);

// [REFLECTION] Invoke the method CoreGetHandler :
// WebServiceHandlerFactory.CoreGetHandler(WebServiceType,context,
    context.Request, context.Response);
HttpHandler = (IHttpHandler)CoreGetHandlerMethod.Invoke(UsedHandlerFactory, 
    new object[]
{
    WebServiceType, context, context.Request, context.Response
}
);
return HttpHandler;

Default.aspx

现在来看看这一切的实际效果。首先,我们向 ScriptManager 添加一个 Web 服务引用

<asp:ScriptManager ID="ScriptManager1" runat="server" >
    <Services>
        <asp:ServiceReference Path="Devusion.CustomControls.asmx" />
    </Services>
</asp:ScriptManager>

在它下面,我们放置

<script type="text/Javascript">
    function RunHelloWorld(name)
    {
        name = prompt("Please enter your name :","John Doe");
        Devusion.CustomControls.Service.HelloWorldYou(name,
            HelloWorldDone,WebServiceCallFailed,'');
    }
    
    function HelloWorldDone(result,context,senderFunction)
    {
        alert(result);
    }
    
    function WebServiceCallFailed(error,context,senderFunction)
    {
        alert(error.get_message());
    }
</script>
<input type="button" onclick="RunHelloWorld();" value="Click Here" />

您现在可以在 service.cs 文件中 WebService 类中的 HelloWorldYou 方法中设置断点。只需运行页面,让反射的魔力完成其余的工作!在此示例中,我没有实现实际的自定义控件或所有 Javascript 与 Silverlight 结合的首选 script# 实现。我将在未来的文章中详细介绍。

演示代码

附件中的 ZIP 是一个完整的演示解决方案。它应该可以直接打开和构建,所以如果出现任何问题请告诉我。代码是使用 Visual Studio 2005 Professional 和 ASP.NET Ajax 1.0 创建的。

历史

  • 2007 年 5 月 30 日 - 发布原始版本
© . All rights reserved.