程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> 關於.NET >> Enterprise Library深入解析與靈活應用(5)

Enterprise Library深入解析與靈活應用(5)

編輯:關於.NET

最近負責一個框架性項目的升級,主要是從.NET Framework 3.0建議到.NET .NET Framework 3.5,開發工具也從VS2005遷移到VS2008。但是最讓我頭疼的是 ,原來Team Foundation Server 2005不能正常工作,公司暫時還沒有購買VSTS 2008的打算。基於TFS 2005的Team Build功能不能使用了,導致原本通過Team Build實現的功能需要手工來做,涉及到的包括:Source Code的編譯、文檔的生 成、VS項目類型的模板的創建、腳本的合並、安裝包的生成等等。由於絕大部分 的功能分為兩類:文件系統的管理(目錄/文件的創建、移動、拷貝和刪除)和可 執行文件的執行,所以我本打算寫一個bat文件搞定就可以了,在操作過程中覺得 可擴展性太差了,於是花了半天的時間寫了一個GUI的工具。

這個工具執 行一組批處理,也可以看成是一個Sequential Workflow的執行器,我把它成為 Batch Job Executor。在使用Batch Job Executor過程中,通過配置可以對批處 理的每個步驟、或者是Workflow的每個Activity進行自由地定義。從功能上將, 這個小工具僅僅是個小玩意兒,不登大雅之堂。 不過考慮到Batch Job Executor 的涉及和實現是基於Enterprise Library典型的實現方式,比如基於EL的配置和 對象創建方式,對於那些希望進一步了解EL的讀者,或許可以通過這個小小的例 子一窺EL的設計原理。對於那些EL的設計不時很了解的讀者,對於以下的內容, 可能在理解上可能比較困難。最好是下載源代碼,結合下面的介紹,希望會幫助 了更好的理解EL。

一、Batch Job Executor使用

使用Batch Job Executor最重要的步驟就是通過配置配處理的每一個步驟進行設置,在這裡我們 組成Batch的步驟成為Job Step。我們可以先來看看下面的配置示例:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <configSections>
        <section name="batchJobExecutor" type="Artech.BatchJobExecutor.Configuration.BatchJobExecutorSettings,Artech.BatchJobExecutor"/>
    </configSections>
    <batchJobExecutor defaultBatchJob="Batch Job 1">
        <variables>
            <add name="RootLocation" value="E:\Other Projects\Artech.BatchJobExecutor\"/>
            <add name="OutputLocation" value="E:\Output\"/>
        </variables>
        <batchJobs>
            <add name="Batch Job 1" description="The first batch job">
                <steps>
                    <!--step 1-->
                    <add name="Create Temp Directory" type="Artech.BatchJobExecutor.DirectoryCreationJobStep,Artech.BatchJobExecutor"
                         directoryToCreate="$OutputLocation$" />
                    <!--step 2-->
                    <add name="Notepad" type="Artech.BatchJobExecutor.ExecutableJobStep,Artech.BatchJobExecutor" executableFile="Notepad" waitForProcessExit="false">
                        <arguments>
                            <add name="param1" value="E:\readme.txt"/>
                        </arguments>
                    </add>
                    <!--step 3-->
                    <add name="Copy to Output Location" type="Artech.BatchJobExecutor.DirectoryMoveJobStep,Artech.BatchJobExecutor"
                         source="$RootLocation$ConsoleApplication2\Bin" destination="$OutputLocation$" />
                    <!--step 4-->
                    <add name="Execute Command 1" type="Artech.BatchJobExecutor.ExecutableJobStep,Artech.BatchJobExecutor" executableFile="$OutputLocation$debug\ConsoleApplication1.exe">
                        <arguments>
                            <add name="param1" value="1st Patameter"/>
                            <add name="param2" value="2nd Patameter"/>
                        </arguments>
                    </add>
                    <!--step 5-->
                    <add name="Delete Temp Directory" type="Artech.BatchJobExecutor.DirectoryDeletionJobStep,Artech.BatchJobExecutor" directoryToDelete="$OutputLocation$" />
                </steps>
            </add>
            <add name="Batch Job 2" description="2nd batch job">
                <steps>
                    … …
                </steps>
            </add>
        </batchJobs>
    </batchJobExecutor>
</configuration>

這個配置包含兩個部分:變量的定義 和Batch Job的定義。前者定義在<variables>配置節中,一個常用的變量 ,比如基地址,可以通過name-value的方式在這裡定義。在本例中,我們定義兩 個變量(RootLocation和OutputLocation),對變量的引用通過$variable name$ 的方式實現。而後者呢,則通過<batchJobs>配置節進行定義,我們可以定 義一個活著多個Batch Job,在本例中我一共定義了兩個批處理:Batch Job 1和 Batch Job 2。

