UML软件工程组织

C#应用反射:建立一个动态WEB服务来简化代码

 

作者: Builder.com.cn 文章来源:Builder.com.cn

 

许多开发者应用WEB服务与其业务逻辑进行通信,这样做有许多好处。在构架方面,这种方法有其它方法所没有的一系列灵活性。但是,它也存在一些缺点。缺点之一在于保持WEB服务方法与业务逻辑方法同步涉及大量繁琐的工作。本文为你说明如何在享受WEB服务好处的同时,避免上述同步问题。

推理过程

我最近设计并建立了一个应用程序,它利用WEB服务进行业务层通信。其界面应用一个定制的组件来要求数据,定制的组件使用WEB服务与业务层进行通信。这样这个界面可配置在我们希望的任何地方,而且可以通过SSL确保所有通信。它的一般构架与图A类似。

          图A


 一般构架

业务逻辑类包含静态方法,可在数据送交到数据访问层前对其进行处理,或通过WEB服务返回通信组件。

前两个星期,当我们把它应用于主系统时,这个程序运行良好。但随后,我们开始在业务逻辑中增加越来越多的方法,它们需要通过WEB服务来揭示。由于我们要为WEB服务和业务逻辑类设定1:1的比例,这一过程要花费大量的时间。每次我们增加一个新业务逻辑类,我们必须建立一个新的WEB服务;为WEB服务安排代理类;保持代理类与WEB服务同步,并保持WEB服务与业务逻辑同步。

我们的最后期限很短,时间安排也非常紧。我们需要想出一个办法来自动化或简化与WEB服务应用有关的维护过程。互相讨论之后(如代码生成器——我们已使用一个代码生成器从数据库表中生成类),我想出一个主意:使用反射自动调用业务逻辑类中的方法,并通过WEB服务返回其结果。半小时后,我建立了第一个原型,约二个小时后,我建成了一个我感觉可用于生产环境中的组件。

安装

让我们假设你已经开发出一些类。这些类可称之为“数据传输对象”(DTO——在这里阅读更多DTO的有关信息)。

  • CustomerData——保存单独客户数据
  • OrderData——保存单独订单数据

我们还建立了以下这些类,它们的任务是与数据访问层进行通信,并移植CustomerData和OrderData对象:

  • Customer——它有两个静态方法,LoadByCustomerID(int customerID)和LoadByOrderID(int orderID)。这两个方法都返回单独一个CustomerData对象。
  • Order——它有两个静态方法,LoadByOrderID(int orderID) 和LoadAllForCustomerID(int customerID)。

正常情况下,在这种情形中,会存在两个WEB服务——一个返回CustomerData对象,一个返回OrderData对象(你可以把它们结合到一个WEB服务中,但当它们处理大量类时会出现堵塞)。这两个WEB服务各自有两个方法。程序将从界面层,或像我的例子一样从通信组件中调用WEB服务方法。然后WEB服务再调用业务层获得所需的数据。因此在正常情况下,每个WEB服务获得两个DTO、两个类、两个WEB服务和两个方法。(见图B)

例如:


 安装

我使用的应用程序有大约100个这样的实例,我们每天都在增加更多实例。很明显,由于时间限制,那不是解决问题的办法。

应用反射,你可以利用一个具有两个方法的WEB服务来处理你需要在业务层进行的每一个单独调用。这样你就可以从WEB服务与代理类维护工作中解放出来。(见图C)

例如:

图C


 反射

 工作原理

它通过在WEB服务中揭示两个方法来工作——ExecuteMethod和ExecuteArrayMethod。这两个方法接受同样的参数,其内部工作原理也基本相同。其不同在于一个返回一ArrayList对象,一个返回一个对象。

这些方法要求你告诉它们你希望使用业务层中的哪个类(typeName参数),执行那个类中的哪个方法(方法参数——记住这里我们使用的是静态方法),送给那个方法什么值作参数(引数参数)。一旦WEB服务被调用,它访问业务类,用Type.Invoke方法执行带指定参数的方法,然后返回与商业类方法返回的相同值。

所以,基本上我们没有通过WEB服务直接在业务逻辑层上执行任何操作。你指导WEB服务执行哪个方法,它就动态执行这个方法。

代码

这种动态WEB服务的代码如列表A所示。

列表A

C# Code:

