commerce starter kit (commerce Starter kit 随书 asp.net2.0典型项目开发 ---应用篇光盘中的一套程序,他改自dashcommerce) 中,有很多自定义提供程序的应用,比方在线支付模块,税率计算模块等,
在网站App_Code/Services/TaxProvider/下面是相关的代码实现
首先是配置节的代码,来自文件:TaxServiceSection.cs
namespace Commerce.Providers
{
public class TaxServiceSection : ConfigurationSection
{
[ConfigurationProperty("providers")]
public ProviderSettingsCollection Providers
{
get { return (ProviderSettingsCollection)base["providers"]; }
}
[StringValidator(MinLength = 1)]
[ConfigurationProperty("defaultProvider",
DefaultValue = "SqlTaxProvider")]
public string DefaultProvider
{
get { return (string)base["defaultProvider"]; }
set { base["defaultProvider"] = value; }
}
}
}
这里声明了TaxServiceSection类,这个类有一个DefaultProvider属性,以及一个Providers集合属性,集合属性使用的.net框架提供的ProviderSettingsCollection类,对于这个类大家应该不陌生,像MemberShip,RoleManager 配置节里的Providers就是对应这个类的,ProviderSettingsCollection 是ProviderSettings的集合类,ProviderSettings 继承于ConfigurationElement, ProviderSetttings 里面定义了一个name ,跟type属性,而Parameters 属性是一个NameValueCollection 类型的,应此如Membership 配置提供程序中(<providers><add name="" type="" .... /></providers>的其他属性如connectionStringName,enablePasswordRetrieval等多是保存在Parameters中的,参考下面的membership节
<membership defaultProvider="AspNetSqlMembershipProvider" userIsOnlineTimeWindow="15" hashAlgorithmType="">
<providers>
<clear/>
<add connectionStringName="LocalSqlServer" enablePasswordRetrieval="false" enablePasswordReset="true" requiresQuestionAndAnswer="true" applicationName="CSK" requiresUniqueEmail="false" passwordFormat="Hashed" maxInvalidPasswordAttempts="5" passwordAttemptWindow="10" passwordStrengthRegularExpression="" minRequiredPasswordLength="4" minRequiredNonalphanumericCharacters="0" name="AspNetSqlMembershipProvider" type="System.Web.Security.SqlMembershipProvider, System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"/>
</providers>
</membership>
TaxServiceSection 在配置文件中对应的声明如下
<section name="TaxService" type="Commerce.Providers.TaxServiceSection" allowDefinition="MachineToApplication" restartOnExternalChanges="true" requirePermission="false"/>
具体的配置如下
<TaxService defaultProvider="StrikeIronTaxProvider">
<providers>
<clear />
<add serviceKey="" name="StrikeIronTaxProvider" type="Commerce.Providers.StrikeIronTaxProvider" />
</providers>
</TaxService>
以上是TaxServiceSection的介绍,TaxServiceSection的作用就是从web.config中读取配置数据,
下面我们来看TaxProvider类,由于各地计算税率的方法不同因此这里把TaxProvider类声明为一个抽象类
public abstract class TaxProvider : ProviderBase ,这个类继承自ProviderBase,ProviderBase是所有使用提供程序模型类的基类.在TaxProvider类中定义了一些属性如ServiceLogin,ServiceKey等,另外重要的是定义的三个抽象方法如下
public abstract decimal GetTaxRate(string zip);
public abstract decimal GetTaxRate(Commerce.Common.USState state);
public abstract DataSet GetTaxTable(Commerce.Common.USState state);
这三个抽象方法在具体的TaxProvider类中被实现,Commerce starter kit 给出了三个TaxProvider类的具体实现,他们是 StrikeIronTaxProvider.cs,FlatRateTaxProvider.cs以及ZeroTaxRateProvider.cs.
以StrikeIronTaxProvider为例, 里面首先是实现了 public override void Initialize(string name, System.Collections.Specialized.NameValueCollection config) 方法,这个方法是必需实现的,因为Provider被实例化后,需要设置一些属性,而这些属性就是保存在web.config相应配置节中<providers>元素中的<add .../>元素中的属性,
下面这段代码摘自StrikeIronTaxprovider.cs中的Initialize方法
public override void Initialize(string name, System.Collections.Specialized.NameValueCollection config)
{
base.Initialize(name, config);
try
{
this.ServiceKey = config["serviceKey"].ToString();
//this.ServicePassword = config["servicePassword"].ToString();
//this.ServiceLogin = config["serviceLogin"].ToString();
}
catch
{
throw new Exception("The Service Key must be set for the StrikeIronTaxProvider to work");
}
}
提供程序模型提供了通过配置来替换具体实现类的编程模型,应此提供程序会通过反射机制来架载,具体实例化的时间是,对于应类被第一次调用时实例化,一般情况下只实例化一次. TaxService.cs文件中定义的TaxService类,封装了TaxProvider类的使用,他对外提供一组静态方法,将具体的TaxProvider类实例保存在静态字段中
private static TaxProvider _provider = null,静太字段对应类的生命周期等同于应用程序的周期(Asp.net 的 Application )
下面是这个类的代码:
public class TaxService
{
#region Provider-specific bits
private static TaxProvider _provider = null;
private static object _lock = new object();
public TaxProvider Provider
{
get { return _provider; }
}
public static TaxProvider Instance
{
get
{
LoadProviders();
return _provider;
}
}
private static void LoadProviders()
{
// Avoid claiming lock if providers are already loaded
if (_provider == null)
{
lock (_lock)
{
// Do this again to make sure _provider is still null
if (_provider == null)
{
// Get a reference to the <TaxServiceSection> section
TaxServiceSection section = (TaxServiceSection)
WebConfigurationManager.GetSection
("TaxService");
// Only want one provider here
_provider = (TaxProvider)ProvidersHelper.InstantiateProvider
(section.Providers[0], typeof(TaxProvider));
if (_provider == null)
throw new ProviderException
("Unable to load default TaxProvider");
}
}
}
}
#endregion
public static decimal CalculateAmountByZIP(string zipCode, decimal subTotal) {
decimal dOut = 0;
try {
decimal dRate = Instance.GetTaxRate(zipCode);
dOut = subTotal * dRate;
} catch(Exception x) {
throw new ApplicationException("Tax calculation failed: " + x.Message, x);
}
return dOut;
}
public static decimal GetUSTaxRate(string zipCode)
{
return Instance.GetTaxRate(zipCode);
}
public static decimal GetUSTaxRate(Commerce.Common.USState state)
{
return Instance.GetTaxRate(state);
}
}
我们注意到LoadProviders()方法中使用双lock语句来保证只实例化一个TaxProvider 的具体类(具体看配置文件),LoadProviders()中首先获取TaxServieceSection 节中的配置数据
// Get a reference to the <TaxServiceSection> section
TaxServiceSection section = (TaxServiceSection)WebConfigurationManager.GetSection("TaxService");
接着是实例化
// Only want one provider here
_provider = (TaxProvider)ProvidersHelper.InstantiateProvider
(section.Providers[0], typeof(TaxProvider));
这里使用ProvidersHelper.InstantiateProvider() 方法
下面是通过Reflector 找到的实现代码
public static class ProvidersHelper
{
// Methods
[AspNetHostingPermission(SecurityAction.Demand, Level=AspNetHostingPermissionLevel.Low)]
public static ProviderBase InstantiateProvider(ProviderSettings providerSettings, Type providerType)
{
ProviderBase base2 = null;
try
{
string str = (providerSettings.Type == null) ? null : providerSettings.Type.Trim();
if (string.IsNullOrEmpty(str))
{
throw new ArgumentException(SR.GetString("Provider_no_type_name"));
}
Type c = ConfigUtil.GetType(str, "type", providerSettings, true, true);
if (!providerType.IsAssignableFrom(c))
{
throw new ArgumentException(SR.GetString("Provider_must_implement_type", new object[] { providerType.ToString() }));
}
base2 = (ProviderBase) HttpRuntime.CreatePublicInstance(c);
NameValueCollection parameters = providerSettings.Parameters;
NameValueCollection config = new NameValueCollection(parameters.Count, StringComparer.Ordinal);
foreach (string str2 in parameters)
{
config[str2] = parameters[str2];
}
base2.Initialize(providerSettings.Name, config);
}
catch (Exception exception)
{
if (exception is ConfigurationException)
{
throw;
}
throw new ConfigurationErrorsException(exception.Message, providerSettings.ElementInformation.Properties["type"].Source, providerSettings.ElementInformation.Properties["type"].LineNumber);
}
return base2;
}
[AspNetHostingPermission(SecurityAction.Demand, Level=AspNetHostingPermissionLevel.Low)]
public static void InstantiateProviders(ProviderSettingsCollection configProviders, ProviderCollection providers, Type providerType)
{
foreach (ProviderSettings settings in configProviders)
{
providers.Add(InstantiateProvider(settings, providerType));
}
}
}
注意红色的代码,这几行代码的功能是通过反射实例化类,并且将ProviderSettings中的Parameters属性复制到config中,
完成后调用ProviderBase的initialize方法, base2.Initialize(providerSettings.Name, config);
//===================
internal static object CreateNonPublicInstance(Type type, object[] args)
{
return Activator.CreateInstance(type, BindingFlags.CreateInstance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance, null, args, null);
}
最终调用的是 Activator.CreateInstance方法