整合Web和Windows服務——按預定時間間隔運行ASP.NET代碼

來源:互聯網
上載者:User

作者:Andrew Needleman
相關技術:C#、.NET、ASP.NET、Window
難度:★★★★☆
讀者類型:資料庫開發人員、系統結構設計人員

    [摘要]本文討論了如何安排ASP.NET代碼的運行和N層體繫結構設計,介紹了Web服務和Windows服務基礎知識。

    由於Web服務可以與ASP.NET應用程式的其餘部分在相同的應用程式上下文中運行,因此它可以在現有代碼所預期的相同上下文中執行。因為Windows服務可以在Windows啟動時自行啟動,所以我將通過Windows服務來啟動Web服務調用。

    假設您已經用ASP.NET編寫了一個出色的N層應用程式,並且想要擴充它以執行預定任務。例如,每兩個小時向資料庫中的選定使用者寄送電子郵件,或者定期分析ASP.NET緩衝中的資料以進行應用程式健全狀態的監視。您不希望從ASP.NET應用程式中丟棄您的物件模型或者在單獨的排程器和ASP.NET應用程式之間建立太多的依賴項,那麼您如何在避免這一點的同時仍然能夠讓這些應用程式一起工作呢?在基於.NET Framework的應用程式中,經常使用計時器按照預定時間間隔執行活動,因此使用一個計時器似乎是適當的解決方案。您可以從Global.asax中的 Application_Start處理常式中啟動計時器以運行預定任務。遺憾的是,該解決方案在應用程式定義域、進程或系統重新啟動方面還不夠健壯,這是因為必須嚮應用程式發出請求以啟動計時器。ASP.NET是一種只響應HTTP請求的被動編程範型,因此進程或使用者輸入必須調用代碼以便它能夠運行。

    更好的解決方案是使用Web服務(Web Service)提供ASP.NET應用程式的介面,並且產生按照預定時間間隔調用它的Windows服務。這樣,ASP.NET應用程式就不必擁有排程邏輯,並且只需要關心執行那些它已經能夠執行的任務。由於Web服務可以與ASP.NET應用程式的其餘部分在相同的應用程式上下文中運行,因此它可以在現有代碼所預期的相同上下文中執行。因為Windows服務可以在Windows啟動時自行啟動,所以我將通過Windows服務來啟動Web服務調用。因此,即使伺服器重新啟動,應用程式也能夠啟動自身。這一重新啟動功能使Windows服務成為對該任務而言比典型的基於Windows的應用程式更為健壯的解決方案。這也是為什麼Windows服務能用於很多後台進程(例如IIS)的原因。

    本文,我將示範如何做到這一點,同時在排程應用程式和ASP.NET應用程式之間建立最少數量的依賴項。該解決方案涉及到對啟動ASP.NET作業的排程應用程式進行簡化。在排程應用程式中,除了它調用的Web服務端點以外,將不會調用特定於ASP.NET應用程式的邏輯。Windows服務將使用app.config檔案來儲存Web服務的UR 以及Windows服務在對Web服務進行的調用之間應當等待的時間間隔。通過在Windows服務的app.config檔案中儲存這兩個設定,您可以更改它們,而無須重新編譯Windows服務。如果您需要在應用程式調用時更改它的行為,則可以只更改ASP.NET應用程式中的邏輯;但是,您不必更改排程應用程式的代碼。這意味著排程應用程式將與ASP.NET應用程式中的更改隔離。

    注意:該解決方案所基於的前提是——有一些任務只應當在正在啟動並執行ASP.NET應用程式的上下文中執行。如果這不是您任務的要求,則您應當認真考慮直接從 Windows服務中引用ASP.NET應用程式的商務邏輯程式集,並且繞過ASP.NET進程以激發這些任務。

應用程式結構


