65.9K
CodeProject 正在变化。 阅读更多。
Home

基于 Provider 模式的灵活可插拔的 .NET 应用程序

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.67/5 (23投票s)

2005 年 12 月 1 日

11分钟阅读

viewsIcon

115556

downloadIcon

1161

本文档演示了如何创建一个灵活、可扩展、可插拔的 .NET 应用程序(JOM - 智能作业管理器)。JOM 是一个异步作业处理引擎,它使用 MS Provider 设计模式和当前可用的 .NET 技术构建。

摘要

本文档演示了如何创建一个灵活、可扩展、可插拔的 .NET 应用程序(JOM - 智能作业管理器)。JOM 是一个异步作业处理引擎,它使用 MS Provider 设计模式和当前可用的 .NET 技术构建。这里使用的技术、库和模式包括 .NET Framework 1.1、C#、MSMQ、Enterprise Library、MS Provider Model 设计模式、.NET Windows 服务和 .NET Windows 应用程序。

引言

在许多情况下,异步处理是可取的,例如,在医院,在为患者打印出院摘要的同时,还需要通过电子邮件、传真和短信通知医生、临床医生患者已出院。所有这些任务都需要处理时间,但您不希望患者等待所有这些任务完成。在这种情况下,您会为传真、电子邮件等实现异步进程,以便您可以尽快专注于出院患者。智能作业管理器框架的设计考虑了这种异步场景。智能作业管理器(JOM)作为 Windows 服务运行,客户端应用程序会将作业排队到 Microsoft 消息队列 (MSMQ)。JOM 使用 MS Provider 设计模式开发,该模式为框架提供了灵活性、可扩展性和即时可插拔性。JOM 从队列接收作业,并分配合适的 Provider 来异步完成它。

介绍智能作业管理器中使用的模式和技术

如果您已经熟悉 MS Provider 设计模式MSMQEnterprise Library,可以跳过本节。

MS Provider 设计模式

MS Provider 设计模式有助于将应用程序与特定的 Provider 依赖项分离,并使应用程序可扩展且灵活。抽象基类包含需要在具体 Provider API 中实现的属性和方法。此外,Provider 的描述在配置文件中定义,这允许 API 的可插拔实现。可以查看 ASP.NET Provider Toolkit,其中 Rob Howard 描述了 Provider 模型及其规范。

MSMQ

JOM 使用 MSMQ 接收任务并将其添加到队列中。消息队列提供一种存储转发的消息传递机制,它建立在预先构建的基础架构之上,有助于解决异步场景。消息队列提供可靠的消息传递、高效的路由、安全性和基于优先级的消息传递。.NET Framework 通过 System.Messaging.dll 提供开箱即用的所有消息队列操作访问。

Enterprise Library

当前 .NET 社区的热门话题是来自 Patterns and Practices Group 的 Enterprise Library,它提供了七个可重用的应用程序块:缓存、配置、加密、数据访问、异常处理、日志记录和检测以及安全性。应用程序块的目的是降低开发成本并提高信心。我只花了几个小时就将缓存、异常处理和日志记录等功能添加到 JOM 中,并且使用了 2005 年 6 月发布的应用程序块。这些应用程序块是非常强大的工具,可以帮助我们在应用程序中保持一致性和效率。使用应用程序块可以使我们的程序更容易编写,因为它在整个应用程序中遵循相似的模式,并且常见的任务被封装在易于使用的类和静态方法中。

智能作业管理器

JOM 促进了两个或多个不同步的、不兼容系统之间的通信。JOM 作为 Windows 服务运行,从 MSMQ 接收作业并动态地分配 Provider 来完成作业。

图. 智能作业管理器工作流

要理解 JOM 框架,我们需要了解 Job 对象和 Providers 的作用。Job 是一个业务对象,负责将 Provider 特定的数据以及 Provider 名称从客户端传递到队列。JOM Windows 服务每 5 秒检查一次队列中包含 Job 对象的事件。当收到作业时,JOM 会从队列中读取它,并分配正确的 Provider 来执行该作业。Provider 通常实现它们希望对给定作业完成的所有代码,例如,一个 Provider 可以发送传真。请记住,Provider 还规定了它对 Job 对象有什么期望才能正确执行作业。客户端应用程序在创建并将其放入队列之前,应该了解 Job 对象的规范。一个例子可能是,要发送一个传真 Provider,它可能需要知道传真号码,而 Job 对象应该包含传真号码。

JOM 框架中的 Job 对象