第一個批處理由5個步驟組成,它們分別是:

Step 1:創建臨時輸出目錄,路經通過變量定義

Step 2:通過Notepad打開一個 .txt文件,文件路徑為E:\readme.txt

Step 3:將原目錄移到Step1創建了 輸出目錄

Step 4:執行Step 3移到輸出目錄下的可執行文件,參數通過 <arguments>配置節指定

Step 5:移出Step 1創建的臨時目錄

有了上面的配置,運行我們Batch Job Executor,將會得到下面的界面。 兩個批處理名稱在下拉框中列出,對於選中的當前批處理,5個Job Step在下面的 Grid中列出來。點擊“Start”按鈕,批處理便開始執行,下面的進度 條現實當前的進度。

二、Batch Job Executor的設計

1、Job Step

構成一個 批處理的步驟通過抽象類JobStep表示,除了定義了Name和Description屬性外, 定義一個抽象的Execute()方法,Job Step的所有邏輯通過該方法實現。

namespace Artech.BatchJobExecutor
{
  public abstract class JobStep
  {
    public string Name
    { get; set; }
    public string Description
     { get; set; }
    public abstract void Execute();
   }
}
由於大部分Job Step用於基於文件系統的操作,我創建了另一 個抽象類DirectoryFileJobStep,暫時還沒有想到需要定義什麼具體的操作,鼓 且定義創建出來,以備以後不時之需:

namespace Artech.BatchJobExecutor
{
  public abstract class DirectoryFileJobStep : JobStep
  {
  }
}

創建了4個具體的JobStep,分別用於進行目錄的創建、移動和刪 除,以及.exe文件的執行(ExecutableJobStep),它們的關系通過下面的類型表 示。

2、Job Step Configuration

由於所有Job Step都需要通過配置 進行設置,所以配置的定義顯得尤為重要。在這裡我們采用Enterprise Library 的Xxx-XxxData-XxxAssembler的結構(比如Exception Handler的定義就采用這樣 的結構)。其中Xxx代表具體使用某種功能的類型(比如WrapHandler),XxxData (比如WrapHandlerData)表示Xxx對應的配置,而XxxAssembler (WrapHandlerAssembler)則實現通過XxxData對Xxx的創建。

我們Job Step的結構大體也由上面3個部分構成,我們以ExecutableJobStep為例,它的結 構大體可以通過下面的類圖表示:

先來看看ExecutableJobStep的定義(只列出重要部分)。三個字段分別 表示可執行文件的路徑、參數和是否需要等待進程結束才能開始下一步驟。 Execute()中通過開啟進程的方式執行可執行文件。

namespace Artech.BatchJobExecutor
{
  [ConfigurationElementType (typeof(ExecutableJobStepData))]
  public class ExecutableJobStep : JobStep
  {
    private string _executableFile;
    private string _arguments;
     private bool _waitForProcessExit;
    … …
    public ExecutableJobStep(string executableFile, string arguments, bool waitForProcessExit)
    {
      if (string.IsNullOrEmpty(executableFile))
      {
         throw new ArgumentNullException("executableFile");
      }
      this._executableFile = executableFile;
      this._arguments = arguments;
       this._waitForProcessExit = waitForProcessExit;
    }
    public override void Execute()
    {
       Process process = null;
      if (string.IsNullOrEmpty(this.Arguments))
      {
         process = Process.Start(this.ExecutableFile);
       }
      else
      {
        process = Process.Start(this.ExecutableFile, this.Arguments);
       }
      if (this._waitForProcessExit)
      {
        process.WaitForExit();
      }
     }
  }
}

需要特別注意的是在ExecutableJobStep 上,通過ConfigurationElementTypeAttribute指定了與之相匹配的配置類型 (ExecutableJobStepData)。ExecutableJobStep 的三個屬性(executableFile 、arguments和waitForProcessExit)都定義在ExecutableJobStepData。 ExecutableJobStepData集成我們自定義的基類:JobStepData,下面是 JobStepData的定義。JobStepData繼承自NameTypeConfigurationElement(定了 兩個Configuration Property:Name和Type的ConfigurationElement),這是一 個在Enterprise Library廣泛使用的配置類型,因為分別自定義的類型都是通過 它的Type屬性進行配置的。

