生成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访问它而不是对其进行硬编码,如下所示:
确保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)。
Column
Datatype
JobID
int identity
JobTitle
varchar(500)
JobInterval
datetime
DateLastJobRan
datetime
DateNextJobStart
datetime
表1 Job表
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参数来调用。
做人要厚道,请注明转自酷网动力(www.ASPCOOL.COM)。