Job 对象只是一个业务对象,可以跨不同的系统通用。Job 对象包含 Provider 为特定任务所期望的数据,而 Job.JobDetailCollection 属性包含作业特定的数据。Job 对象还应在 JobManagerProviderName 属性中指定要使用的 Provider。例如,要发送传真,Job 对象将在 JobDetailCollection 中包含 faxNoJobDetail 对象充当键/值对,JobDetailCollection 充当键/值对集合。

图. 业务对象:JobJobDetailJobDetailCollection

客户端应用程序只需创建一个 Job 对象并将其发送到指定的队列即可

Job job = new Job(0,0,DateTime.Now,
                  DateTime.Now,"SimpleJobManagerProvider");
JobDetailCollection jobDetails = new JobDetailCollection();
JobDetail jobDetail = new JobDetail(0,0,
                  "Copyright","Use at your own risk.");
jobDetails.Add(jobDetail);
jobDetail = new JobDetail(0,0,"PersonName","Shahed Khan");
jobDetails.Add(jobDetail);
jobDetail = new JobDetail(1,0,"Email","shahed.khan@gmail.com");
jobDetails.Add(jobDetail);
job.JobDetailCollection = jobDetails;
QueueJob(job, QueuePath);

在上面的代码中,我们只是创建了一个指定了 Provider 的 Job 对象,在本例中使用了 SimpleJobManagerProvider。我们还为 Job.JobDetailCollection 赋值了 PersonName、Email 等数据,并通过调用 QueueJob 方法将作业排队到消息队列 (MSMQ) 中。客户端应用程序只需要创建一个带有正确 JobDetailCollection 和正确 ProviderName 的 Job 对象,然后将其传递给消息队列 (MSMQ)。JOM 框架将处理其余的事情。JobJobDetail 之间存在 1:N 关系,您会注意到 JobJobDetail 都有一个 ID 属性。JobDetail 还有一个 JobID 属性。这样做是为了方便将 JobJobDetail 对象集成到数据库环境中。

JOM 中 Provider 模式的实现

由于 JOM 实现了 Provider 模式,因此它是可扩展、灵活的,并提供了 Provider API 的可插拔实现。Provider 模式在此用于克服预定 Provider API 和预定功能的限制。相反,它被设计用于采用实现了 JobManagerProvider 的任何 Provider API。

让我们通过一个简单的示例来演示此模式如何在 JOM 中实现。

图. 智能作业管理器和 Provider 模式

public abstract class JobManagerProvider : ProviderBase { 
   public abstract bool DoJob(Job job);
}

JobManagerProviderabstract 基类具有 DoJob(Job job) 方法,该方法需要在具体 Provider 中实现。例如,在演示应用程序中,我们实现了几个具体 Provider,如 TextToFileJobManagerProviderTextToImageJobManagerProviderSimpleJobManagerProvider

public class SimpleJobManagerProvider : JobManagerProvider {
public override bool DoJob(Job job){
   //DoJob  

   //Write  to file

   StreamWriter sw = new StreamWriter(string.Format("{0}{1}{2}{3}", 
                     OutputFilePath, "SimpleJob", 
                     DateTime.Now.Ticks.ToString(), ".txt"));
   sw.AutoFlush = true; // good practice if you've lots of data

   sw.WriteLine(job.JobManagerProviderName);
   foreach(JobDetail jobDetail in job.JobDetailCollection){
      sw.WriteLine(string.Format("{0}: {1}", 
                   jobDetail.ElementDesc, jobDetail.ElementData));           
   }
   sw.Close();
   return false;
   }
}

在上面的代码中,SimpleJobManagerProvider 继承自 JobManagerProvider 并实现了 DoJob( Job job)。在上面的代码中,此 Provider 将 Job 的内容写入文本文件。

JOM 根据通过 Job 对象传递过来的 Provider 名称动态加载 Provider API 并运行 DoJob 方法。

private  void OnMessageArrival(IAsyncResult ar) {  
    MessageQueue mq = (MessageQueue)ar.AsyncState;
    Job job = new Job();
    try{
        Message msg=mq.EndReceive(ar);
        job = (Job) msg.Body;
        JobManager.DoJob(job);
    }
    catch(Exception e){
        EntLibHelper.JobException("OnMessageArrival",job,e);
    }
    finally{
        mq.BeginReceive(TimeSpan.FromSeconds(5),mq, 
              new AsyncCallback(OnMessageArrival));
    }
}

