深入Atlas系列:Web Sevices Access in Atlas(2) - 服务器端支持(上)

  在上一片文章里,我们分析讨论了使用Atlas在进行AJAX访问Web Services所用的客户端代码。但是如果要实现这一功能,很显然还离不开服务器端的支持。在这篇文章里,我们就来讨论这一点。

   增加服务器端的支持其实就是添加/改变处理一个HTTP Request的方式。在ASP.NET中,是通过一个实现了System.Web.IHttpHandler接口的类来处理Request。我们可以在 Web.config里通过配置将Request与实现IHttpHandler的类进行映射,以此告诉ASP.NET这个Request该由谁来处理。 例如,在Atlas中,对于Culture的支持文件atlasglob.axd,就把该文件请求交由 Microsoft.Web.Globalization.GlobalizationHandler类来处理。
< httpHandlers >
    
< add  verb ="*"  path ="atlasglob.axd"  type ="Microsoft.Web.Globalization.GlobalizationHandler"  validate ="false" />
</ httpHandlers >

  但是如果需要对于一个请求,使用不同的IHttpHandler来处理呢?甚者,如果需要对于已有一个请求的处理方式进行扩展呢? ASP.NET也考虑到了这一点,只需要将一个请求交给一个实现了System.Web.IHttpHandlerFactory接口的类即可。该类的功 能就是根据该Request的一些“特点”,创建一个IHttpHandler实例。该类也提供了释放Hanlder的方法,提供了对于Handler实 例复用的可能,减少由于构造和初始化对象的消耗,自然也减轻了GC的负担。

  在Atlas中就利用了这一点,改变了对于*.asmx请 求的处理方式,对于在Query String中有mn的请求需要作特别的处理(在以后的文章中我会提到,对于“*.asmx/js”的请求,也会有另一种处理。它提供了客户端访问Web Services的代理,这超出了本篇文章的范围)。于是,如果需要使用Atlas从客户端以AJAX方式访问Web Services,则在Web.config里下面的设置绝对不可少:
< httpHandlers >
    
< remove  verb ="*"  path ="*.asmx" />
    
< add  verb ="*"  path ="*.asmx"  type ="Microsoft.Web.Services.ScriptHandlerFactory"  validate ="false" />
</ httpHandlers >

  这个设置删除了原有*.asmx文件请求的映射,将*.asmx文件的请求交由Microsoft.Web.Services.ScriptHandlerFactory处理。这就是Atlas在服务器端的支持。

   接下来就要开始分析Atlas提供的Microsoft.Web.Atlas.dll里的代码了。这个程序集里的代码量和复杂程度均大大超过Atlas 的客户端代码。因此,我只对于起关键作用的代码进行详细分析,一些辅助的方法或类的实现,只能请感兴趣的朋友们自行查看了。另外,为了大家阅读方便,我将 局部变量名都改成了可读性比较高的名称,避免了“text1”,“flag1”之类的变量名,希望对大家阅读代码有所帮助。

  我们先来看一下Microsoft.Web.Services.ScriptHandlerFactory类的成员:
ScriptHandlerFactory类成员
 1 public class ScriptHandlerFactory : IHttpHandlerFactory
 2 {
 3     // Methods
 4     public ScriptHandlerFactory();
 5     private static void CheckAtlasWebServicesEnabled();
 6     public virtual IHttpHandler GetHandler(HttpContext context, string requestType, string url, string pathTranslated);
 7     public virtual void ReleaseHandler(IHttpHandler handler);
 8 
 9     // Fields
10     private IHttpHandlerFactory _restHandlerFactory;
11     private IHttpHandlerFactory _webServiceHandlerFactory;
12 
13     // Nested Types
14     private class AsyncHandlerWrapper : ScriptHandlerFactory.HandlerWrapper, IHttpAsyncHandler, IHttpHandler
15     {
16         // Methods
17         internal AsyncHandlerWrapper(IHttpHandler originalHandler, IHttpHandlerFactory originalFactory);
18         public IAsyncResult BeginProcessRequest(HttpContext context, AsyncCallback cb, object extraData);
19         public void EndProcessRequest(IAsyncResult result);
20     }
21 
22     private class AsyncHandlerWrapperWithSession : ScriptHandlerFactory.AsyncHandlerWrapper, IRequiresSessionState
23     {
24         // Methods
25         internal AsyncHandlerWrapperWithSession(IHttpHandler originalHandler, IHttpHandlerFactory originalFactory);
26     }
27 
28     internal class HandlerWrapper : IHttpHandler
29     {
30         // Methods
31         internal HandlerWrapper(IHttpHandler originalHandler, IHttpHandlerFactory originalFactory);
32         public void ProcessRequest(HttpContext context);
33         internal void ReleaseHandler();
34 
35         // Properties
36         public bool IsReusable { get; }
37 
38         // Fields
39         private IHttpHandlerFactory _originalFactory;
40         protected IHttpHandler _originalHandler;
41     }
42 
43     internal class HandlerWrapperWithSession : ScriptHandlerFactory.HandlerWrapper, IRequiresSessionState
44     {
45         // Methods
46         internal HandlerWrapperWithSession(IHttpHandler originalHandler, IHttpHandlerFactory originalFactory);
47     }
48 }

  可以看到,除了IHttpHandlerFactory接口的方法外,类的内部还有着“丰富”地成员。 CheckAtlasWebServicesEnabled()静态方法是查看是否提供Atlas访问WebServices的服务器端支持,如果不支 持,则抛出异常。要让Atlas提供对于服务器端的支持,在Web.config里需要增加如下的元素:
< microsoft .web >
    
< webServices  enableBrowserAccess ="true"   />
</ microsoft.web >

  另外,在ScriptHandlerFactory类内部,有着数个内部类,它们提供了对于IHttpHandler对象的简单封装。在自己 的代码中使用这样的Wrapper类,是扩展一个现有框架时常用的方法。通过阅读Microsoft.Web.Atlas.dll的代码,可以发现在 Atlas中下至HttpRequest,上至Page,提供了大大小小十数个Wrapper类。

  我们从ScriptHandlerFactory的构造函数看起:
ScriptHandlerFactory构造函数
1 public ScriptHandlerFactory()
2 {
3     this._restHandlerFactory = new RestHandlerFactory();
4     this._webServiceHandlerFactory = new WebServiceHandlerFactory();
5 }

  构造函数相当简单,只是初始化了类的两个私有字段。ScriptHandlerFactory在工作时,会将产生和释放 IHttpHander对象的责任,根据一定逻辑委托给这两个IHttpHandlerFactory类的对象之一。 this._restHandlerFactory是Microsoft.Web.Services.RestHandlerFactory类的实例,负 责处理Atlas对于*.asmx请求的扩展。而this._webServiceHandlerFactory是 System.Web.Services.Protocols.WebServiceHandlerFactory类的实例,那么它又是什么呢?查看一个 文件就能知晓,这个文件就是“%WINDOWS%\Microsoft.NET\Framework\v2.0.50727\CONFIG\ web.cofig”,它提供了ASP.NET全局的默认配置。我们可以在里面发现这样的设置:
< httpHandlers >
    ……
    
< add  path ="*.asmx"  verb ="*"  type ="System.Web.Services.Protocols.WebServiceHandlerFactory, System.Web.Services, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"  validate ="False"   />
    ……
</ httpHandlers >

  可以发现,这就是ASP.NET原有处理*.asmx请求的类。Atlas的扩展要保证原有的功能不被破坏,因此使用了这个类对于扩展外的请求进行处理。

  接下来进入IHttpHandlerFactory的关键方法:GetHandler。代码如下:
GetHandler方法分析
 1 public virtual IHttpHandler GetHandler(HttpContext context, string requestType, string url, string pathTranslated)
 2 {
 3     IHttpHandlerFactory factory;
 4 
 5     // 判断是否是Atlas扩展请求
 6     if (RestHandlerFactory.IsRestRequest(context))
 7     {
 8         // 检测是否提供Atlas访问Web Services的支持
 9         ScriptHandlerFactory.CheckAtlasWebServicesEnabled();
10         // 委托给RestHandlerFactory进行处理
11         factory = this._restHandlerFactory;
12     }
13     else
14     {
15         // 既然不是Atlas扩展请求,则使用ASP.NET原有的方式进行处理
16         factory = this._webServiceHandlerFactory;
17     }
18     
19     // 调用Factory的GetHandler方法获得处理请求的Handler
20     IHttpHandler handler = factory.GetHandler(context, requestType, url, pathTranslated);
21 
22     // 下面的代码就是根据Handler是否支持Session,
23     // 以及是否是异步Handler,选择不同的Wrapper类
24     // 进行封装并返回。
25     bool requiresSession = handler is IRequiresSessionState;
26     if (handler is IHttpAsyncHandler)
27     {
28         if (requiresSession)
29         {
30             return new ScriptHandlerFactory.AsyncHandlerWrapperWithSession(handler, factory);
31         }
32         return new ScriptHandlerFactory.AsyncHandlerWrapper(handler, factory);
33     }
34     if (requiresSession)
35     {
36         return new ScriptHandlerFactory.HandlerWrapperWithSession(handler, factory);
37     }
38     return new ScriptHandlerFactory.HandlerWrapper(handler, factory);
39 }

  四个Wrapper类为ScriptHandlerFactory.HandlerWrapper及其子类。之所以分如此多的封装类,是为了 在进行统一封装的同时,保留ASP.NET的原有功能不变。有了统一的封装,ScriptHandlerFactory得ReleaseHandler也 能非常轻易的处理,只需将责任委托给Handler本身,被分装的Handler本身能够知道自己应该用什么Factory来释放自己。代码如下:
ReleaseHandler代码
1 public virtual void ReleaseHandler(IHttpHandler handler)
2 {
3     if (handler == null)
4     {
5         throw new ArgumentNullException("handler");
6     }
7     ((ScriptHandlerFactory.HandlerWrapper) handler).ReleaseHandler();
8 }

  接下来要关心的就是RestHandlerFactory类的GetHandler方法了,代码如下:
RestHanderFactory的GetHandler方法分析
 1 public virtual IHttpHandler GetHandler(HttpContext context, string requestType, string url, string pathTranslated)
 2 {
 3     // 如果是请求“*.asmx/js”,则说明是要求Web Services代理
 4     if (RestHandlerFactory.IsClientProxyRequest(context.Request.PathInfo))
 5     {
 6         // 那么返回处理代理的Handler
 7         return new RestClientProxyHandler();
 8     }
 9 
10     // 使用静态函数CreateHandler得到Handler。
11     return RestHandler.CreateHandler(context);
12 }

  对于Atlas请求Web Services代理的请求将在以后的文章中进行讨论,现在我们只关心RestHandler的静态方法CreateHandler(HttpContext)的行为。代码如下:
CreateHandler(HttpContext)方法分析
 1 internal static IHttpHandler CreateHandler(HttpContext context)
 2 {
 3     // 使用WebServiceData的静态方法GetWebServiceData(string)获得WebServiceData对象,
 4     // 它描述了即将使用的那个Web Services的信息。
 5     WebServiceData data = WebServiceData.GetWebServiceData(context.Request.Path);
 6     // 获得Method Name
 7     string methodName = context.Request.QueryString["mn"];
 8     // 使用CreateHandler(WebServiceData, string)获得Handler
 9     return RestHandler.CreateHandler(data, methodName);
10 }

  这里出现了一个非常重要的类,那就是WebServiceData,它封装了通过Atlas访问的Web Services,并提供了缓存等重要功能。这个类可以说是Atlas访问Web Serivces的重要组成部分,接下来我将对它进行简单的分析。实事求是地说,了解这些代码(乃至对整个服务器端代码的分析)并不会对Atlas技术的 使用能力产生直接的效果,因此不感兴趣的朋友可以跳过这部分,而直接看之后的结论与范例。:)

  在分析WebServiceData.GetWebServiceData(string)之前,我们先看一下这个类的静态构造函数。
WebServiceData静态构造函数分析
1 static WebServiceData()
2 {
3     // Cache:以Web Services的Type作为key,WebServiceData的实例作为value
4     WebServiceData._cache = Hashtable.Synchronized(new Hashtable());
5     // Cache:以*.asmx文件的Virtual Path作为key,Web Service的Type作为value
6     WebServiceData._mappings = Hashtable.Synchronized(new Hashtable(StringComparer.OrdinalIgnoreCase));
7     // Cache:和上面正好相反,以Type作为key,Virtual Path作为value
8     WebServiceData._typeVirtualPath = Hashtable.Synchronized(new Hashtable(StringComparer.OrdinalIgnoreCase));
9 }

  静态构造函数的作用是初始化每个Cache对象,Atlas使用了Synchronized Hashtable作为Cache的容器。似乎.NET Framework 2.0没有提供Synchronized Generic Collection,颇为遗憾。

   接下来要看的就是静态方法GetWebServiceData(string)了,不过它只是直接使用了静态方法GetWebServiceData (string, bool),并将第二个参数设为true,那么我们就跳过它,直接看静态方法GetWebServiceData(string bool)的实现。代码如下:
GetWebServiceData方法分析
 1 internal static WebServiceData GetWebServiceData(string virtualPath, bool failIfNoData)
 2 {
 3     if (virtualPath.EndsWith("bridge.axd", StringComparison.InvariantCultureIgnoreCase))
 4     {
 5         virtualPath = virtualPath.Substring(0, virtualPath.Length - 10+ ".asbx";
 6     }
 7 
 8     // 得到绝对路径(~/xxxxx/xxx.xxx)
 9     virtualPath = VirtualPathUtility.ToAbsolute(virtualPath);
10     // 设法从Cache内获得Web Service的Type
11     Type wsType = WebServiceData._mappings[virtualPath] as Type;
12 
13     bool wsFileExists = false;
14     // 如果Cache内没有
15     if (wsType == null)
16     {
17         // 查看访问的Web Service文件是否存在
18         wsFileExists = HostingEnvironment.VirtualPathProvider.FileExists(virtualPath);
19         // 如果存在的话
20         if (wsFileExists)
21         {
22             // 将Web Service文件编译并得到其Type
23             wsType = BuildManager.GetCompiledType(virtualPath);
24         }
25     }
26 
27     // 如果没有得到Type,并且Web Services文件不存在,
28     // 说明这不是用户提供的Web Service,而是使用程序集
29     // 自己提供的类型,于是就在程序集里进行寻找。
30     if ((wsType == null&& !wsFileExists)
31     {
32         string typeName = null;
33         int num1 = virtualPath.IndexOf("ScriptServices/");
34         // 如果路径里有ScriptServices/
35         if (num1 != -1)
36         {
37             num1 += "ScriptServices/".Length;
38             // 截取"ScriptServices/"后面的字符串,并且将扩展名去掉
39             typeName = virtualPath.Substring(num1, (virtualPath.Length - num1) - 5);
40             // 将所有的'/'换成'.',这样就变成了一个类的FullName。
41             typeName = typeName.Replace('/''.');
42             // 从Atlas自身的程序集得到这个类型。
43             wsType = typeof(WebServiceData).Assembly.GetType(typeName, falsetrue);
44             // 如果Atlas程序集里没有这个类型,那么在全局找这个类型
45             if (wsType == null)
46             {
47                 wsType = BuildManager.GetType(typeName, falsetrue);
48             }
49         }
50         else
51         {
52             try
53             {
54                 // 去掉扩展名
55                 typeName = Path.GetFileNameWithoutExtension(virtualPath);
56                 // 使用Reflection调用Sys.Web.UI.Page的DecryptString获得typeName
57                 typeName = WebServiceData.DecryptString(typeName);
58                 wsType = Type.GetType(typeName);
59             }
60             catch
61             {
62             }
63 
64             if (wsType != null)
65             {
66                 // 在Cache保存Type对象和Virtual Path之间的对应关系。
67                 WebServiceData._mappings[virtualPath] = wsType;
68                 WebServiceData._typeVirtualPath[wsType] = virtualPath;
69             }
70         }
71     }
72 
73     // 如果得到了Web Service的Type
74     if (wsType != null)
75     {
76         // 通过静态方法GetWebServiceData(Type)得到WebServiceData对象
77         return WebServiceData.GetWebServiceData(wsType);
78     }
79 
80     if (failIfNoData)
81     {
82         throw new InvalidOperationException();
83     }
84 
85     return null;
86 }

  方法内部使用了部分.NET Framework 2.0提供的方法,如果希望具体了解这些方法请参考MSDN。

  这是个 比较复杂的方法,不过对它的阅读能够让使用Atlas的方式上一个新的台阶。上面的代码经过了注释,应该已经可以比较方便的理解了。在这里可能需要我详细 解释一下第35到第49行的具体含义。这段逻辑目的是将一个路径映射一个类型,目前在Atlas中的应用就是在使用Authentication Service的时候,它事实上是请求了一个路径 “ScriptServices/Microsoft/Web/Services/Standard/AuthenticationWebService.asmx”, 自然在客户端不会有这个文件。于是就会将这个路径去除扩展名和ScriptServices等字样,变成了 “Microsoft/Web/Services/Standard/AuthenticationWebService”,再将所有的“/”变成 “.”,就成为了一个类的标识 “Microsoft.Web.Services.Standard.AuthenticationWebService”,您可以在程序集中找到这个 类。同样的作法,也存在于Atlas的Profile Service中,它请求的Web Services是 “ScriptServices/Microsoft/Web/Services/Standard/ProfileWebService.asmx”。

   对于开发人员来说,它的价值就是:我们能够把一个Web Service方法的请求处理编译在程序集之中!例如,我们只需要写一个Jeffz.Atlas.SampleService类继承 System.Web.Services.WebService,并在Javascript中请求 “ScriptServices/Jeffz/Atlas/SampleService.asmx”即可。这对于发布和部署组建提供了非常大的便利,对于 喜欢编写Extender的朋友们,也提供了和服务器端交互的完美方式。关于这一点,我会在这一系列接下去的文章中给与具体的范例供大家参考。


  继续回到对代码的分析,GetWebServiceData(string, bool)最终是返回了GetWebServiceData(Type)调用结果。代码如下:
GetWebServiceData(Type)方法分析
 1 private static WebServiceData GetWebServiceData(Type type)
 2 {
 3     // 设法从Cache内获得WebServiceData对象
 4     WebServiceData data = WebServiceData._cache[type] as WebServiceData;
 5 
 6     // 如果Cache内没有
 7     if (data == null)
 8     {
 9         // 构造该对象
10         data = new WebServiceData(type);
11         // 并放入Cache中
12         WebServiceData._cache[type] = data;
13     }
14 
15     return data;
16 }

  代码非常简单,就不多作解释了。WebServiceData类的构造函数也无需分析,只是简单的保留那个Type而已。代码如下:
WebServiceData构造函数
1 private WebServiceData(Type type)
2 {
3     this._type = type;
4 }

  呼,WebServiceData类的分析到这里先告一段落,我们回到之前的代码。获得IHttpHandler对象是调用了RestHandler的CreateHandler(WebServiceData, string)静态方法,代码如下:
CreateHandler(WebServiceData, string)静态方法分析
 1 private static IHttpHandler CreateHandler(WebServiceData webServiceData, string methodName)
 2 {
 3     RestHandler handler;
 4     // 调用GetMethodData得到WebServiceMethodData对象实例,
 5     // 描述了一个Web Service方法。
 6     WebServiceMethodData data = webServiceData.GetMethodData(methodName);
 7 
 8     // 根据是否支持Session选择不同的Handler
 9     if (data.RequiresSession)
10     {
11         handler = new RestHandlerWithSession();
12     }
13     else
14     {
15         handler = new RestHandler();
16     }
17     
18     handler._webServiceMethodData = data;
19     return handler;
20 }

  这里出现了对于Web Services方法的描述类WebServiceMethodData,通过WebServiceData的GetMethodData方法获得。该方法代码如下:
GetMethodData方法分析
 1 internal WebServiceMethodData GetMethodData(string methodName)
 2 {
 3     // 保证Method的描述都被加载并保存了
 4     this.EnsureMethods();
 5 
 6     WebServiceMethodData data = this._methods[methodName];
 7     if (data == null)
 8     {
 9         throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, AtlasWeb.UnknownWebMethod, new object[] { methodName }), "methodName");
10     }
11 
12     return data;
13 }

  this.EnsureMethod()方法通过反射得到了Web Service中类的方法信息并保存下来,代码如下:
EnsureMethod方法分析
 1 private void EnsureMethods()
 2 {
 3     if (this._methods == null)
 4     {
 5         lock (this)
 6         {
 7             Dictionary<string, WebServiceMethodData> methodDict = 
 8                 new Dictionary<string, WebServiceMethodData>(StringComparer.OrdinalIgnoreCase);
 9 
10             // 获得所有public的实例方法
11             MethodInfo[] infoArray = this._type.GetMethods(BindingFlags.Public | BindingFlags.Instance);
12 
13             // 枚举每个MethodInfo
14             foreach (MethodInfo info in infoArray)
15             {
16                 // 获得WebMethodAttribute标注
17                 object[] webMethodAttArray = info.GetCustomAttributes(typeof(WebMethodAttribute), true);
18 
19                 // 如果这个方法被WebMethodAttribute标注了
20                 if (webMethodAttArray.Length != 0)
21                 {
22                     // 获得WebOperationAttribute标注
23                     object[] webOpAttArray = info.GetCustomAttributes(typeof(WebOperationAttribute), true);
24 
25                     // 生成WebServiceMethodData对象
26                     WebServiceMethodData data = new WebServiceMethodData(
27                         this,
28                         info,
29                         (WebMethodAttribute)webMethodAttArray[0],
30                         (webOpAttArray.Length != 0? ((WebOperationAttribute)webOpAttArray[0]) : null);
31 
32                     // 放入Dictionary
33                     methodDict[info.Name] = data;
34                 }
35             }
36 
37             this._methods = methodDict;
38         }
39     }
40 }

  代码运行到此处,已经获得要执行的那个方法。马上就要进入了Handler的ProcessRequest阶段了,在那里会对接受这个请求的输入,并提供输出。那么它就是如何工作的呢?

  我们将在下一篇文章中讨论这个问题。

本文出自 “赵��” 博客,转载请与作者联系!

你可能感兴趣的:(Web,Access,服务器端,支持,Atlas,Sevices)