Run Tasks in an ASP.NET Application Domain
Keywords: Task implement, ASP.NET 2.0 Provider Model
Web应用中有很多耗时的操作比如发送email、生成报表、处理上传图片等等,这些操作是不适合放到页面中的。比如回复一个帖子后email通知所有订阅了该帖子更新的用户,如果在回复操作中处理,那么用户回复帖子时大部分的时间就会浪费在发送email上了。在CommunityServer里,类似的耗时的操作都被独立出来,作为独立的任务运行于服务端同一程序域中的一个或多个线程中。本文通过email notification的实现,来具体了解一下这种实现模式。涉及的知识点包括:.NET configuration API, Provider Model, ASP.NET 多线程, 抽象工厂模式等。
描述:CS Email Notifcation 包括两部分生成email和发送email。生成email时,针对每个发送地址生成了一个具体的.NET Mail Mesage对象,序列化后存入数据库。发送时反序列化每个Mail Message对象,调用.NET SMTP API发送。发送email可以作为服务独立出来,而生成email是和具体的业务相关的,不能作为独立的服务。但这也是个耗时的操作,涉及到序列化,有时也会有大量email的生成。CS采用异步事件调用,模仿Http Module,定义全局事件,以plug-in的方式载入module,在module中hook全局事件,在事件中处理具体的操作,如生成email。通过事件,使主程序之外的操作异步的进行。以Provider Model的方式来hook事件,增强了程序的可扩展性,实现了即插即用。
代码:
部分代码下载
先看一下配置文件,CS中并没有使用.net 配置文件(即web.config文件)和标准API,本文将其修改为标准的,并采用ASP.NET 2.0中标准的Provider Model。
<configSections>
<section name="poConfiguration" type="PO.Component.Configuration.POConfigurationSection, PO.Component"/>
</configSections>
<poConfiguration>
<modules>
<add name="EmailNotificationModule" type="PO.Component.Module.EmailNotificationModule, PO.Component" />
</modules>
<providers>
<add name="CommonDataProvider" type="PO.Component.Provider.SqlCommonDataProvider, PO.Component" connectionStringName="ShippingWisePortalConnectionString" />
<add name="EmailTemplateProvider" type="PO.Component.Provider.XmlEmailTemplateProvider, PO.Component" />
<add name="NotificationProvider" type="PO.Component.Provider.EmailNotificationProvider, PO.Component" />
</providers>
<tasks>
<modules>
</modules>
<threads>
<thread minutes="1">
<add name="Emails" type="PO.Component.Task.EmailJob, PO.Component" enabled="true" enableShutDown="false" failureInterval="1" numberOfTries="10" />
</thread>
</threads>
</tasks>
</poConfiguration>
配置文件的操作:
整个自定义节poConifguration
namespace PO.Component.Configuration
{
public class POConfigurationSection : ConfigurationSection
{
[ConfigurationProperty("modules")]
public ProviderSettingsCollection Modules
{
get { return (ProviderSettingsCollection)base["modules"]; }
}
[ConfigurationProperty("providers")]
public ProviderSettingsCollection Providers
{
get { return (ProviderSettingsCollection)base["providers"]; }
}
[ConfigurationProperty("tasks")]
public TasksElement Tasks
{
get { return (TasksElement)base["tasks"]; }
}
}
}
tasks子节
namespace PO.Component.Configuration
{
public sealed class TasksElement : ConfigurationElement
{
[ConfigurationProperty("modules")]
public ProviderSettingsCollection Modules
{
get { return (ProviderSettingsCollection)base["modules"]; }
}
[ConfigurationProperty("threads")]
public ThreadSettingsCollection Threads
{
get { return (ThreadSettingsCollection)base["threads"]; }
}
}
}
tasks子节中的threads节
namespace PO.Component.Configuration
{
[ConfigurationCollection(typeof(TaskSettingsCollection))]
public sealed class ThreadSettingsCollection : ConfigurationElementCollection
{
public TaskSettingsCollection this[int index]
{
get
{
return (TaskSettingsCollection)base.BaseGet(index);
}
set
{
if (base.BaseGet(index) != null)
{
base.BaseRemoveAt(index);
}
this.BaseAdd(index, value);
}
}
public new TaskSettingsCollection this[string key]
{
get
{
return (TaskSettingsCollection)base.BaseGet(key);
}
}
protected override ConfigurationElement CreateNewElement()
{
return new TaskSettingsCollection();
}
protected override object GetElementKey(ConfigurationElement element)
{
return string.Empty;
}
public override ConfigurationElementCollectionType CollectionType
{
get
{
return ConfigurationElementCollectionType.BasicMap;
}
}
protected override string ElementName
{
get
{
return "thread";
}
}
}
}
threads节中的thread节
namespace PO.Component.Configuration
{
[ConfigurationCollection(typeof(ProviderSettings))]
public sealed class TaskSettingsCollection : ConfigurationElementCollection
{
[ConfigurationProperty("minutes", IsRequired = false, DefaultValue = 15)]
public int Minutes
{
get { return (int)base["minutes"]; }
set { base["minutes"] = value; }
}
public ProviderSettings this[int index]
{
get
{
return (ProviderSettings)base.BaseGet(index);
}
set
{
if (base.BaseGet(index) != null)
{
base.BaseRemoveAt(index);
}
this.BaseAdd(index, value);
}
}
public new ProviderSettings this[string key]
{
get
{
return (ProviderSettings)base.BaseGet(key);
}
}
protected override ConfigurationElement CreateNewElement()
{
return new ProviderSettings();
}
protected override object GetElementKey(ConfigurationElement element)
{
return ((ProviderSettings)element).Name;
}
}
}