上面的代码显示了在消息到达时,Job 对象从队列中派生,并调用 JobManager.DoJob(job)。异常通过 Enterprise Library Logging App Block 记录。

public class JobManager {
   public static bool DoJob (Job job){
      JobManagerProvider provider = 
          JobManagerProvider.Instance(job.JobManagerProviderName);
      return provider.DoJob(job);
   }
}

在上面的代码中,我们看到 JobManager 作为工厂来决定加载哪个 Provider。Job 对象应通过 JobManagerProviderName 传递要实例化的 Provider。JobManager 类中的方法是 static 的,因此我们可以直接调用 JobManager.DoJob(Job job),而无需先实例化 JobManager

配置信息:如 MS Provider 规范中所述,在具体 Provider 实现后,必须在配置文件中对其进行描述。Provider 模式的美妙之处在于,合适的 Provider 在运行时根据配置文件中的信息进行实例化,并且您可以定义无限数量的 Provider。

<smartJobManager.providers>
    <jobManager defaultProvider="SimpleJobManagerProvider">
        <providers>
            <add name = "SimpleJobManagerProvider" 
                 type = "SmartJobManager.SimpleJobManagerProvider, 
                         SimpleJobManagerProvider"/>
        </providers>
    </jobManager>
</smartJobManager.providers>

在上面的节中,我们将 SimpleJobManagerProvider 添加到了我们的 Provider 列表中。您可以添加任意数量的 Provider。有关配置文件的完整规范,请参阅 ASP.NET Provider Toolkit。

要实现一个新的 Provider,我们只需编写一个从 JobManagerProvider 扩展的新 Provider,并将 Provider 特定信息添加到配置文件中,仅此而已。

在 JOM 框架中使用 Enterprise Library

JOM 使用 Enterprise Library 2005 年 6 月版进行缓存、日志记录和异常处理。这使得 JOM 可以轻松地根据您的需求转换这些功能。下面的代码块演示了 JOM 中如何实现缓存、日志记录和异常处理。

JOM 中 Provider 的缓存

Provider 被添加到缓存中,因为后面的反射操作开销很大。

// Use the cache because the reflection used later is expensive

Type type = null;
string cacheKey = null;
// Get the names of the providers

JobManagerConfiguration config = 
                       JobManagerConfiguration.GetConfig();
// Read the configuration specific information for this provider

Provider jobManagerProvider = 
                      (Provider) config.Providers[providerName];
// In the cache?

cacheKey = "Job::" + providerName;
if (EntLibHelper.GetCachedObject(cacheKey) == null){
   // The assembly should be in \bin or GAC, so we simply need

   // to get an instance of the type

   try {
      type = Type.GetType( jobManagerProvider.Type );
      // Insert the type into the cache

      Type[] paramTypes = System.Type.EmptyTypes;
      EntLibHelper.StoreInCache( cacheKey, 
             type.GetConstructor(paramTypes) );
   }
   catch (Exception e) {
      throw new Exception("Unable to load provider", e);
   }
}
// Load the configuration settings

object[] paramArray = new object[0];
return (JobManagerProvider)(((ConstructorInfo)
  EntLibHelper.GetCachedObject(cacheKey)).Invoke(paramArray) );

JOM 中的日志记录

对于日志记录,使用了 Enterprise Library 附带的简单平面文件接收器。您可以轻松地将其转移到数据库接收器或任何自定义接收器。

try{
   Message msg=mq.EndReceive(ar);
   job = (Job) msg.Body;
   if (job != null){
      //Log Recieve of a Job

      EntLibHelper.JobInfo("Recieved New Job",job);
      if (!JobManager.DoJob(job)){
         //Log Error

         EntLibHelper.JobError("JobError Occured",job);
      }
   }
}
catch(MessageQueueException e)   {
   if (e.MessageQueueErrorCode != 
               MessageQueueErrorCode.IOTimeout){
      //Log Error

      EntLibHelper.Exception("OnMessageArrival",e);
   }
}

JOM 中的异常处理

所有异常都使用平面文件接收器进行记录。您可以根据需要进行修改。

catch(Exception e){
    EntLibHelper.JobException("OnMessageArrival",job,e);
}

Enterprise Library 配置

图. Enterprise Library 配置屏幕

如何在自己的应用程序中使用智能作业管理器

JOM 步骤

实现一个继承自 JobManagerProvider 的具体 Provider,类似于前面使用的 SimpleJobManagerProvider。您会在演示应用程序中找到一些 Provider。可以编写一些示例 Provider 来打印文件、发送传真、发送电子邮件、记录到数据库、传输文件等。