圖1 計劃

    典型的ASP.NET應用程式是用一系列執行特定功能的獨立層產生的。在我的特定樣本中,我具有資料庫訪問類、商務邏輯類、商務程序類以及作為這些層的進入點的ASP.NET頁(參見圖 1)。

    ASP.NET頁只用來顯示和檢索資料,它們是實際協調所有工作的商務程序類的介面。流程類按照正確的順序調用商務邏輯類,以便完成特定的事務,例如訂購小組件。例如,流程類可以首先調用商務邏輯以檢查庫存,然後訂購小組件,並且最終將庫存減少至適當的水平。

    商務邏輯類決定如何調用資料庫訪問類,並且根據需要處理該結果,以獲得可以用於其他動作的最終結果。例如,商務邏輯可以用來計算包括特定州的稅金在內的總價格。首先,您可能需要使用資料訪問類從資料庫中檢索該州的稅率以及基本價格,然後將它們相乘以尋找每個項的總稅金。資料庫訪問類保持該邏輯,以便串連到資料庫,並且以可供更高層使用的格式(例如,DataSet、DataTable或DataReader)返回結果集。這些類只是從資料庫中檢索資料,並且按照反饋給它們的資訊來更新該資料庫;它們不處理結果。例如,它們可能檢索特定州(美國)的稅率,但是它們不會計算訂單的總稅金。

    Microsoft Data Access Application Building Block通過提供更容易的方式與資料庫和預存程序進行通訊簡化了資料訪問類。例如,您可以對它的SQLHelper對象的FillDataSet方法進行調用,以便使用一行代碼根據預存程序的輸出來填充DataSet。通常,您必須編寫代碼來至少建立DataAdapter和一個命令對象,而這至少需要四行代碼。Data Access Application Block串連到資料庫中的預存程序。該預存程序提供訪問和修改資料庫中的資料所需要的SQL代碼。

嚮應用程式中添加預定作業

    ASP.NET Web服務能夠提供保持任務邏輯的現有ASP.NET應用程式的介面,該介面充當該服務和調用ASP.NET應用程式以採取操作的Windows服務之間的中間人。Windows服務隨後將按照預定時間間隔調用ASP.NET應用程式。


圖2 運行預定作業

    ASP.NET Web服務能夠向您提供保持任務邏輯的現有ASP.NET應用程式的介面。該介面充當該服務和調用ASP.NET應用程式以採取操作的Windows服務之間的中間人。Windows服務隨後將按照預定時間間隔調用ASP.NET應用程式。通過在現有ASP.NET應用程式中產生ASP.NET Web服務,可以在預定作業中重新使用已經為該ASP.NET應用程式建立的業務對象和邏輯。圖2顯示應用程式流程程的詳細資料——從用戶端Windows服務應用程式到在該伺服器上啟動並執行Web服務啟動,始終貫穿於每個預定任務的執行。


圖3 應用程式附加功能

    正如您可以在圖3中看到的那樣,該過程要求對前面描述的標準分層進行一些修改。Windows服務將按照指定的時間間隔喚醒ASP.NET Web服務。然後,ASP.NET Web服務將調用Web應用程式流程程層中的方法,以實際確定應當運行哪些預定作業,並隨後運行它們。在實現了基本解決方案以後,您可以使用用戶端app.config檔案來確定Windows服務調用Web服務的時間間隔。接下來,您可以添加商務程序層所需要的功能,以便遍曆和運行作業。許多N層專家對於流程層的興趣肯定超過了對其餘層的興趣,因此我將最後討論資料庫表、資料庫預存程序、資料存取碼和商務邏輯。最後,從底部(資料庫表層級)到中部(商務邏輯層)嚮應用程式的現有層中添加代碼,以便支援流程層所使用的作業功能。

產生Web服務

    要產生Web服務,請首先向與現有ASP.NET代碼位於相同層中的ASP.NET應用程式中添加JobRun ASP.NET Web服務。確保您的ASP.NET項目具有對商務邏輯、流程和資料訪問項目的引用。接下來,要在JobRun Web服務中建立RunJob Web服務方法,Web服務方法將需要調用流程層的相應函數以運行適當的作業。這意味著RunJob方法可以像下面一樣簡單:

[WebMethod]
public void RunJob()
{
    Flow.JobFlow jf = new Flow.JobFlow();
    jf.RunAllActiveJobs();
}

    使用RunJob函數建立JobFlow類(它位於流程層中)的執行個體並調用它的RunAllActiveJobs函數。JobFlow函數的RunAllActiveJobs完成了協調作業啟動並執行所有實際工作,而RunJob函數只是充當該序列的進入點。請注意,這段代碼無法防止作業同時在一個以上的線程中運行——如果Windows服務過於頻繁地安排任務(速度超過了任務的運行速度),或者如果其他某個應用程式調用了進入點,則可能發生這種情況。如果該方法不是安全執行緒的並且允許多個線程同時調用它,則可能給這些作業的結果帶來問題。例如,如果作業X向Mary Smith發送了一封電子郵件,但是在作業Y查詢資料庫以處理其電子郵件時尚未更新資料庫,則Mary可能收到兩封電子郵件。為了同步對該函數的訪問,我將使用System.Threading命名空間中的Mutex類:

private static Mutex mut = new Mutex(false, "JobSchedulerMutex");

    Mutex支援跨進程同步,因此這可以防止多個進程同時運行——即使涉及到兩個不同的ASP.NET輔助進程。現在,讓我們更改RunJob方法以使用Mutex,從而確保在啟動作業之前沒有其他作業運行。

程式碼片段1 RunJob Web Service:

[WebMethod]
public bool RunJob()
{
    bool ranJob = false;
    mut.WaitOne();
    try
    {
        Flow.JobFlow jf = new Flow.JobFlow();
        jf.RunAllActiveJobs();
        ranJob = true;
    }
    finally
    {
        mut.ReleaseMutex();
    }
    return ranJob;
}

    正如您可以在程式碼片段1中的RunJob函數中看到的那樣,可以調用Mutex的WaitOne函數以使該線程等待,直到它在執行之前成為唯一的線程。然後,調用ReleaseMutex函數以指示您已經執行完只需要在一個線程中啟動並執行代碼。當然,在這裡阻塞可能不是正確的解決方案。您可以選擇在另一個線程已經執行作業時(在此情況下,您可以為WaitOne方法指定短暫的逾時)立即返回,並且在無法獲得互斥鎖時立即從RunJob中返回。將該函數的所有主要操作都放到一個try-finally塊中,以便即使RunAllActiveJobs函數中的意外異常導致RunJob函數退出,也會調用ReleaseMutex。

    您可能希望使用某種形式的身分識別驗證和授權(可能使用Windows安全)來保證 Web服務的安全,以確保必須經過正確的授權才能運行該服務,但是我不打算在本文對此進行詳細討論。

    既然您已經產生了Web服務以便可以從另一個應用程式中調用它,那麼現在就讓我們產生能夠使用它的Windows服務。

產生Windows服務

    首先, 在Visual Studio .NET的另一個執行個體中建立一個新的Windows服務項目,然後將其命名為InvokingASPNetService.cs。通過按以下方式添加Main方法來確保該服務將正確啟動:

public static void Main()
{
    ServiceBase.Run(new InvokingASPNetService());
}

    現在,為下列命名空間添加using語句:

using System.Configuration;
using System.Globalization;

    通過按右鍵InvokingASPNetService.cs的設計表面並選擇“Add Installer”,添加該服務的安裝程式。您應當將所建立的serviceInstaller1 的StartType屬性更改為Automatic,以便Windows服務在Windows啟動時啟動。將serviceInstaller1 的 ServiceName 屬性設定為InvokingASPNetService,以便在服務管理員中適當地命名它,然後將serviceProcessInstaller1 Account 屬性更改為Local Service。

    第三步,建立對InvokingASPNetService Web服務的Web引用,然後將其命名為JobRunWebService。將JobRunWebService URL Behavior屬性更改為Dynamic,以便讓Visual Studio .NET自動用Web引用的URL來擴充app.config。所產生的代理類將在該設定檔中尋找Web服務的URL,從而使您無須重新編譯即可將 Windows服務引導至不同的終結點。

    第四,在Windows服務中建立一個方法,以便在Web服務每次被調用時運行它。該方法如下所示:

private void RunCommands()
{
    JobRunWebService.JobRunInterval objJob =
        new JobRunWebService.JobRunInterval();
    objJob.RunJob();
}

    如您所見,您將聲明Web服務代理,並且就像建立其他任何.NET對象一樣建立它。然後,調用Web服務的RunJob方法,以便在遠程Web伺服器上運行作業。請注意,每個步驟都與使用本地類沒有什麼不同,即使您使用的是Web服務。

    第五,您需要在Windows服務中調用RunCommands函數。您應當基於您希望在遠程伺服器上運行作業的頻率,按照固定的時間間隔調用該方法。使用System.Timers.Timer對象來確保RunCommands函數按照正確的時間間隔運行。Timer的Elapsed事件將使您可以在每個時間間隔流逝之後觸發您指定的任何函數(請注意,時間間隔長度是在Interval屬性中指定的)。您將使用被觸發的函數來調用RunCommands函數,以便可以自動執行該功能。預設情況下,該timer類只在它首次到期時才會觸發事件,因此您需要通過將它的AutoReset屬性設定為true來確保它每次都反覆地重設它本身。您應當在服務等級對其進行聲明,以便該服務的任何函數都可以引用它:

private Timer timer;

    接下來,建立相應的函數以初始化timer並設定它的所有相關值:

private void InitializeTimer()
{
    if (timer == null)
    {
        timer = new Timer();
        timer.AutoReset = true;
        timer.Interval = 60000 * Convert.ToDouble(
            ConfigurationSettings.AppSettings["IntervalMinutes"]);
        timer.Elapsed += new ElapsedEventHandler(timer_Elapsed);
    }
}

    為了能夠在不重新編譯應用程式的情況下更改配置時間間隔,我已經在app.config檔案中儲存了該時間間隔,以便InitializeTimer方法可以使用ConfigurationSettings.AppSettings訪問它而不是對其進行寫入程式碼,如下所示:

<add key="IntervalMinutes" value="5" />

    確保timer在計時器到期時調用timer_Elapsed函數以處理Elapsed事件。timer_Elapsed方法非常簡單,它調用剛剛產生的RunCommands函數,如下所示:

private void timer_Elapsed(object source,System.Timers.ElapsedEventArgs e)
{
    RunCommands();
}

    最後,您必須使用installutil命令安裝Windows服務。最容易的方式是開啟Visual Studio .NET命令提示視窗,導航到該服務的目錄,然後運行installutil工具 + 生產力,並且指定您的程式集作為參數。

擴充流程層以處理預定作業

    一項重要的工作是擴充流程層以處理正在啟動並執行預定作業的需要(假定作業之間的差異足夠大,以至於需要對它們進行編碼而不僅僅是參數化)。這涉及到從資料庫中的下一個啟動時間已經過去的資料庫收集所有作業,並單獨地運行它們。在流程層內部,您將建立一個名為Job的基類以提供作業所共有的全部功能。這包括一個用於初始化和檢索JobID的機制、一個用於運行作業以及在成功運行之後設定資料庫中的下一個啟動並執行公用方法(RunSingleJob),以及一個要針對每個作業進行自訂的可重寫方法(PerformRunJob)。

    流程層還將需要為它執行的每個作業產生特定於作業的類。這些類將從基礎的Job類繼承,並將重寫Job類的PerformRunJob函數以自訂該特定作業的執行。您還會需要一個工廠類(JobFactory)以建立和初始化正確的Job類的JobID。靜態CreateJob函數將根據傳遞到該函數中的JobID來建立適當的作業。最後,流程層將必須能夠確定需要啟動並執行作業、遍曆它們並運行它們。這就是JobFlow類將通過它的RunAllActiveJobs方法提供的功能。

    首先,讓我們在流程層項目中建立Job基類(該類將成為每個作業類的父類)。程式碼片段2顯示Job抽象基類的核心。它允許對其JobID進行初始化和檢索,並且能夠確保在作業成功運行後更新資料庫。JobID在建立之後,它將不會針對給定的作業變更,因此您必須確保在初始化之後設定函數不會更改該值。建立各個Job類的JobFactory類將設定自己的JobID值。

程式碼片段2 Job抽象基類:

protected bool isInitialized = false;
protected int mJobID;
public int JobID
{
    get { return mJobID; }
    set
    {
        if (!isInitialized)
        {
            mJobID = value;
            isInitialized = true;
        }
        else throw new InvalidOperationException("JobID already set.");
    }
}

public void RunSingleJob()
{
    if (isInitialized)
    {
        PerformRunJob();
        RecordJobSuccess();
    }
}

protected abstract void PerformRunJob();

