带 RDLC 处理程序和可自定义处理程序的任务服务器





5.00/5 (1投票)
一个任务处理服务器,实现了 RDLC 集成,并可扩展到自定义处理程序
引言
本项目是一个任务处理服务器,实现了一个处理管道,允许将多个处理程序插入以处理来自任务表(作为请求队列)的任务。所有任务处理程序都实现了通用接口,以便在运行时将其添加到任务处理管道中。
项目中包含了一个简单的 RDLC(Microsoft SQL Client Report)处理程序,这使得任务处理服务器可以像批量报表生成器一样工作。
背景
我发起这个项目是因为用户希望在网站上批量提交耗时长的报表,这样网页就不会因等待完成而锁定。
结果我创建了一个通用的批量处理服务器,其设计可以扩展以处理不同的作业,使用自定义处理程序。基于这种设计,我们将来可以轻松地添加电子邮件、Zip 和任何其他任务处理程序。
使用代码
架构
该服务器是一个控制台程序,可以添加到 Windows 任务计划程序中,作为系统启动的一部分运行。它从任务表中获取任务条目,并使用每个任务设置中配置的处理程序进行处理,处理日志被写入 TaskLog 表。
数据库对象
由于需要 MS SQL 表,我们需要使用下载中提供的脚本 DBSetupForTask.sql 来创建它们。该脚本还创建一个用于任务分配的存储过程。由于任务服务器应用程序在应用程序配置的连接字符串部分没有定义单独的连接字符串,RDLC 报表处理程序实现了共享相同的数据库连接设置。这意味着您需要将脚本应用于您希望报表处理程序从中检索数据的数据库。
任务表
我没有对任务表中存储的任务设置做太多假设。由于设置应由同一任务记录中配置的处理程序解析,我选择使用 XML 数据类型来存储设置,以允许任务请求者和处理程序定义自己的格式。
尽管本项目是由网站项目需求启动的,但对如何插入任务表条目没有做出任何限制。这意味着除了网站请求者之外,任何希望将批处理作业卸载到此任务处理服务器的系统都可以简单地将任务插入到 TaskTable
中。
Column | 类型 | 可空 | 主键/外键 | 注释 |
---|---|---|---|---|
ID | int | 否 | 主键 | 自动生成的任务 ID |
标题 | nvarchar(255) | 否 | 请求者输入的任务标题 | |
SubmittedDT | datetime | 否 | 请求提交日期时间 | |
状态 | nvarchar(20) | 是 | 新任务将为 null 值 | |
StatusDT | datetime | 是 | 状态日期时间 | |
SubmittedUser | nvarchar(50) | 否 | 提交此请求的用户 | |
Handler | nvarchar(255) | 否 | .NET 类型全名(带程序集),用于任务服务器加载正确处理程序 | |
设置 | xml(max) | 是 | XML 格式的处理程序特定设置。请参阅 RDLC 实现以获取完整示例。 | |
ActualOutput | nvarchar(max) | 是 | 由任务处理程序更新的实际输出信息。例如,RDLC 处理程序生成的物理文件。 | |
ModifiedDateTime | datetime | 否 | 记录最后更新日期时间 | |
CreatedDateTime | datetime | 否 | 记录创建日期时间 |
在 Task 表中,Handler 列将存储分配的处理程序(.NET 完全限定类型名)。如果分配了多个处理程序,它们的类型名称将用竖线 | 字符分隔。
Column | 类型 | 可空 | 主键/外键 | 注释 |
---|---|---|---|---|
ID | int | 否 | 主键 | 自动生成的日志 ID |
TaskId | int | 是 | 外键 | 此日志记录引用的任务 ID。如果日志记录未引用任何任务,则可能为 null。 |
LogDateTime | datetime | 否 | 日志条目日期时间 | |
CheckingItem | nvarchar(255) | 否 | 创建此日志记录的模块或函数 | |
EntryType | int | 否 | 4 - 信息,2 - 警告,1 - 错误 | |
Content | nvarchar(max) | 否 | 日志详细信息 | |
AppName | nvarchar(128) | 否 | 连接中的应用程序名称 | |
UserName | nvarchar(255) | 否 | 连接中的用户名 | |
SPID | int | 否 | 连接中的会话 ID | |
HostName | nchar(128) | 否 | 连接中的主机名 |
任务分配存储过程
任务服务器运行为一个多线程进程,通过允许同时处理多个任务来最小化一个任务对另一个任务的阻塞。在这样做时,我们需要避免同一个任务被分配给多个处理程序线程时的竞态条件。我编写了一个存储过程 TaskAllocate
,它使用 UPDATE
语句中的 OUTPUT
子句来获取已分配的任务 ID。使用已分配的任务 ID,它将返回给任务服务器程序以供处理。
CREATE PROCEDURE [dbo].[TaskAllocate]
AS
BEGIN
SET NOCOUNT ON;
declare @cnt int ;
declare @logType_Info int = 4, @logType_Error int = 1 ;
declare @procName nvarchar(128) = ISNULL(OBJECT_NAME(@@PROCID), 'TaskAllocate');
declare @allocTask table (
[Id] [int] NOT NULL
) ;
update top (1) dbo.task set [Status] = 'Allocated' , [StatusDT] = GETDATE()
, ModifiedDateTime = GETDATE()
output Inserted.Id into @allocTask
where [Status] is null ;
insert into dbo.TaskLog ( TaskId, CheckingItem, EntryType, Content)
select Id, @procName , @logType_Info, 'Task allocated to job for processing' from @allocTask
select * from dbo.task where id in (select id from @allocTask ) ;
END
任务服务器程序配置
由于任务服务器程序需要访问 Task
和 TaskLog
表以及分配存储过程,我们需要在 SForce.TaskServer
应用程序配置文件中输入正确的连接字符串。
另外,如下面的配置示例所示,noOfServices
应用程序设置控制要启动的线程数量,以同时处理 Task
表条目。
<configuration>
<configSections>
<section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
</configSections>
<connectionStrings>
<add name="TaskContext" connectionString="data source=MYSERVER;initial catalog=MYDATABASE;integrated security=False;User Id=MYUSER;Password=MYPASSWORD;multipleactiveresultsets=True;Application Name=SF TASK SERVER;" providerName="System.Data.SqlClient" />
</connectionStrings>
<appSettings>
<add key="noOfServices" value="10" />
<add key="commandTimeout" value="1800" />
</appSettings>
</configuration>
Microsoft Report Definition Language 客户端 RDLC 报表处理程序
在输入了上一节中提到的正确应用程序配置后,您可以立即运行服务器程序。
要生成 RDLC 报表,处理程序需要以下三项。您可以在文件 SForce.TaskImplementation.xml
中找到相关的配置。
- 首先是带值的参数。设置
commonReportParameters
指的是通过 SQL 检索的公共参数。 - 其次是待填充的数据集。设置
statementFile
指的是数据加载 SQL 语句文件的路径。 - 第三是使用的报表模板。设置
reportTemples
指的是报表模板的文件夹路径。
<settings>
<setting name="commonReportParameters">
<![CDATA[
if exists(select * from information_schema.tables where table_name = 'SystemSetting' and table_schema = 'dbo')
select SystemCode as [name], SystemValue as [value] from dbo.SystemSetting where SystemCode in ('CompanyName', 'DateFormat', 'TimeFormat', 'AmountFormat','QtyFormat', 'PercentageFormat', 'RatioFormat', 'LocationCode','LocationName', 'CurrencyCode')
]]>
</setting>
<setting name="statementFile" value="D:\VSProjects\Visual Studio 2013\Projects\ReportWeb\Dev\ReportWeb\bin\DataStatement.xml" />
<setting name="reportTemples" value="D:\VSProjects\Visual Studio 2013\Projects\ReportWeb\Dev\ReportWeb\reports" />
<setting name="outfileNamePrependTaskId" value="Y" />
</settings>
参数
RDLC 报表处理程序将参数分为公共参数和报表特定参数组。公共参数是所有报表共享的参数,例如货币格式、日期格式、公司名称等。它们在多个报表中应具有相同的参数名称,因为标准的命名约定是良好的设计实践,可以为我们节省大量麻烦和时间。
从技术上讲,有两种类型的参数:一种用于 RDLC 报表,另一种用于 SQL 数据语句。对于 RDLC 报表的使用,我只是将它们合并为一组参数,RDLC 处理程序会尝试解析报表模板并匹配传入的具有相同名称的参数。
数据语句仅使用报表特定参数,因为公共参数通常从数据库表中加载,数据语句可以直接访问它们。数据检索处理由 DataLoadTaskHandler
执行,该类继承自 RDLC 处理程序。DataLoadTaskHandler
解析 SQL 语句,并尝试在执行数据库操作之前匹配语句参数与传入的具有相同名称的参数。
任何传递给处理程序的参数是否用于数据语句和报表取决于名称匹配,因此参数命名约定在此方面非常重要,否则我们需要在逻辑中添加参数绑定机制。
RDLC 报表使用的公共参数
要传入公共参数值,请在文件 SForce.TaskImplementation.xml
的 commonReportParameters
设置中进行设置。它们使用标准的 SQL 语句定义,结果必须在 name
和 value
列中。我下面的示例展示了如何从一个虚构的数据库表中获取标准参数,您可以编写适合您项目设置的 SQL 语句。
RDLC 报表和数据语句使用的报表特定参数
对于报表特定参数,它们在 Task
表记录的 Settings
列中定义为 XML setting
节点的值。这应该是存储这些值的最合理的地方,因为每个任务条目记录存储的信息仅与将要生成的报表相关。
数据语句参数使用 MS SQL @parameter
约定指定。此外,数据加载器处理程序会尝试解析检索到的 SQL 文本,并将 {parameter}
标记替换为具有相同名称的参数值。这使得 SQL 语句更加动态,因为文本的任何部分都可以无限制地组装,而不仅仅是常规参数部分。
数据加载 SQL 语句 XML 文件
数据语句文件的位置在上面提到的 SForce.TaskImplementation.xml
文件中的 statementFile
设置中配置,下面显示了一个标准命名 SQL 语句条目的示例。
每个数据检索 SQL 语句使用 name
属性和一个 select-statement
节点进行命名。您可以在节点值中放入带有参数的原始 SQL 语句,或者像下面的示例一样,通过调用存储过程来返回数据。
<?xml version="1.0" encoding="utf-8" ?>
<statements>
<select-statement name="FranchiseMemberTypeReport">
<![CDATA[
exec dbo.FranchiseMemberTypeReport @CutoffDate = @CutoffDate, @y_or_m = @y_or_m , @ShopCodes = @shop_List;
]]>
</select-statement>
</statements>
任务表记录条目
要发出任务请求,请求者应用程序只需按照上一节“数据库对象 - 任务表”中描述的格式将条目放入 Task
表,但您还需要在 Settings
列中提供报表设置信息。这是因为只有请求者和处理程序应该知道要传递的设置以及如何解析它们,而不是任务服务器。下面的示例显示了 任务 ID 1
的 RDLC 请求。
ID | 标题 | SubmittedDT | 状态 | StatusDT | SubmittedUser |
---|---|---|---|---|---|
1 | 示例报表 | 16/10/21 17:51 | NULL | 16/10/21 17:51 | PCD |
2 | 我的虚拟任务 | 16/10/21 17:51 | 已分配 | NULL | PCD |
Handler |
---|
SForce.TaskImplementation.RDLCTaskHandler,SForce.TaskImplementation |
SForce.TaskImplementation.DummyHandler,SForce.TaskImplementation |
设置 | ActualOutput | ModifiedDateTime | CreatedDateTime |
---|---|---|---|
<settings> <setting name="ReportId" value="157" /> <setting name="ReportName" value="ReportFranchiseMemberTypeReport" /> <setting name="QueryType" value="Name" /> <setting name="QueryText" value="FranchiseMemberTypeReport" /> <setting name="OutputFormat" value="EXCELOPENXML" /> <setting name="OutputPath" value="D:\temp\Report_.xlsx" /> <setting name="parameters"> <setting name="cutOffDate" value="2016-10-08" type="System.DateTime" /> <setting name="shop_list" value="5103,5046,5069" type="System.String" /> <setting name="y_or_m" value="M" type="System.String" /> </setting> </settings> | NULL | 16/10/21 17:51 | 16/10/21 17:51 |
<settings /> | NULL | 16/10/21 17:51 | 16/10/21 17:51 |
要创建 RDLC 请求,首先,在 Handler
列中输入 RDLC 处理程序的 .NET 完全限定名称 SForce.TaskImplementation.RDLCTaskHandler,SForce.TaskImplementation
。其次,在 Settings
列中输入 RDLC 任务特定的设置。RDLC 请求设置必须带有根节点 settings
,并且每个 setting
指定为具有 name
和 value
属性的 setting
节点。
报表特定参数设置需要作为子节点指定在 Settings
列中一个名为 "parameters" 的 setting
节点下,如以下示例所示。这种 XML 布局有助于指定多个参数。
<settings>
<setting name="ReportId" value="157" />
<setting name="ReportName" value="ReportFranchiseMemberTypeReport" />
<setting name="QueryType" value="Name" />
<setting name="QueryText" value="FranchiseMemberTypeReport" />
<setting name="OutputFormat" value="EXCELOPENXML" />
<setting name="OutputPath" value="D:\VSProjects\Visual Studio 2013\Projects\ReportWeb\Dev\ReportWeb\temp\PCD\ReportFranchiseMemberTypeReport_20161009_112925.xlsx" />
<setting name="parameters">
<setting name="cutOffDate" value="2016-10-08" type="System.DateTime" />
<setting name="shop_list" value="5103,5046,5069" type="System.String" />
<setting name="y_or_m" value="M" type="System.String" />
</setting>
</settings>
设置名称 | 注释 |
---|---|
ReportId | 此项被处理程序忽略。您可以只在此处放入数字 0。我只用它来引用我们 ERP 系统中的报表。 |
ReportName | 报表模板,不带 .rdlc 文件扩展名 |
QueryType | 此处始终填写 Name。表示按名称查找数据加载 SQL 语句。 |
QueryText | 在此处输入 SQL 语句的名称,用于在上一节“数据加载 SQL 语句 XML 文件”中提到的语句文件中查找实际的 SQL 语句。 |
OutputFormat | RDLC 规范支持的报表输出格式。 |
OutputPath | 生成的报表输出路径。实际输出文件可能会附加任务 ID,并在任务表记录的 ActualOutput 列中指定。 |
参数 | 上面描述的报表特定参数。参数作为子节点指定。 |
用于测试的虚拟处理程序
首先,启动 Task Server 控制台程序,您将看到有 10 个线程并行运行,等待 Task
表中输入的新任务。
基本请求者测试
提供了一个 DummyHandler
任务处理程序来测试设置的健康状况(当任务服务器已部署时)。设置脚本包含下面的 SQL 来插入一个基本的虚拟任务请求到 Table
。插入后,您将看到虚拟任务只是回显传入的信息,并会无限循环。要退出,您可以按 X
。
insert into dbo.Task ( Title, SubmittedDT, Status, StatusDT, SubmittedUser, Handler, Settings, ModifiedDateTime, CreatedDateTime)
values ('My Dummy Task', getdate() , null, null, 'PCD', 'SForce.TaskImplementation.DummyHandler,SForce.TaskImplementation', N'<settings />', getdate(), getdate() ) ;
RDLC 请求者测试
设置脚本包含下面的 SQL 来插入一个测试 RDLC 请求到 Task
表。如果您运行 Task Server 并插入此示例请求,您将在 C:\Temp 文件夹中看到从 RDLC 任务处理程序导出的 Excel 文件。
insert into dbo.Task ( Title, SubmittedDT, Status, StatusDT, SubmittedUser, Handler, Settings, ModifiedDateTime, CreatedDateTime)
values ('My Dummy RDLC Task', getdate() , null, null, 'PCD', 'SForce.TaskImplementation.RDLCTaskHandler,SForce.TaskImplementation', N'<settings>
<setting name="ReportId" value="0" />
<setting name="ReportName" value="ReportSample" />
<setting name="QueryType" value="Name" />
<setting name="QueryText" value="Sample" />
<setting name="OutputFormat" value="EXCELOPENXML" />
<setting name="OutputPath" value="C:\temp\Sample.xlsx" />
<setting name="parameters">
<setting name="id" value="20" type="System.Int32" />
<setting name="ReportTitle" value="MY SAMPLE REPORT" type="System.String" />
</setting>
</settings>', getdate(), getdate() ) ;
关注点
TaskManager 类
主要的控制点在 TaskManager
类中,该类从 Task
表检索任务记录,并按顺序调用为该任务配置的处理程序。下图显示了此过程的整体流程。
Microsoft Unity 依赖注入
使用 Microsoft Unity 依赖注入库的父子容器功能,每个线程中分配的任务都运行在自己的子容器中。以下代码摘自 TaskManager
类的 AllocateAndProcess
方法。这有助于更好地管理资源,共享对象运行在 Unity
父容器中,而子容器使用 ContainerControlledLifetimeManager
控制对象,对象的生命周期不超过任务的生命周期。
task = this.taskStore.Allocate();
while (task != null)
{
this.taskManagerLogger.SetTask(task, dummayTask.Handler);
using (var childContainer = this.container.CreateChildContainer())
{
var taskContext = new TaskContext();
childContainer.RegisterInstance(typeof(ILogger), new TaskLogger(this.connectionFactory, task), new ContainerControlledLifetimeManager());
childContainer.RegisterInstance(typeof(TaskContext), taskContext, new ContainerControlledLifetimeManager());
// ... skip lines
}
}
TaskHandler 类
TaskHandler
类是所有处理程序的祖先。要添加一个新的自定义处理程序,您需要继承自这个基类。
查看 TaskHandler
构造函数中的代码,有三个对象实例是通过上面提到的 Unity
DI 传入的。实例 connectionFactory
和 logger
都非常直观,它们为实现提供了数据库连接和日志记录功能。context
实例将用于处理程序之间的信息传递。正如我之前提到的,每个任务可以配置为由多个处理程序处理,其 .NET 名称在 handler
列的值中用 |
分隔,处理程序按顺序处理任务,context 用于每个处理程序将其数据传递给其后续处理程序。
public TaskHandler(IConnectionFactory connectionFactory, ILogger logger, TaskContext context)
{
this.connectionFactory = connectionFactory;
this.logger = logger;
this.context = context;
if (this.context.Items.ContainsKey(GlobalSettings.Context.SignalExit))
this.signalExit = this.context.Items[GlobalSettings.Context.SignalExit] as WaitHandle;
}
TaskContext
类非常简单,它有一个 Items
属性用于存储通用数据,以便每个处理程序可以从中添加或删除数据并将其传递给其后续处理程序。
public class TaskContext
{
public IDictionary<string, object=""> Items { get; private set; }
public TaskContext()
{
this.Items = new Dictionary<string, object="">(StringComparer.InvariantCultureIgnoreCase);
}
}
TaskHandler
的核心是 Process
方法,所有任务处理都应该放在其中。这意味着每个自定义处理程序至少需要重写 Process
方法,调用 base.Process(task)
进行初始化,然后在之后插入代码。
virtual public bool Process(Task task)
{
if (this.ReceivedSignalExit(0))
{
this.logger.WriteLog(string.Format("Task id {0} for {1} received cancel request and exit!", task.Id, task.Title), LogType.Warning);
return false; // Stop
}
this.taskSettings = task != null ? this.ParseTaskSettings(task.Settings) : new Dictionary<string, string="">();
return true; // Continue
}
此外,如果您详细查看上面的代码,Process
方法最后返回 True
或 False
。它有助于通知任务服务器是否应继续调用下一个处理程序来处理此特定任务。
此外,在 context 的 Items Dictionary
中放置了一个 WaitHandle
。这将信号处理器停止,信号由任务服务器在收到取消请求后发送。
任务分配和取消监控
任务分配
任务分配和取消监控是任务服务器的核心功能,需要在此处进行详细讨论。由于新任务由请求系统直接插入到 Task
表中,我们可以通过轮询或使用 SQLDependency
(.NET 类,用于 Microsoft Query Notification Service
)来找到它们。
查看下面从 TaskManager
中提取的代码,由 Unity DI
解析的 dbListener
类决定实际使用的实现。实际上,我使用 DbPoller
类提供的轮询,设置间隔为 5 秒。
public TaskManager(..., IListener dbListener,...)
{
this.taskStore = taskStore;
}
public void ProcessTasks()
{
this.dbListener.Monitor(this.monitorCommand);
this.dbListener.Changed += DbListener_Changed;
}
private void DbListener_Changed(object sender, EventArgs e)
{
AllocateAndProcess();
}
private void AllocateAndProcess()
{
task = this.taskStore.Allocate();
while (task != null)
{
// ... skip codes for task Handling
//
task = null; // Set to null before next allocation
task = this.taskStore.Allocate(); // Try allocate next task
}
}
取消监控
在任务被分配并由处理程序处理后,我们需要随时准备请求者发送任务的取消请求。在此处使用轮询是不合适的,因为取消必须立即响应。
使用 SQLDependency
实现的 DbListener
类监控 Task
表的取消请求,然后在收到任务取消通知后引发 Changed
事件。
在分配新任务后,TaskManager
在调用第一个处理程序之前,使用下面的 PrepareExitNotification
方法开始处理。此方法通过订阅 DbListener cancelNotification
实例的 Changed
事件来准备取消信号,使用监控 SQL 语句 - *select Status from dbo.Task where Status='ReqCancel'*。这意味着请求者可以在任务更新为 Allocated
或 Processing
状态后,将状态更新为 ReqCancel
- Request Cancel。请注意,处理程序依赖于 TaskContext
中包含的 ManualResetEvent
进行取消信号。
public TaskManager(..., [Dependency("NotificationService")] IListener cancelNotification, ...)
{
// Skip lines
//...
this.cancelNotification = cancelNotification;
string monitorText = string.Format("select Status from dbo.Task where Status='{0}'", TaskStatus.ReqCancel);
this.cancelNotification.Monitor(monitorText);
}
private WaitHandle PrepareExitNotification(Task task, TaskContext taskContext)
{
ManualResetEvent exitEvent = new ManualResetEvent(false);
taskContext.Items[GlobalSettings.Context.SignalExit] = exitEvent;
// Subscribe for request canceling notification
this.cancelNotification.Changed += delegate (object sender, EventArgs e)
{
task = this.taskStore.GetTask(task.Id);
if (TaskStatus.ReqCancel.Equals(task.Status))
exitEvent.Set();
};
return exitEvent;
}
如果任务可以在完成前被取消,状态将更新为 Canceled
,并在控制台上显示消息,如下面的示例所示。
具有多个 TaskManagers 的宿主进程(在独立线程中)
正如我们所预期的,批处理任务请求通常是耗时长的作业,并且 TaskManager
是单线程运行并按顺序处理处理程序,因此更适合在独立的线程中启动多个 TaskManagers
,以便可以并行处理多个任务。
宿主进程是一个控制台程序,它启动多个线程并初始化主 Unity
容器来解析所有线程的共享实例。实际上,每个具有独立 TaskManager
的线程都有一个独立的 Unity
子容器,如下面的代码所示。
由于 TaskServer
是在单独的项目 SForce.TaskServerImplementation
中实现的,我预计开发 Windows 服务宿主而不是控制台宿主的工作量不会太大。
class Program
{
static void Main(string[] args)
{
// ...
for (int i = 0; i < taskManagerServices.Length; ++i)
{
taskManagerServices[i] = new System.Threading.Tasks.Task(delegate () {
using (var container = mainContainer.CreateChildContainer())
{
var mgr = container.Resolve<taskmanager>();
mgr.ProcessTasks();
signal.WaitOne();
mgr.Terminate();
}
});
}
// ...
}
}
CleanupManager 类
清理工作委托给 CleanupManager
。它删除历史记录和相关的物理输出文件(如果有)。
安全注意事项
此应用程序应在受控环境中运行,这意味着我们需要限制对数据库表的访问权限,并且不对配置文件进行未经授权的访问。这是因为黑客篡改数据语句文件中的 SQL 语句等配置会带来灾难性的后果。
数据库访问限制
一种限制数据库访问的方法是创建一个数据库用户,该用户只有更新 Task
和 TaskLog
表、执行 TaskAllocate
存储过程的权限,以及对选定表的只读权限。然后,在任务服务器连接字符串中使用创建的用户。
安全处理程序
您可以选择实现自定义安全处理程序,并将其类型名称放在配置文件 SForce.TaskServerImplementation.xml
中的 systemHandlers
设置中,如下面的示例所示
此外,下载中包含了一个示例处理程序 SecurityHandler.cs
,用于开始您的自定义安全检查。列在 systemHandlers
设置中的处理程序对每个已分配的任务运行。因此,这是放置安全处理程序以过滤任何未经授权访问的最佳位置。
<settings>
<setting name="pollingInterval" value="5000" />
<setting name="daysToKeepTask" value="180" />
<setting name="systemHandlers" value="SForce.TaskImplementation.SecurityHandler,SForce.TaskImplementation" />
</settings>
public class SecurityHandler : TaskHandler
{
private ILogger hostLogger;
public SecurityHandler(IConnectionFactory connectionFactory, ILogger logger, TaskContext context, [Dependency("host")] ILogger hostLogger)
:base(connectionFactory, logger, context)
{
this.hostLogger = hostLogger;
}
public override bool Process(Task task)
{
this.hostLogger.WriteLog(string.Format("{0} {1} processes {2}.", DateTime.Now, this.GetType().Name, task), LogType.Information);
return base.Process(task);
}
}
历史
- 2016-10-23 版本 1 - 项目发布到 CodeProject。