通常我們使用工廠模式來建立對象都需要知道目标對象所在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);
也許隻有這樣,我們的代碼可讀性才會得以提高--不至于連自己寫的代碼還有不斷的回想及細究。