protected void RecordJobSuccess()
{
    JobLogic jl = new JobLogic();
    jl.UpdateJobDone(JobID);
}

    RunSingleJob函數確定該作業的JobID已經初始化,運行作業(PerformRunJob),並且在成功運行之後用RecordJobSuccess方法更新資料庫。isInitialized變數用來確保每個作業在運行之前都使它的JobID得到初始化。PerformRunJob抽象方法由派生的Job類實現,並且保持任務的實際邏輯。在作業的實現(PerformRunJob方法)成功運行之後,基類調用 RecordJobSuccess函數,該函數使用商務邏輯層的JobLogic類的UpdateJobDone方法來記錄它在資料庫中啟動並執行時間以及預定的下一個已耗用時間。稍後,我將建立商務邏輯層的JobLogic類。

    Job類既提供初始化JobID變數的功能,又提供在成功時用下一個已耗用時間更新資料庫的功能。另外,您只須用特定於類的代碼重寫一個函數。這使您可以建立Job類的子類。為此,您需要建立兩個類,以便運行特定類型的作業,並且從Job類繼承以獲得它們的其餘功能。建立一個JobRunTest類和一個JobEmailUsers類,並且確保每個類都從Job類繼承,如下所示:

public class JobRunTests : Job

    現在,按如下方式重寫這兩個類的PerformRunJob方法(使用JobRunTest類作為樣本):

protected override void PerformRunJob()
{
    ///Do RunTest specific logic here
}

    將特定於作業的邏輯放到該方法的內部。負責運行作業並且更新資料庫中下一個已耗用時間的其餘代碼是從Job基類中繼承的。您的作業將組合對現有商務邏輯類的調用,以便運行複雜的過程。既然已經有了樣本作業,那麼讓我們考察一下如何使用JobFactory對象建立這些作業。

    JobFactory類用於建立每個JobID的相應子Job類。JobFactory類在它的靜態 CreateJob函數中採用JobID變數,並且返回適當的Job子類。程式碼片段3顯示JobFactory中的代碼。

程式碼片段3 JobFactory類:

public static Job CreateJob(int currentJobID)
{
    Job myJob;
    switch(currentJobID)
    {
        case 1:
            myJob = new JobEmailUsers();
            break;
        case 2:
            myJob = new JobRunTest();
            break;
        default:
            return null;
    }
    myJob.JobID = currentJobID;
    return myJob;
}

    CreateJob函數採用當前JobID,並且在一個case語句中用它來確定應當返回Job類的哪個子類。然後,初始化當前的JobID並且返回從Job派生的類。既然您具有了Job基類、它的特定於作業的子類以及用來選擇要建立的類的方式,那麼您就可以考察如何使用JobFlow類將這一切組合在一起。

    要建立一個名為JobFlow的類以便收集和執行適當的作業,請添加一個名為“RunAllActiveJobs”的函數以遍曆您需要啟動並執行每個作業,並調用它們各自的RunSingleJob函數。您將需要使用RunAllActiveJobs函數來擷取預定從資料庫中經過業務層、資料訪問層和預存程序啟動並執行作業的列表,然後使用其各自的RunSingleJob函數來運行它們。以下代碼顯示JobFlow類的RunAllActiveJobs方法如何完成這些目標:

JobLogic jl = new JobLogic();
DataSet jobsActiveData = jl.GetAllActiveJobs();
foreach (DataRow jobsActive in jobsActiveData.Tables[0].Rows)
{
    int currentJobID = Convert.ToInt32(jobsActive["JobID"]);
    Job myJob = JobFactory.CreateJob(currentJobID);
    myJob.RunSingleJob();
}

    基本上,您將在資料庫中儲存作業,同時儲存有關它們上次啟動並執行時間以及代碼在連續兩次運行之間應當等待的時間間隔的資訊。然後,通過BusinessLogic層中帶有GetAllActiveJobs方法的JobLogic類來檢索需要啟動並執行作業。每個活動作業的ID都用於獲得Job對象,如前所述,該對象的RunSingleJob方法可以用來執行任務。

作業計時資訊

    確定應當運行哪些預定作業意味著您需要儲存有關它們的基本資料,例如,連續兩次運行之間的時間間隔、它們的上次已耗用時間和它們下一次應當啟動並執行時間。為了完成該工作,請在SQL Server資料庫中建立一個作業表(參見表1)。

表1 Job表

Column Datatype
JobID int identity
JobTitle varchar(500)
JobInterval datetime
DateLastJobRan datetime
DateNextJobStart datetime

    JobID列保持該作業表中每個作業的唯一識別碼。JobTitle列包含作業名稱,以便您可以確定哪個作業正在運行。JobInterval列保持連續兩個作業之間的時間間隔。它們是大於1/1/1900的日期和時間間隔,在作業成功後,應當將其添加到目前時間中,以計算下一個作業應當啟動並執行時間。例如,JobInterval欄位中的值1/2/1901 意味著需要將一年和一天添加到該作業上次啟動並執行時間中。

    DateLastJobRan列包含作業上次啟動並執行日期和時間的datetime值。最後一列 DateNextJobStart包含作業下一次應當啟動並執行時間。儘管該列應當是一個由JobInterval加DateLastJobRan計算結果而得的列,但如果您將該列設定為常規的datetime列,則可更生動地理解應用程式層。