决定您将通过 JobJobDetailCollection 对象传递什么。例如,要通过 Provider 发送电子邮件,您可能需要通过 JobJobDetailCollection 对象发送电子邮件地址。

在您的 Provider 中,包含一个 DoJob 方法并编写代码。此方法应执行您希望执行的任务。编译并将其 newprovider.dll 复制到 JOM 的 bin 文件夹中。修改配置文件,并为您的 Provider 添加一个新键,如上面的配置部分所示。

客户端应用程序步骤

准备一个 Job 对象,指定 Provider 名称,使用 Provider 指定的数据创建 JobDetailCollection,并通过客户端应用程序将其发送到消息队列。JOM 应该会接收它并异步处理它。现在,修改 Enterprise Library 特定配置。

与“分布式系统命令模式设计”的比较

Brad King 在他的文章 使用命令模式、MSMQ 和 .NET 简化分布式系统设计 中,采用了一种不同的方法,即向队列传递命令对象。

图. 命令模式下的分布式系统工作流

这种方法的优点是它是强类型的,并且可以对业务对象进行验证。但缺点是客户端和调用者都需要知道每个命令对象的原始定义。因此,所有命令对象都应该同时存在于客户端应用程序和调用者应用程序中。然而,在 JOM 中,我们使用了一个所有应用程序通用的业务对象 Job。客户端不知道 Provider,因为客户端只知道 Provider 的名称以及通过 Job 对象传递什么。JOM 完全隐藏了 Provider(业务逻辑)对客户端的暴露。

图. 智能作业管理器工作流

未来增强功能

在这里,演示应用程序将简单的可恢复消息发送到 MSMQ。然而,您可能希望实现可靠消息、创建事务性队列、实现确认、超时和消息的日志记录。此外,此应用程序还可以设计为允许您通过 Enterprise Library UI 进行配置管理。

演示应用程序中将 Job 排入 MSMQ 的代码片段。

private static void QueueJob(Job job, string destinationQueue){
   try{
       // open the queue

       MessageQueue mq = new MessageQueue(destinationQueue);
       // set the message to durable.

       mq.DefaultPropertiesToSend.Recoverable = true;
       // set the formatter to Binary, default is XML

       mq.Formatter = new BinaryMessageFormatter();
       // send the job object

       mq.Send(job, "Job Message Test");
       mq.Close();
   }
   catch (Exception e){
       Console.WriteLine(e.ToString());
   }     
}

此代码从队列读取消息。

try{
   Message msg=mq.EndReceive(ar);
   job = (Job) msg.Body;
   JobManager.DoJob(job);
}

演示应用程序

演示应用程序提供了 JOM 工作流的完整演示。演示包括从 JobManagerProvider 实现的多个具体 Provider、JOM Windows 服务和客户端应用程序。

提供者
  • TextToFileJobManagerProvider:使用用户指定的文本创建文本文件。
  • TextToImageJobManagerProvider:使用用户指定的文本创建 JPEG 图像文件。
  • SimpleJobManagerProvider:创建包含一些预定义文本的文本文件。
  • ScreencaptureJobManagerProvider:捕获客户端应用程序正在运行的屏幕,并创建一个图像文件。
JOM Windows 服务

JOM Windows 服务托管了上述所有 Provider。如果您想使用 JOM 托管自己的 Provider,只需遵循前面讨论的说明即可。JOM 每 5 秒检查一次 MSMQ 中的新作业。

图. 使用计算机管理工具查看和启动 .NET 作业管理器服务

图. 使用计算机管理工具查看排队的作业

客户端应用程序

客户端应用程序具有将作业发送到队列的界面。它可以为上述所有 Provider 创建作业。

图. 智能作业管理器工作流

运行演示应用程序的检查表

查看演示中提供的 Readme.txt 文件。

结论

智能作业管理器采用 Provider 模式进行架构,使其灵活、可插拔且可扩展。此外,所有客户端使用 Job 对象引入了所有应用程序之间的通用通信平台。这也隐藏了 Provider 中实现的所有业务逻辑。JOM 适用于任何需要异步任务的场景。MSMQ 在 JOM 框架中的使用使其可靠且可恢复。并且 Enterprise Library 的日志记录、缓存和异常处理的实现使得 JOM 能够轻松地与企业环境中的其他应用程序集成。

特别感谢 Christopher Heale 的校对。

© . All rights reserved.