在上一篇文章里,我們分析討論了使用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階段了,在那里會對接受這個請求的輸入,并提供輸出。那么它就是如何工作的呢?
我們將在下一篇文章中討論這個問題。