日期:2010-11-02  浏览次数:20414 次

  一、 引言

  早在2001年,我就着手开发一个ASP.NET在线消息板应用程序WebForums.NET。其目的是创建一个基于ASP.NET的消息板系统,而且该系统可以容易插入到一个现有网站中。构建这样一个端对端应用程序的特别挑战之一就是,要为客户提供一种方式以便能够把它集成到他们自己的系统中去。例如,一个在线论坛明显需要使用某种数据存储来存储用户信息、论坛、回寄信息等;但是,最好不要把客户锁定到一种特定的数据存储中。也就是说,你不应该说,"我的应用程序必须使用微软的SQL Server 2000";因为这样的话,使用Oracle或Access的客户怎么会使用你的软件呢?

  另一个集成到客户现有数据中的问题是,所有的在线论坛站点都提供用户帐户和一种创建新帐户的方式。典型情况下,这被建模为一个论坛架构(以一个数据库中的Users表形式存在)。但是,客户很可能已经有他们自己的拥有成千的用户帐户的数据库表。或者,一个客户可能想在一个内部网设置中使用该论坛,并且想使用活动目录而不是某种数据库表来认证和存储用户信息。因此,当一个论坛软件系统创建一个Users数据库表并对其客户说"这就是你存储用户的方式"时,那些已经拥有现有基础结构和用户数据的客户很可能会疏远这样的软件。

  因此,当你使用一种"僵硬"的API构建一个系统时,会产生特别的挑战。一种"僵硬"的API不是提供一种方式来定制逻辑而是硬编码实现细节(例如,你必须使用SQL Server作为你的后端数据存储,且在这个数据库有一个Users表,并将在其中存储所有的用户信息)。然而,通过使用提供者设计模式,你可以轻易地打破这种"僵硬"性。借助于提供者设计模式,系统架构师只需要定义API,至于编程功能则由系统来提供。对于一个在线论坛应用程序来说,这可能包括一个具有例如Authenticate(username,password)和GetUserDetails(username)等方法的Users类。

  提供者模型的优秀在于客户实现方案可以指定一个系统应该使用的定制类。这种定制类必须实现系统的良好定义的API;但是,它允许无缝地插入任何定制实现。也就是说,一旦定义这个API,系统实现者可以创建一个使用SQL Server和一个Users表的默认的具体实现-大多数客户可以直接使用之而不必要作任何修改。那些有定制需要的客户(他们想使用Oracle或以另外一些方式存储用户数据)可以创建他们自己的类,该类提供必要的功能并且把它们插入到这些客户的系统中。

  其实,提供者设计模式被应用于整个ASP.NET 2.0实现中。当然,网上也存在一些如何在ASP.NET 1.x应用程序中使用这一功能的教程。

  在本文中,我们将详细探讨提供者模型并分析如何把它应用于ASP.NET 2.0开发中。

  二、 打破"僵硬"的API实现

  在我早期的WebForums.NET开发中,我认识到,这种"僵硬"的API实现将会成为一个问题。我的软件设计目标之一就是:尽可能灵活且可定制,并且使用户使用SQL Server,而且我的用户数据模型实现应该看起来充其量只是有些限制性。为了克服这些问题,我构建了一个包含下面两部分的系统:

  1. 一组定义了系统的核心功能的抽象基类;

  2. 能够在运行时刻动态地加载一个扩展抽象基类。具体地说,该代码负责检查包含一个<ConfigSetting>节(该节中给出要使用的类的完全限定名)的Web.config文件。

  借助于这一架构,我可以通过一系列抽象基类来定义系统的功能,并使用SQL Server 2000和Users表来提供这些类的具体实现。满足这一配置的客户可以只管使用该应用程序,并且一切将工作良好,且不需要他们编写一行代码。然而,那些需要定制的开发者们可以通过创建他们自己的派生自适当的抽象基类的类来实现。通过简单地把该程序集放到应用程序的/bin目录并更新Web.config文件,他们可以让系统使用这个新类。具体地说,WebForums.NET发行中带有一个抽象基类DataProvider,它清楚地列举出了系统中的所有方法,类似如下:

public abstract class DataProvider
{
public abstract bool AuthenticateUser(string username,string password);
public abstract User GetUserInfo(string username);
...
public static DataProvider Instance()
{
...
}
}

  AuthenticateUser(username,password)和GetUserInfo(username)方法是系统定义的许多方法中的两个方法的代表。而静态Instance()方法是该DataProvider类的主要实现;它包含检查代表了WebForums.NET配置信息(该信息指示系统要使用的类的全称限定名)的Web.config文件的代码。然后,该方法使用反射(和缓冲)来创建该类的一个实例并且把它返回到系统。

  WebForums.NET发行中还带有一个派生自DataProvider基类的SqlDataProvider类,这个类提供分类方法的具体实现。例如,SqlDataProvider的所有方法都可以操作存储于一个SQL Server 2000数据库中的数据;与用户相关的方法可以与一个预定义的Users数据库表一起工作。一个想改变后端功能的客户可以创建他自己的派生自DataProvider的类,这些信息都可以展示于Web.config文件中(指明应该使用他们的定制类)。例如,WebForums.NET中的Web.config可能包括下列内容:

<WebForumsSettings>
<add key="DataProviderAssemblyPath" value="path" />
<add key="DataProviderClassName" value="Namespace.Classname" />
</WebForumsSettings>

  默认情况下,这个设置信息引用随同WebForums.NET一起发行的SqlDataProvider类。然而,如果一个客户创建他自己的API实现,那么他可以提供自己的类的细节,并且系统会自动地开始使用他的实现来创建默认实现。

  借助于这一架构,使用WebForums.NET的页面开发者可以使用如下所示的代码来认证一个用户:

if (DataProvider.Instance().AuthenticateUser(username,password))
//用户被认证
else
//用户名/口令无效!

  当调用DataProvider.Instance()方法时,上面的配置文件被检查并且返回适当类的一个实例。如果客户还没有创建他们自己的实现的话,这将是默认的SqlDataProvider类;而如果他们已经实现的话,它将是他们自己的类。一旦DataProvider.Instance()方法返回一个适当的提供者实例,我们就可以简单地调用该API的成员(在这个例子中是AuthenticateUser())。

  WebForums.NET提供者模型-一个早期的原型

  相对于微软建议使用的提供者模型,Andy的提供者模型含有一些不足。一方面,WebForums.NET中提供了单个抽象基类,所有的API定义都聚集在这个类中。其负面作用在于,如果一个客户仅想定制系统的一小部分,例如用户信息的存储方式,那么他必须提供该系统中所有方法的实现。一种更好的方案是,为系统中的每一个逻辑实体创建一个抽象基类。例如,对于一个在线消息板应用程序来说,它可能需要一些类,如UsersProvider,ForumsProvider,PostsProvider,等等。然而,在你提供给一个客户的提供者数目之间也存在一个平衡问题。更多提供者允许更为细致的系统定制,但是也会相应地提高要求的配置标记的数量。

  另外,我已经展示了对WebForums.NET的