【转】解析Atlas服务器端支持

增加服务器端的支持其实就是添加/改变处理一个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

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

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

11factory = 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文件是否存在

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

19// 如果存在的话

20if (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 {

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

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

 

 

 

在上一篇文章《解析Atlas服务器端支持(上)》里,我们分析了一部分服务器端的代码。到现在为止,我们已经获得处理Web Services方法请求的Handler,马上就要开始Process Request了。我们知道,处理Web Services方法请求的Handler是RestHandler,所以我们来看一下它的ProcessHandler方法:

ProcessRequest获得调用结果代码分析:

1 public void ProcessRequest(HttpContext context)

2 {

3 // 根据Web Services的Cache设置来配置Cache策略

4 this.InitializeCachePolicy(context);

5

6 try

7 {

8 string contentType;

9

10 // 从body的JSON字符串中得到输入的参数。

11 IDictionary<string, object> inputParams = this.GetRawParams(context);

12

13 // this._webServiceMethodData.Ower.Type即获得了即将调用的那个

14 // Web Service类,通过Activator.CreateInstance方法构造实例。

15 object serviceObj = Activator.CreateInstance(this._webServiceMethodData.Owner.Type);

16

17 // 这是一段很有技巧的代码,我们目前不去分析它。

18 DataService service = serviceObj as DataService;

19 if (service != null)

20 {

21 service.IsCalledRemotely = true;

22 }

23

24 // Call这个Web Service方法来得到结果

25 object resultObj = this._webServiceMethodData.CallMethodFromRawParams(serviceObj, inputParams);

26

27 ……

28 }

29 catch (Exception e)

30 {

31 ……

32 }

33 }

首先调用InitializeCachePolicy方法来处理缓存策略,代码很短也很简单,因此不多解释了。其次查看serviceObj是否是DataService类型,如果是的话则将IsCalledRemotely设为ture,这是比较有技巧的做法,我们目前不去分析它。接着通过GetRawParams方法方法获得以Dictionary方式存放的参数,我们需要看一下它的框架,可以了解它获得参数的方法。

GetRawParams(HttpContext) :

1 private IDictionary<string, object> GetRawParams(HttpContext context)

2 {

3 // 如果是Cross Domain Access,则抛出异常

4 if (!this._webServiceMethodData.SafeForCrossDomain && ChildRequest.IsCrossDomainRequest(context))

5 {

6 throw new InvalidOperationException(

7 string.Format(

8 CultureInfo.CurrentCulture,

9 AtlasWeb.InvalidCrossDomainRequest,

10 new object[] { this._webServiceMethodData.MethodInfo.Name }));

11 }

12

13 // 如果使用HTTP POST方法

14 if (context.Request.HttpMethod == "POST")

15 {

16 // 则通过Body中的JSON代码获得参数

17 return this.GetRawParamsFromPostRequest(context);

18 }

19

20 // 由于使用了HTTP GET方法,需要看一下Web Services方法是否允许GET操作

21 if (!this._webServiceMethodData.GetVerbEnabled)

22 {

23 throw new InvalidOperationException(

24 string.Format(

25 CultureInfo.CurrentCulture,

26 AtlasWeb.InvalidGetRequest,

27 new object[] { this._webServiceMethodData.MethodInfo.Name }));

28 }

29

30 // 从Query String中获得参数

31 return this.GetRawParamsFromGetRequest(context);

32 }

一个Web Service方法,可以使用Microsoft.Web.Services.WebOperationAttribute来标记是否使用能够通过GET方法访问。下面的代码让该Web Service方法允许使用GET方法来访问:[WebOperation(true)]

获得的Dictionary数据结构以Key - Value的方式对应的参数名和表示参数值的字符串,如果是复杂类型的话会产生许多层的Dictionary或List,大家应该能够想象出来它是什么样子,因为这和大名鼎鼎的JSON非常相似!获得参数之后,会将其传入WebServiceMethodData的CallMethodFromRawParams方法,以获得方法执行的结果。


WebServiceMethodData.CallMethodFromRawParams方法代码如下:

 

CallMethodFromRawParams方法分析

1 internal object CallMethodFromRawParams(object target, IDictionary<string, object> parameters)

2 {

3 // 获得强类型的参数字典

4 parameters = this.StrongTypeParameters(parameters);

5 return this.CallMethod(target, parameters);

6 }
首先通过StrongTypeParameters方法来获得一个强类型的参数字典,它不会有多层的Dictionary或List。此方法非常的复杂,在这里就先不进行分析了,有兴趣的朋友可以先看一下相关代码,我会在今后的文章中分析这些代码的细节,它们还是写得非常优秀的。得到强类型的参数后,就会使用CallMethod来调用方法得到结果了。在这里面只是使用了简单的Reflection,相信大家也能够想象得到个中实现。

接下来就要输出结果了,代码如下:

ProcessRequest输出结果代码分析 :

1 public void ProcessRequest(HttpContext context)

2 {

3 ……

4

5 try

6 {

7 ……

8

9 // 如果这个Web Service方法被要求使用XML作为Response

10 if (this._webServiceMethodData.UseXmlResponse)

11 {

12 // 如果result是String,那么直接输出

13 if (resultObj is string)

14 {

15 body = (string) resultObj;

16 }

17 else if (resultObj is XmlNode)

18 {

19 // 如果是一段XML,则输出它的OuterXml

20 body = ((XmlNode) resultObj).OuterXml;

21 }

22 else // 如果只是一个普通的对象

23 {

24 try

25 {

26 // 使用XmlSerializer来序列化对象

27 body = ServicesUtilities.XmlSerializeObjectToString(resultObj);

28 }

29 catch (Exception e)

30 {

31 throw new InvalidOperationException(

32 string.Format(

33 CultureInfo.CurrentCulture,

34 AtlasWeb.InvalidXmlReturnType,

35 new object[] {

36 this._webServiceMethodData.MethodInfo.Name,

37 resultObj.GetType().FullName,

38 e.Message

39 }

40 )

41 );

42 }

43 }

44

45 // contentType为"text/xml"

46 contentType = "text/xml";

47 }

48 else // 如果不以Xml输出

49 {

50 // 那么以JSON方式输出

51 body = JavaScriptObjectSerializer.Serialize(resultObj, this._webServiceMethodData.Owner);

52 // contentType为"application/json"

53 contentType = "application/json";

54 }

55

56 // 设置ContentType

57 context.Response.ContentType = contentType;

58 // 输出body

59 if (body != null)

60 {

61 context.Response.Write(body);

62 }

63 }

64 catch (Exception e)

65 {

66 ……

67 }

68 }
要设置该Web Services方法的输出方式为XML(UseXmlResponse == true),则也是需要使用Microsoft.Web.Services.WebOperationAttribute来标记方法,如下:
[WebOperation(false, ResponseFormatMode.Xml)]

后面会有一个例子来演示这一点。如果该方法被标记使用XML方式输出,则会判断结果类型。如果是字符串则直接输出;如果是Xml类型的结果,则输出它的OuterXml;最后则会尝试使用XmlSerializer来序列化这个对象。

在默认情况下,该对象会被JSON序列化输出,这就是我们最常见的做法。


最后,对于异常情况,也需要进行输出。代码如下:

 

ProcessRequest输出异常代码分析 :

1 public void ProcessRequest(HttpContext context)

2 {

3 ……

4

5 try

6 {

7 ……

8 }

9 catch (Exception e)

10 {

11 // 输出异常信息

12 context.Response.ClearHeaders();

13 context.Response.Clear();

14 // Status Code设为500

15 context.Response.StatusCode = 500;

16 context.Response.StatusDescription = HttpWorkerRequest.GetStatusDescription(500);

17 using (StreamWriter writer = new StreamWriter(context.Response.OutputStream, new UTF8Encoding(false)))

18 {

19 // 以JSON方式输出异常信息

20 RestHandler.WriteExceptionJsonString(context, writer, e);

21 return;

22 }

23 }

24 }

代码使用RestHandler.WriteExceptionJsonString来分别输出异常的Message,StackTrace和异常的FullName。在代码里可以使用这一点。

到现在为止,可以说Atlas在服务器端对于Web Services的支持代码已经分析完了。我们通过两个实例来详细理解这一点。

范例1:在Web Services方法中使用复杂的数据类型。

首先,我们定义两个表示数据的类,Employee和Company。代码如下:

Employee与Company代码:

1 [Serializable]

2 public class Employee : IComparable<Employee>

3 {

4 public string Name;

5

6 public int Age;

7

8 #region IComparable<Employee> Members

9

10 public int CompareTo(Employee other)

11 {

12 return this.Name.CompareTo(other.Name);

13 }

14

15 #endregion

16 }

17

18 [Serializable]

19 public class Company

20 {

21 public string Name;

22

23 public Employee[] Employees;

24 }

接着我们定义一个Web Services方法Sort,该方法的作用是拿到公司姓名和一个Employee数组作为参数,将Employee按照姓名排序之后,再组成一个Company对象输出。代码如下:

Sort方法

1 [WebService(Namespace = "http://tempuri.org/")]

2 [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]

3 public class ComplexTypeWS : System.Web.Services.WebService {

4

5 [WebMethod]

6 public Company Sort(string companyName, Employee[] employees)

7 {

8 Array.Sort<Employee>(employees);

9

10 Company company = new Company();

11 company.Name = companyName;

12 company.Employees = employees;

13 return company;

14 }

15 }
然后就是HTML了。在页面最上方(id为employees的div)会显示内存中目前所有的Employee,之后是向内存中添加Employee的输入框,接着是填写公司名的文本框和排序按钮,最后则是经过了Web Services排序后的结果显示区域(id为sortedDisplay的div):

HTML代码:

1 <body style="font-family:Verdana; font-size: 14px;">

2 <form id="form1" runat="server">

3 <atlas:ScriptManager runat="server" ID="ScriptManager1" />

4

5 <div>Employees:</div>

6 <div id="employees"></div>

7 <hr />

8 <div>Add Employee:</div>

9 <div>Name: <input type="text" id="empName" /></div>

10 <div>Age: <input type="text" id="empAge" /></div>

11 <input type="button" value="Add employee" οnclick="addEmployee()" /><br />

12 <hr />

13 <div>Company Name:<input type="text" id="companyName" /></div>

14 <input type="button" value="Sort!" οnclick="sort()" /><br />

15 <hr />

16 <div id="sortedDisplay"></div>

17 </form>

18 </body>


最后我们来看Javascript代码:

 

Javascript代码:

1 <script language="javascript">

2 // 内存中的Employee数组

3 var empArray = new Array();

4

5 // 添加一个Employee

6 function addEmployee()

7 {

8 // 建立一个对象表示Employee

9 var emp = new Object();

10 emp.Name = $('empName').value;

11 emp.Age = parseInt($("empAge").value, 10);

12

13 // 加入数组

14 empArray.push(emp);

15

16 // 更新最上方的显示

17 updateSource();

18 }

19

20 // 将内存中的empArray数组显示在id为employee的div中

21 function updateSource()

22 {

23 var html = "";

24

25 for (var i = 0; i < empArray.length; i++)

26 {

27 var emp = empArray[i];

28 html += ((i + 1) + ". " + emp.Name + ", " + emp.Age + " years old.<br />")

29 }

30

31 $("employees").innerHTML = html;

32 }

33

34 // 访问Web Service进行排序

35 function sort()

36 {

37 // 构造参数

38 var params = { "companyName" : $("companyName").value, "employees" : empArray };

39 // 构造Web Service方法访问对象

40 var method = new Sys.Net.ServiceMethod("ComplexTypeWS.asmx", "Sort", null);

41

42 // 调用Web Service方法

43 method.invoke(params, onMethodComplete);

44 }

45

46 // 回调函数

47 function onMethodComplete(company, response, userContext)

48 {

49 // 在id为sortedDisplay的div中显示所有的Employee,

50 // 可以发现company对象和服务器端对象的结构相同

51 var html = "Company Name: " + company.Name;

52 for (var i = 0; i < company.Employees.length; i++)

53 {

54 var emp = company.Employees[i];

55 html += ("<br />" + (i + 1) + ". " + emp.Name + ", " + emp.Age + " years old.")

56 }

57

58 $("sortedDisplay").innerHTML = html;

59

60 // 清空内存中的Employee

61 empArray.length = 0;

62 // 更新最上方的显示

63 updateSource();

64 }

65 </script>

所有的代码都在这里,我们来看一下使用。首先打开页面,输入数个Employee,如图:


然后点击填写好Company Name并点击Sort按钮,则可以看出按照姓名排序后的结果:

 

我们使用Fiddler查看一下数据传输,可以看到Request Body和Response Body里的JSON代码:

点击放大此图片

可以看出,Atlas使用了JSON方式传递数据非常的直观,对于复杂的类型支持也非常好。在客户端得到的对象,其结构和服务器端相同,这对于开发人员带来了不小的便利。

范例2:使用Web Services将对象序列化成XML并使用客户端XSLTView空间输出信息

使用了与上例相同的Employee和Company两个类,在这里就不重复了,先来看一下Web Service方法GetXmlSerializedCompany的代码:

GetXmlSerializedCompany方法代码:

1 [WebService(Namespace = "http://tempuri.org/")]

2 [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]

3 public class ComplexTypeWS : System.Web.Services.WebService {

4

5 [WebMethod]

6 [WebOperation(false, ResponseFormatMode.Xml)]

7 public Company GetXmlSerializedCompany(Company company)

8 {

9 return company;

10 }

11 }

这个方法简单地令人惊讶,只是直接将参数返回。其精妙之处就是使用了Microsoft.Web.Services.WebOperationAttribute进行标记,表明了该方法将以XML形式输出。

接下来是HTML,与上例非常的相似,就不多作解释了。代码如下:

1 <atlas:ScriptManager ID="ScriptManager1" runat="server" />

2

3 <form id="form1" runat="server">

4

5 <div>Employees:</div>

6 <div id="employees"></div>

7 <hr />

8 <div>Add Employee:</div>

9 <div>Name: <input type="text" id="empName" /></div>

10 <div>Age: <input type="text" id="empAge" /></div>

11 <input type="button" value="Add employee" οnclick="addEmployee()" /><br />

12 <hr />

13 <div>Company Name:<input type="text" id="companyName" /></div>

14 <input type="button" value="Serialize!" οnclick="serialize()" /><br />

15 <hr />

16 <div id="xmlDisplay"></div>

17

18 </form>
然后准备一下Atlas Xml Script,声明一个XmlDataSource,用来获得XSLT文件。再添加一个XSLTView,将其transform属性与XmlDataSource的document属性绑定起来。代码如下:

Atlas Xml Script代码:

1 <script type="text/xml-script">

2 <page>

3 <components>

4 <xmlDataSource id="xsltSource" autoLoad="true" serviceURL="Company.xsl" />

5 <xsltView id="xmlDisplay">

6 <bindings>

7 <binding property="transform" dataContext="xsltSource" dataPath="document" />

8 </bindings>

9 </xsltView>

10 </components>

11 </page>

12 </script>

 

顺便给出Company.xsl文件代码:

Company.xsl:

1 <?xml version="1.0" encoding="utf-8"?>

2 <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

3 <xsl:template match="/Company">

4 <div>

5 Company:

6 <xsl:value-of select="Name" />

7 </div>

8 <xsl:for-each select="Employees/Employee">

9 <div>

10 <xsl:value-of select="Name" />

11 <xsl:text>, </xsl:text>

12 <xsl:value-of select="Age" />

13 <xsl:text> years old.</xsl:text>

14 </div>

15 </xsl:for-each>

16 </xsl:template>

17 </xsl:stylesheet>

然后是Javascript代码,大部分与上例相同,只作了少量注释:

Javascript代码:

1 <script language="javascript">

2 var empArray = new Array();

3

4 function addEmployee()

5 {

6 var emp = new Object();

7 emp.Name = $('empName').value;

8 emp.Age = parseInt($("empAge").value, 10);

9

10 empArray.push(emp);

11 updateSource();

12 }

13

14 function updateSource()

15 {

16 var html = "";

17

18 for (var i = 0; i < empArray.length; i++)

19 {

20 var emp = empArray[i];

21 html += ((i + 1) + ". " + emp.Name + ", " + emp.Age + " years old.<br />")

22 }

23

24 $("employees").innerHTML = html;

25 }

26

27 function serialize()

28 {

29 // 构造一个Company对象作为参数,

30 // 结构和服务器端对象相同。

31 var company = new Object();

32 company.Name = $("companyName").value;

33 company.Employees = empArray;

34

35 var params = { "company" : company }

36 var method = new Sys.Net.ServiceMethod("ComplexTypeWS.asmx", "GetXmlSerializedCompany", null);

37

38 method.invoke(params, onMethodComplete);

39 }

40

41 function onMethodComplete(resultXml, response, userContext)

42 {

43 // 这时第一个参数是一个Xml,

44 // 用它来设置XSLTView的document属性。

45 $("xmlDisplay").control.set_document(resultXml);

46

47 empArray.length = 0;

48 updateSource();

49 }

50 </script>
代码就是这些,接下来看一下使用。首先依旧是添加数个Employee:

填写Company Name并点击“Serialize!”按钮,可以看到下方的XSLT输出:

使用Fiddler查看的Request Body和Response Body的信息:

点击放大此图片

正如我们所期望的那样,Response Body里的信息是Company对象被Xml序列化之后的结果,然后使用XSLT转换后即得到了我们需要的信息!

通过了上面两个例子,我们可以看出Atlas对于Web Services的支持是非常灵活的。具体的使用方式让开发人员有很大的发挥空间,开发人员完全可以选择合适的方式把Atlas灵活运用在自己的项目中。

转载于:https://www.cnblogs.com/djbone/archive/2008/08/21/1272824.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值