分享
 
 
 

深入Atlas系列之服务器端支持(上)

王朝other·作者佚名  2008-06-01
窄屏简体版  字體: |||超大  

在上一篇文章里,我们分析讨论了使用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

10private IHttpHandlerFactory _restHandlerFactory;

11private IHttpHandlerFactory _webServiceHandlerFactory;

12

13// Nested Types

14private 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

22private class AsyncHandlerWrapperWithSession : ScriptHandlerFactory.AsyncHandlerWrapper, IRequiresSessionState

23{

24 // Methods

25 internal AsyncHandlerWrapperWithSession(IHttpHandler originalHandler, IHttpHandlerFactory originalFactory);

26}

27

28internal class HandlerWrapper : IHttpHandler

29{

30// Methods

31internal HandlerWrapper(IHttpHandler originalHandler, IHttpHandlerFactory originalFactory);

32public void ProcessRequest(HttpContext context);

33internal void ReleaseHandler();

34

35// Properties

36public bool IsReusable { get; }

37

38// Fields

39private IHttpHandlerFactory _originalFactory;

40protected IHttpHandler _originalHandler;

41}

42

43internal 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进行处理

11factory = this._restHandlerFactory;

12}

13else

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

11Type wsType = WebServiceData._mappings[virtualPath] as Type;

12

13bool wsFileExists = false;

14// 假如Cache内没有

15if (wsType == null)

16{

17// 查看访问的Web Service文件是否存在

18wsFileExists = HostingEnvironment.VirtualPathProvider.FileExists(virtualPath);

19// 假如存在的话

20if (wsFileExists)

21{

22// 将Web Service文件编译并得到其Type

23wsType = BuildManager.GetCompiledType(virtualPath);

24}

25}

26

27// 假如没有得到Type,并且Web Services文件不存在,

28// 说明这不是用户提供的Web Service,而是使用程序集

29// 自己提供的类型,于是就在程序集里进行寻找。

30if ((wsType == null) && !wsFileExists)

31{

32string typeName = null;

33int num1 = virtualPath.IndexOf("ScriptServices/");

34// 假如路径里有ScriptServices/

35if (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, false, true);

44 // 假如Atlas程序集里没有这个类型,那么在全局找这个类型

45 if (wsType == null)

46 {

47 wsType = BuildManager.GetType(typeName, false, true);

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”。

更多的请看:http://www.QQread.com/windows/2003/index.Html

对于开发人员来说,它的价值就是:我们能够把一个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阶段了,在那里会对接受这个请求的输入,并提供输出。那么它就是如何工作的呢?

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

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有