通常我们使用工厂模式来创建对象都需要知道目标对象所在DLL文件名以及包含命名空间的类名。以下为其中比较普遍的创建方式:
Activator.CreateInstance("YourAssemblyName", "YourTypeName");
那DNN是怎么做的呢?
对于DNN来说它自身封装了一套反射的函数用于对它的Provider模式进行支撑。但是今天回看之前写的一个数据访问层的SqlDataProvider时,发现写的代码怪别扭的。
一下是我用于创建自己的SqlDataProvider的代码片段。
objProvider = (DataProvider)Reflection.CreateObject("data", "MyCompany.News.SqlDataProvider", "MyCompany.News");
其中Reflection.CreatObject的定义如下:
/// -----------------------------------------------------------------------------
/// <summary>
/// Creates an object
/// </summary>
/// <param name="ObjectProviderType">The type of Object to create (data/navigation)</param>
/// <param name="ObjectNamespace">The namespace of the object to create.</param>
/// <param name="ObjectAssemblyName">The assembly of the object to create.</param>
/// <returns>The created Object</returns>
/// <remarks>Overload for creating an object from a Provider including NameSpace and
/// AssemblyName ( this allows derived providers to share the same config )</remarks>
/// <history>
/// [cnurse] 10/13/2005 Documented
/// </history>
/// -----------------------------------------------------------------------------
public static object CreateObject(string ObjectProviderType, string ObjectNamespace, string ObjectAssemblyName)
但是我的代码的实际结构却跟函数说明对不上号,但是运行起来没有什么问题。那又是怎么回事呢?
我我创建的对象名称空间确实为:MyCompany.News.SqlDataProvider,但是我的数据访问层代码所在dll应该是MyCompany.News.SqlDataProvider。而且我的数据层访问类名也SqlDataProvider,所以我要创建的对象的类的全名应该是MyCompany.News.SqlDataProvider.SqlDataProvider。但是我在一开始的代码中似乎没有告诉函数我的数据访问层类名,那DNN是怎么做的呢?
仔细翻看了DNN的源码,原来是我对它提供的反射机制理解的不对。其实所有的DNN反射方法调用都会调用这个重载:
/// -----------------------------------------------------------------------------
/// <summary>
/// Creates an object
/// </summary>
/// <param name="ObjectProviderType">The type of Object to create (data/navigation)</param>
/// <param name="ObjectProviderName">The name of the Provider</param>
/// <param name="ObjectNamespace">The namespace of the object to create.</param>
/// <param name="ObjectAssemblyName">The assembly of the object to create.</param>
/// <param name="UseCache">Caching switch</param>
/// <param name="fixAssemblyName">Whether append provider name as part of the assembly name.</param>
/// <returns>The created Object</returns>
/// <remarks>Overload for creating an object from a Provider including NameSpace,
/// AssemblyName and ProviderName</remarks>
/// <history>
/// [benz] 2/16/2012 Created
/// </history>
/// -----------------------------------------------------------------------------
public static object CreateObject(string ObjectProviderType, string ObjectProviderName, string ObjectNamespace, string ObjectAssemblyName, bool UseCache, bool fixAssemblyName)
{
string TypeName = "";
//get the provider configuration based on the type
ProviderConfiguration objProviderConfiguration = ProviderConfiguration.GetProviderConfiguration(ObjectProviderType);
if (!String.IsNullOrEmpty(ObjectNamespace) && !String.IsNullOrEmpty(ObjectAssemblyName))
{
//if both the Namespace and AssemblyName are provided then we will construct an "assembly qualified typename" - ie. "NameSpace.ClassName, AssemblyName"
if (String.IsNullOrEmpty(ObjectProviderName))
{
//dynamically create the typename from the constants ( this enables private assemblies to share the same configuration as the base provider )
TypeName = ObjectNamespace + "." + objProviderConfiguration.DefaultProvider + ", " + ObjectAssemblyName + (fixAssemblyName ? "." + objProviderConfiguration.DefaultProvider : string.Empty);
}
else
{
//dynamically create the typename from the constants ( this enables private assemblies to share the same configuration as the base provider )
TypeName = ObjectNamespace + "." + ObjectProviderName + ", " + ObjectAssemblyName + (fixAssemblyName ? "." + ObjectProviderName : string.Empty);
}
}
else
{
//if only the Namespace is provided then we will construct an "full typename" - ie. "NameSpace.ClassName"
if (!String.IsNullOrEmpty(ObjectNamespace))
{
if (String.IsNullOrEmpty(ObjectProviderName))
{
//dynamically create the typename from the constants ( this enables private assemblies to share the same configuration as the base provider )
TypeName = ObjectNamespace + "." + objProviderConfiguration.DefaultProvider;
}
else
{
//dynamically create the typename from the constants ( this enables private assemblies to share the same configuration as the base provider )
TypeName = ObjectNamespace + "." + ObjectProviderName;
}
}
else
{
//if neither Namespace or AssemblyName are provided then we will get the typename from the default provider
if (String.IsNullOrEmpty(ObjectProviderName))
{
//get the typename of the default Provider from web.config
TypeName = ((Provider) objProviderConfiguration.Providers[objProviderConfiguration.DefaultProvider]).Type;
}
else
{
//get the typename of the specified ProviderName from web.config
TypeName = ((Provider) objProviderConfiguration.Providers[ObjectProviderName]).Type;
}
}
}
return CreateObject(TypeName, TypeName, UseCache);
}
最开始我们的调用方式等同于成如下代码:
objProvider = (DataProvider)Reflection.CreateObject("data","", "MyCompany.News.SqlDataProvider", "MyCompany.News",true,true);
这里类名是空的,那类名从哪里来的呢? 从上述方法实现中,我们可以知道DNN先根据ObjectProviderType(这里就是data)在配置文件中读取相关设置。
在Web.config中我的data的配置节为:
<data defaultProvider="SqlDataProvider">
<providers>
<clear />
<add name="SqlDataProvider" type="DotNetNuke.Data.SqlDataProvider, DotNetNuke" connectionStringName="SiteSqlServer" upgradeConnectionString="" providerPath="~\Providers\DataProviders\SqlDataProvider\" objectQualifier="dnn_" databaseOwner="dbo" />
</providers>
</data>
如果方法传进来的ObjectProviderName为空,那么DNN就将读取defaultProvider做为它的值(在这就是SqlDataProvider)。
那DLL名称是怎么回事呢?明明我的DLL是MyCompany.News.SqlDataProvider,为什么传给函数的却是MyCompany.News?
答案还是在代码中,虽然我传的是MyCompany.News但是函数对应的参数fixAssemblyName默认为true,代码将根据这个参数动态的拼装出Dotnet需要的TypeName,
TypeName = ObjectNamespace + "." + ObjectProviderName + ", " + ObjectAssemblyName + (fixAssemblyName ? "." + ObjectProviderName : string.Empty);
至此我对我所遇到的问题也都有了合理的解释,相比我在写代码的时候应该是对DNN反射有所了解的不然也不至于写出能运行起来的代码。但是由于其与标准Dotnet动态创建对象上语义的不一致,我想我们在DNN使用反射创建自己的Provider时候就直接进行如下调用:
objProvider = (DataProvider)Reflection.CreateObject("data", "SqlDataProvider", "MyCompany.News.SqlDataProvider", "MyCompany.News.SqlDataProvider", true, false);
也许只有这样,我们的代码可读性才会得以提高--不至于连自己写的代码还有不断的回想及细究。