在上一篇文章里,我们分析讨论了使用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, 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”。 对于开发人员来说,它的价值就是:我们能够把一个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阶段了,在那里会对接受这个请求的输入,并提供输出。那么它就是如何工作的呢?
我们将在下一篇文章中讨论这个问题。