檢索和設定作業計時資訊

    要通過SQL Server資料庫中新的預存程序檢索和設定作業計時資訊,這些預存程序必須找到該資料庫所有需要由該應用程式啟動並執行作業,更新該資料庫中單個作業的資訊以指示它已經運行,並且設定該作業的下一個作業運行日期。每個作業都在該資料庫中具有一個DateNextJobStart列,以指示該作業應當啟動並執行日期和時間。如果當前日期和時間已經超過DateNextJobStart列的日期和時間,則應當在該進程中運行該作業。選擇應該啟動並執行作業的預存程序如下所示:

CREATE PROCEDURE
dbo.Job_SelectJobs_NextJobStartBefore
@DateNextJobRunStartBefore datetime
AS
SELECT * FROM JOB WHERE DateNextJobStart < @DateNextJobRunStartBefore

    該預存程序在Job表中選擇的作業符合以下條件,其DateNextJobStart值早於(小於)@DateNextJobRunStartBefore DateTime參數的值。要查明應當運行哪些作業,只須通過該預存程序的參數傳入當前日期和時間。既然您可以選擇需要啟動並執行作業,那麼您就可以轉而產生該過程以便在作業運行之後更新它們。用單個作業的上一個運行日期和下一個運行日期更新資料庫的預存程序如下所示:

CREATE PROCEDURE dbo.Job_Update_StartEnd_CalcNext
@JobID int,
@DateLastJobRan datetime
AS
UPDATE JOB
SET
DateLastJobRan = @DateLastJobRan,
DateNextJobStart = @DateLastJobRan + JobInterval
WHERE
JobID = @JobID

    該過程用一個新的DateLastJobRan來更新由@JobID標識的作業,並且通過將JobInterval與傳入的@DateLastJobRan相加來計算DateNextJobStart值。該過程只應當在@JobID中引用的作業之後運行,並且應當用等於作業上次啟動並執行日期和時間的@DateLastJobRan參數來調用。

調用作業計時預存程序

    您可以通過添加一個名為JobAccess的新類來擴充資料訪問層,以便調用作業計時預存程序。資料訪問層中函數的作用是將業務層傳遞給該層的參數轉換為預存程序資料庫查詢,並且向業務層返回結果。資料訪問層的函數中的參數將鏡像它們所訪問的預存程序的那些參數,因為它們不對這些值執行任何商務邏輯。您將通過Microsoft Data Application Building Block的SQLHelper類訪問資料庫。該類包含用於簡化資料存取碼的功能,從而使您的代碼更為簡潔,可讀性更高。

    要更改資料訪問層以運行預定作業,請首先向現有資料訪問層中添加JobAccess類,以便保持安排作業所需的函數。接下來,在JobAccess類中建立一個函數,以返回需要通過調用Job_SelectJobs_NextJobStartBefore預存程序啟動並執行作業的DataSet。您還將需要在JobAccess類中建立一個函數以調用Job_Update_StartEnd_CalcNext預存程序,但不返回結果。

    首先,將JobAccess類添加到資料訪問層。然後,編輯JobAccess類以添加下列“using”語句:

using System.Data;
using System.Data.SqlClient;
using Microsoft.ApplicationBlocks.Data;

    現在,讓我們考察一下如何添加SelectJobsBeforeDate函數以檢索需要啟動並執行作業的列表。以下為SQLHelper的ExecuteDataset函數的簽名:

public static DataSet
ExecuteDataset(
string connectionString, string spName,
params object[] parameterValues)

    以下為SelectJobsBeforeDate函數,它使用ExecuteDataset來調用Job_Update_StartEnd_CalcNext預存程序,並且返回結果的DataSet:

public DataSet SelectJobsBeforeDate(DateTime beforeDate)
{
    return SqlHelper.ExecuteDataset(
        ConnectionInfo.connectionString,
        "Job_SelectJobs_NextJobStartBefore, myparams);
        new object[]{new SqlParameter("BeforeDate", beforeDate)});
}

    在作業運行之後,您需要執行相應的預存程序以更新有關作業的狀態資訊。完成該工作的方法UpdateJob將使用SQLHelper類的ExecuteNonQuery方法。以下是它的簽名:

public static int ExecuteNonQuery(
    string connectionString, string spName, params object[]
    parameterValues)

    UpdateJob方法可以按以下方式編寫:

public void UpdateJob(int JobID, DateTime dateLastJobRan)
{
    string connStr = ConnectionInfo.connectionString;
    string spName = "Job_Update_StartEnd_CalcNext";
    SqlParameter myparam1 = new SqlParameter("JobID", JobID);
    SqlParameter myparam2 = new
        SqlParameter("DateLastJobRan",dateLastJobRan);
    object[] myparams = {myparam1, myparam2};
    SqlHelper.ExecuteNonQuery(connStr, spName, myparams);
}

    JobAccess類中的UpdateJob函數應該鏡像傳遞給它所使用的預存程序的參數。因此,UpdateJob函數具有一個JobID參數和一個dateLastJobRan參數,並且它們的資料類型與Job_Update_StartEnd_CalcNext預存程序中的參數相同。使用JobID和dateLastJobRan參數,您可以建立兩個SqlParameters,將它們放到myparams對象數組中,並且使用ExecuteNonQuery函數來執行該預存程序。既然您已經建立了JobAccess類,那麼您需要建立最後一個類層,以便將流程層和資料訪問層串連起來。

處理預定作業

    最後一個必須加以修改以便處理預定作業的層是商務邏輯層,我將其稱為JobLogic。該類將對流程層和資料訪問層之間的變數執行基本邏輯。

    首先,使用下列語句向DataAccess層中添加JobLogic類:

using System.Data;
using ScheduledWebService.DataAccess;

    其次,產生JobLogic類的GetAllActiveJobs函數,以尋找所有仍然需要在目前時間或之前啟動並執行作業,如下所示:

public DataSet GetAllActiveJobs()
{
    JobAccess ja = new JobAccess();
    return ja.SelectJobsBeforeDate(DateTime.Now);
}

    GetAllActiveJobs函數建立JobAccess類的執行個體,並且用當前日期的參數值調用它的SelectJobsBeforeDate。GetAllActiveJobs選取當前日期以傳遞給該函數,因此您可以查明哪些作業被安排在目前時間之前運行。

    最後,建立JobLogic類的UpdateJobDone函數以更新資料庫,以便指示所指定的作業剛剛完成,如下所示:

public void UpdateJobDone(int JobID)
{
    JobAccess ja = new JobAccess();
    ja.UpdateJob(JobID, DateTime.Now);
}

    該函數建立JobAccess類的執行個體並且調用它的UpdateJob方法。它傳遞JobID參數,然後使用dateLastJobRan參數的當前日期。您需要將當前日期和時間傳遞給UpdateJob函數,因為它是作業成功完成的時間。

小結

    通過用自動完成的任務擴充ASP.NET應用程式,可以顯式計劃事件,而不是等待執行代碼的請求。您可以利用這一功能執行多種任務——從運行複雜的計算到定期建立報告並將其發送給經理。這樣的任務可以同時重用現有的邏輯和ASP.NET層中的對象,並且可以減少開發時間和提高可維護性。您還可以擴充該排程器啟動的作業,而無須更改啟動它的Windows服務。

    請注意,對於我在本文中討論的內容,有許多不同的情況。例如,您可以不建立自訂的Windows服務來充當排程器,而是使用某種像Windows工作排程器一樣簡單的工具,它非常可靠,並且實現了本文中討論的大多數功能,而無需建立自訂的Windows服務來充當排程器。總之,.NET Framework已經大大簡化了Windows服務的建立,因此即使您先前已經發現它們非常難以使用,您也應當將它們作為一種選擇而重新加以考慮。類似地,Web服務是應用程式向其他應用程式公開功能的一種很好的方式,並且將繼續在這一方面發揮作用。

作者簡介

    Andrew Needleman是Claricode的一名經營合伙人,這是一家位于波士頓附近的諮詢公司,專門從事在 .NET中設計和開發N層Web應用程式。他已經培訓了數以百計的C#、.NET Framework和Visual Basic .NET領域的開發人員。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.