namespace Artech.BatchJobExecutor.Configuration
{
  public class JobStepData : NameTypeConfigurationElement
  {
     [ConfigurationProperty("description", IsRequired = false, DefaultValue = "")]
    public string Description
    {
      get
      {
         return this["description"] as string;
      }
    }
    public JobStepData()
    {
     }
    public JobStepData(string name, Type type)
       : base(name, type)
    {
    }
  }
}

ExecutableJobStepData直接繼承自JobStepData ,定了3個配 置屬性分別於ExecutableJobStep的三個屬性:executableFile、arguments和 waitForProcessExit,以及參數列表。而以name-value形式定義的參數又定義在 ArgumentEntry中。

namespace Artech.BatchJobExecutor.Configuration
{
  [Assembler (typeof(ExecutableFileJobStepAssmbler))]
  public class ExecutableJobStepData : JobStepData
  {
     [ConfigurationProperty("executableFile", IsRequired = true)]
    public string ExecutableFile
    {
       get
      {
        return this ["executableFile"] as string;
      }
     }
    [ConfigurationProperty("arguments", IsRequired = false)]
    public NamedElementCollection<ArgumentEntry> Arguments
    {
      get
      {
        return this ["arguments"] as NamedElementCollection<ArgumentEntry>;
      }
     }
    [ConfigurationProperty ("waitForProcessExit", IsRequired = false, DefaultValue = true)]
    public bool WaitForProcessExit
    {
      get
      {
        return (bool) this["waitForProcessExit"];
      }
     }
  }
  public class ArgumentEntry : NamedConfigurationElement
  {
     [ConfigurationProperty("value", IsRequired = true)]
     public string Value
    {
      get
       {
        return this["value"] as string;
      }
    }
    public ArgumentEntry()
    { }
    public ArgumentEntry (string name)
      : base(name)
    { }
   }
}

在ExecutableJobStepData上應用了AssemblerAttribute,並 指明了Assembler的類型:ExecutableFileJobStepAssmbler。而通過 ExecutableFileJobStepAssmbler,則可以通過配置創建具體的 ExecutableFileJobStep對象。ExecutableFileJobStepAssmbler實現了接口: IAssembler<JobStep, JobStepData>,在Assemble方法中,通過配置對象 (objectConfiguration)創建ExecutableFileJob對象。由於可執行文件的路徑 (ExecutableFile屬性)可能通過定義的變量定義,所以 BatchJobExecutorSettings.ApplyVariable對變量進行解析。

namespace Artech.BatchJobExecutor.Configuration
{
  public class ExecutableFileJobStepAssmbler : IAssembler<JobStep, JobStepData>
  {
    #region IAssembler<JobStep,JobStepData> Members
    public JobStep Assemble(IBuilderContext context, JobStepData objectConfiguration, IConfigurationSource configurationSource, ConfigurationReflectionCache reflectionCache)
    {
       ExecutableJobStepData jobStepData = objectConfiguration as ExecutableJobStepData;
      string spliter = " ";
      StringBuilder arguments = new StringBuilder ();
      foreach (ArgumentEntry argument in jobStepData.Arguments)
      {
         arguments.Append(BatchJobExecutorSettings.ApplyVariable(argument.Value) + spliter);
      }
      JobStep jobStep = new ExecutableJobStep(BatchJobExecutorSettings.ApplyVariable (jobStepData.ExecutableFile), arguments.ToString().Trim (spliter.ToCharArray()), jobStepData.WaitForProcessExit);
       jobStep.Name = jobStepData.Name;
       jobStep.Description = jobStepData.Description;
      return jobStep;
    }
    #endregion
  }
}

從上面的類圖,我們會發現我們漏掉了一個對象 CustomJobStepData,它繼承自JobStepData類型,並實現了兩個重要的接口: IHelperAssistedCustomConfigurationData<CustomJobStepData>和 ICustomProviderData。要說到具體的作用和實現,可能需要很多的文字才能闡述 清楚,在這裡我們可以把CustomJobStepData看成是能夠實現配置文件中的配置內 容和具體配置類型的適配。

我們有了配置相關的輔助類型,最終需要通過配置來創建與之匹配的對 象,在EL中顯得相對簡單,我們只需要調用 AssemblerBasedObjectFactory<TObject, TConfiguration>類型的Create 方法就可以了。為此我創建了一個特殊的工廠類:JobStepCustomFactory ,用於 創建具體的JobStep。

namespace Artech.BatchJobExecutor
{
  public class JobStepCustomFactory : AssemblerBasedObjectFactory<JobStep, JobStepData>
  {
    public static JobStepCustomFactory Instance = new JobStepCustomFactory();
  }
}

3、整個配置

