天天看点

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);
           

也许只有这样,我们的代码可读性才会得以提高--不至于连自己写的代码还有不断的回想及细究。

继续阅读