///<summary>

/// Summary description for BusinessPipe.

///</summary>

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

[XmlInclude(typeof(CustomerData)),XmlInclude(typeof(OrderData))]

publicclass BusinessPipe : System.Web.Services.WebService

{

//This should be set to the name of your business logic assembly.

privateconststring BUSINESS_ASSEMBLY = "My.Business.Assembly";

#region Component Designer generated code

//Required by the Web Services Designer

private IContainer components = null;

///<summary>

/// Clean up any resources being used.

///</summary>

protectedoverridevoid Dispose( bool disposing )

{

if(disposing && components != null)

{

components.Dispose();

}

base.Dispose(disposing);

}

#endregion

private Type AccessType(string typeName)

{

Type type = null;

Assembly assembly = System.Reflection.Assembly.Load(BUSINESS_ASSEMBLY);

if(assembly == null)

thrownew Exception("Could not find assembly in BusinessPipe!");

type = assembly.GetType(BUSINESS_ASSEMBLY + "." + typeName);

if(type == null)

thrownew Exception("Could not find type!");

return type;

}

///<summary>

/// Executes a method on the Business Logic and returns whatever object that

/// method returns.

///</summary>

///<param name="typeName">The class in the Business Logic to reference.</param>

///<param name="method">The method that you want to execute in the class.</param>

///<param name="arguments">The arguments to send to the method.</param>

///<returns>The same object that the business logic method returns.</returns>

[WebMethod]

publicobject ExecuteMethod(string typeName, string method,

paramsobject[] arguments)

{

object returnObject = null;

Type type = AccessType(typeName);

try

{

returnObject = type.InvokeMember(method,

BindingFlags.Default | BindingFlags.InvokeMethod,

null, null, arguments);

}

catch

{

//Do some custom exception handling here.

throw;

}

return returnObject;

}

///<summary>

/// Executes a method on the Business Logic and returns the same ArrayList that

/// the method returns.

///</summary>

///<param name="typeName">The class in the Business Logic to reference.</param>

///<param name="method">The method that you want to execute in the class.</param>

///<param name="arguments">The arguments to send to the method.</param>

///<returns>The same object that the business logic method returns.</returns>

[WebMethod]

public ArrayList ExecuteArrayMethod(string typeName, string method,

paramsobject[] arguments)

{

ArrayList returnObject = null;

Type type = AccessType(typeName);

try

{

returnObject = type.InvokeMember(method,

BindingFlags.Default | BindingFlags.InvokeMethod,

null, null, arguments) as ArrayList;

}

catch

{

//Do some custom exception handling here.

throw;

}

return returnObject;

}

}

代码注释

注意在WEB服务顶部,我包含了两个XmlInclude语句。要求这两个语句使框架了解如何序列化OrderData和CustomerData对象。你需要为每种类型的对象(它们要么送交到WEB服务,要么由WEB服务发出)增加这些语句。而且,包含业务逻辑的配件必须从WEB服务中引用。如果不是这样,WEB服务将不能访问业务逻辑。

缺陷

这种方法存在一些缺陷:

  • 你失去WEB服务的IntelliSense和被调用的方法。如果你打算由其它部分耗用WEB服务,或你的开发者并不熟悉业务逻辑代码,这确实是一种缺陷。这还意味着你在与WEB服务交互时会失去类型安全。在我看来,失去类型安全是这种技巧最大的缺点。
  • 没有内置的安全机制限制可从WEB服务访问的业务逻辑方法。

虽然这些确实是问题,但我觉得可以相对容易地解决并更正它们。

  • 要允许IntelliSense,你可以轻松建立仅仅处理对来自WEB服务的请求/响应的代理类。但是,由于你必须使代理类与业务类保持同步,这种方法取消了这种动态WEB服务的一个优点。
  • 为保证安全,你可以使用定制属性来定义可见与不可见的业务逻辑方法。在调用Type.Invoke()之前应用反射来检查属性可以相对方便地完成这一任务。


 并非万能

本文详细说明的解决方案当然不能适用于一切情形。例如,它不能用于外部WEB服务。但是,某些情况下,这种类型的解决方案可节省大量时间。当它与数据库映射功能组合使用时,我的工作团队编写的代码比应用传统技巧时减少了大约60%。

 


版权所有:UML软件工程组织