在一開始,我們就介紹了如果進行批處理的配置,我們現在來看看,該配 置類如何來定義:BatchJobExecutorSettings。

namespace Artech.BatchJobExecutor.Configuration
{
  public class BatchJobExecutorSettings : SerializableConfigurationSection
   {
    [ConfigurationProperty("variables", IsRequired = true)]
    public NamedElementCollection<VariableEntry> Variables
    {
      get
      {
        return this ["variables"] as NamedElementCollection<VariableEntry>;
      }
     }
    [ConfigurationProperty("batchJobs", IsRequired = true)]
    public NamedElementCollection<BatchJobEntry> BatchJobs
    {
      get
      {
        return this ["batchJobs"] as NamedElementCollection<BatchJobEntry>;
      }
     }
    [ConfigurationProperty ("defaultBatchJob", IsRequired = true)]
    public string DefaultBatchJob
    {
      get
       {
        return this["defaultBatchJob"] as string;
      }
    }
    public static BatchJobExecutorSettings GetConfigurationSection()
    {
      return ConfigurationSourceFactory.Create().GetSection ("batchJobExecutor") as BatchJobExecutorSettings;
     }
    private static NamedElementCollection<VariableEntry> variables;
     public static string ApplyVariable(string statement)
    {
      if (variables == null)
      {
         variables = GetConfigurationSection().Variables;
       }
      foreach (VariableEntry variable in variables)
      {
        statement = statement.Replace ("$" + variable.Name + "$", variable.Value);
      }
      return statement;
    }
   }
}

整個Batch Job Executor的配置大體由以下兩個部分 組成:

變量列表:這是一個 NamedElementCollection<VariableEntry>類型,VariableEntry定義如下 ,

namespace Artech.BatchJobExecutor.Configuration
{
  public class VariableEntry : NamedConfigurationElement
  {
    [ConfigurationProperty("value", IsRequired = true)]
    public string Value
    {
       get
      {
        return this ["value"] as string;
      }
    }
    public VariableEntry()
    { }
    public VariableEntry(string name)
      : base(name)
    { }
  }
}
Batch Job列表: NamedElementCollection<BatchJobEntry>類型,BatchJobEntry定義如下 :

namespace Artech.BatchJobExecutor.Configuration
{
  public class BatchJobEntry : NamedConfigurationElement
  {
    [ConfigurationProperty("description", IsRequired = false)]
    public string Description
     {
      get
      {
        return this["description"] as string;
      }
     }
    [ConfigurationProperty("steps", IsRequired = true)]
    public NameTypeConfigurationElementCollection<JobStepData, CustomJobStepData> Activities
    {
      get
      {
        return this["steps"] as NameTypeConfigurationElementCollection<JobStepData, CustomJobStepData>;
      }
    }
  }
}

表示Job Step序列的單個步驟的類型是 NameTypeConfigurationElementCollection<JobStepData, CustomJobStepData>。

除了以上兩個主要成員之外,在根節點上還定 義了默認的Batch Job的名稱,以及輔助方法ApplyVariable用於解析包含變量的 表達式。

4、Batch Job的Batch Job Factory

我們最後還看看 Batch Job的定義和創建,下面的類圖列出來整個BatchJob創建體系的結構:通過 BatchJobFactory創建BatchJob對象,BatchJobFactory最終通過EL的 EnterpriseLibraryFactory實現對象的創建,而BatchJobFactory在進行對象創建 工程中,會根據BatchJob類型指定的實現了ICustomFacotory的具體類型來創建對 象,而我們定義的BatchJobCustomFactory實現了該接口,以及實現真正的對象創 建過程。由於在配置中每個BatchJob都具有一個具體的、唯一的名稱,一般地, 我們通過傳入具體的名稱創建對應的BatchJob。但是如果我們在創建過程中,不 曾傳入BatchJob的名稱,我們希望的是創建默認的BatchJob。EL中通過一個特殊 的接口IConfigurationNameMapper實現了Default Name和具體的Batch Jon Name 的匹配。BatchJobMapper實現了該接口,實現了我們需要的名稱匹配關系。在這 裡我就不一一介紹了,有興趣的朋友可以下載代碼自行研究。

實際上,關 於對象的創建一直是EL關注的問題,也是EL的核心所在。EL的ObjectBuild和 ObjectBuild2就是專門為對象創建而設計的。ObjectBuild和ObjectBuild2是整個 EL的基石,也是Unity、Software Factory的根基所在,涉及的類型比較復雜,非 三言兩語就能概括,有機會的話,我會寫一些關於此方面的內容。

本文配套源碼

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved