天天看點

DNN中怎麼使用反射建立資料通路層Provider

通常我們使用工廠模式來建立對象都需要知道目标對象所在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);
           

也許隻有這樣,我們的代碼可讀性才會得以提高--不至于連自己寫的代碼還有不斷的回想及細究。

繼續閱讀