写代码的人

Posts tagged ‘asp.net’

通过自定义配置实现插件式设计

软件设计有一句话叫做“约定优于配置”,很多人将其作为拒绝配置的理由。但是,“约定”和“配置”的使用,都有个度的问题。我不赞为了所谓的扩展性,为你的应用设计一套只有你自己才能看懂的配置体系。但是,在很多场景中,配置是提供应用灵活度的首要甚至是唯一途径。对于框架的设计者来说,对于配置的驾驭是一项基本的技能。

可能你很少使用自定义配置,可能你理解的自定义配置仅仅限于AppSetting,不过我想你应该对于System.Configuration这个命名空间下的几个基本的类型有基本的了解。比如ConfigurationSectionConfigurationElementConfigurationElementCollection等。本篇文章不会介绍关于System.Configuration的基础知识,而是通过一个简单的例子为你讲述一些所谓“高级”的知识点,比如“不可识别配置元素的动态解析”。(源代码从这里下载)

目录
一、通过自定义配置实现的最终效果
二、相关配置类型的定义
三、两个重要的类型:NameTypeConfigurationElement和NameTypeConfigurationElementCollection<T>
四、ResourceProviderFactory的定义
五、补充

一、通过自定义配置实现的最终效果

为了让大家对自定义配置的作用有一个深刻的映像,我们先来给出一个简单的例子。我们采用在《.NET的资源并不限于.resx文件,你可以采用任意存储形式》中介绍的关于自定义ResourceManager以实现对多种资源存储形式的支持。现在只关注与资源的读取,我们将基于不同存储形式的资源读取操作实现在相应的ResourceProovider中,它们实现如下一个简单的IResourceProvider接口。

   1: public interface IResourceProvider
   2: {
   3:     object GetObject(string key);
   4: }

然后我们创建两个具体的ResourceProvider:DbResourceProviderXmlResourceProvider,它们分别基于数据库表和XML文件的资源存储形式。DbResourceProvider需要连接数据库,需要引用配置的连接字符串,所以有一个ConnectionStringName属性;而XmlResourceProvider需要访问具体的XML文件,FileName属性表示文件路径。

   1: [ConfigurationElementType(typeof(DbResourceProviderConfigurationElement))]
   2: public class DbResourceProvider : IResourceProvider
   3: {
   4:     public string ConnnectionStringName { get; private set; }
   5:     public DbResourceProvider(string connectionStringName)
   6:     {
   7:         this.ConnnectionStringName = connectionStringName;
   8:     }
   9:     public object GetObject(string key)
  10:     {
  11:         throw new NotImplementedException();
  12:     }
  13:     public override string ToString()
  14:     {
  15:         return string.Format("{0}\n\tConncectionString Name:{1}", typeof(DbResourceProvider).FullName, this.ConnnectionStringName);
  16:     }
  17: }
  18:  
  19: [ConfigurationElementType(typeof(XmlResourceProviderConfigurationElement))]
  20: public class XmlResourceProvider : IResourceProvider
  21: {
  22:     public string FileName { get; private set; }
  23:     public XmlResourceProvider(string fileName)
  24:     {
  25:         this.FileName = fileName;
  26:     }
  27:     public object GetObject(string key)
  28:     {
  29:         throw new NotImplementedException();
  30:     }
  31:     public override string ToString()
  32:     {
  33:         return string.Format("{0}\n\tFile Name:{1}", typeof(XmlResourceProvider).FullName, this.FileName);
  34:     }
  35: }

具体使用哪个ResourceProvider,通过配置来决定。整个配置定义在<artech.resources>配置节中,该配置节具有一个<providers>子节点,它定义了一系列ResourceProvider的列表。每个ResourceProvider配置具有两个相同的属性:Name和Type,以及一些自己专属的配置属性(比如DbResourceProvider的connectionStringName,XmlResourceProvider的fileName)。至于默认采用哪个Provider,则通过配置节的defaultProvider属性来决定。在本例中,我们默认采用的是DbProvider。

   1: <?xml version="1.0" encoding="utf-8" ?>
   2: <configuration>
   3:   <configSections>
   4:     <section name="artech.resources" type="Artech.Resources.Configuration.ResourceSettings,Artech.CustomConfiguration"/>
   5:   </configSections>
   6:   <artech.resources defaultProvider="DbProvider">
   7:     <providers>
   8:       <add name="DbProvider" type="Artech.Resources.DbResourceProvider, Artech.CustomConfiguration" connectionStringName="LocalSqlServer"/>
   9:       <add name="XmlProvider" type="Artech.Resources.XmlResourceProvider, Artech.CustomConfiguration" fileName="C:\resources.xml"/>
  10:     </providers>
  11:   </artech.resources>
  12: </configuration>

现在我们有一个ResourceProviderFactory的工厂类来帮助我们根据配置创建默认的ResourceProvider,或者创建指定名称的ResourceProvider。现在我们按照如下的方式使用ResourceProviderFactory。

   1: static void Main(string[] args)
   2: {
   3:     IResourceProvider resourceProvider = ResourceProviderFactory.GetResourceProvider();
   4:     Console.WriteLine(resourceProvider);
   5:     Console.WriteLine();
   6:  
   7:     resourceProvider = ResourceProviderFactory.GetResourceProvider("XmlProvider");
   8:     Console.WriteLine(resourceProvider);
   9:     Console.WriteLine();
  10:  
  11:     resourceProvider = ResourceProviderFactory.GetResourceProvider("DbProvider");
  12:     Console.WriteLine(resourceProvider);
  13:     Console.WriteLine();
  14: }

输出结果:

   1: Artech.Resources.DbResourceProvider
   2:         ConncectionString Name:LocalSqlServer
   3:  
   4: Artech.Resources.XmlResourceProvider
   5:         File Name:C:\resources.xml
   6:  
   7: Artech.Resources.DbResourceProvider
   8:         ConncectionString Name:LocalSqlServer

接下来我们就来介绍整个配置体系,以及ResourceProviderFactory的实现。

二、相关配置类型的定义

我们现在来看看与配置相关的类型的定义。整个配置节定义在如下一个ResourceSettings的类中,它直接继承自ConfigurationSection。ResourceSettings具有两个配置属性:DefaultProvider和Providers,分别代表<artech.resources>的defaultProvider属性和<providers>子节点。

   1: public class ResourceSettings: ConfigurationSection
   2: {
   3:     [ConfigurationProperty("defaultProvider", IsRequired = true)]
   4:     public string DefaultProvider
   5:     {
   6:         get{return (string)this["defaultProvider"];}
   7:         set{this["defaultProvider"] = value;}
   8:     }
   9:     [ConfigurationProperty("providers", IsRequired = true)]
  10:     public NameTypeElementCollection<ResourceProviderConfigurationElement> Providers
  11:     {
  12:         get{return (NameTypeElementCollection<ResourceProviderConfigurationElement>)this["providers"];}
  13:         set{this["providers"] = value;}
  14:     }
  15:     public static ResourceSettings GetConfiguration()
  16:     {
  17:         return (ResourceSettings)ConfigurationManager.GetSection("artech.resources");
  18:     }
  19: }

属性Providers是一个名称为NameTypeElementCollection<T>的泛型类型。从名称我们不难看出,这是一个集合类型,代表配置的ResourceProvider集合。而基于ResourceProvider的配置定义在如下一个ResourceProviderConfigurationElement抽象类中。该类继承自我们自定义的NameTypeConfigurationElement类型,具有一个CreateProvider抽象方法用于创建相应的ResourceProvider。

   1: public abstract class ResourceProviderConfigurationElement: NameTypeConfigurationElement
   2: {
   3:     public abstract IResourceProvider CreateProvider();
   4: }

DbResourceProvider和XmlResourceProvider具有各自的ResourceProviderConfigurationElement,分别为DbResourceProviderConfigurationElementXmlResourceProviderConfigurationElement

   1: public class DbResourceProviderConfigurationElement : ResourceProviderConfigurationElement
   2: {
   3:     [ConfigurationProperty("connectionStringName", IsRequired = true)]
   4:     public string ConnectionStringName
   5:     {
   6:         get{return (string)this["connectionStringName"];}
   7:         set{this["connectionStringName"] = value;}
   8:     }
   9:     public override IResourceProvider CreateProvider()
  10:     {
  11:         return new DbResourceProvider(this.ConnectionStringName);
  12:     }
  13: }
  14:  
  15: public class XmlResourceProviderConfigurationElement : ResourceProviderConfigurationElement
  16: {
  17:     [ConfigurationProperty("fileName", IsRequired = true)]
  18:     public string FileName
  19:     {
  20:         get{return (string)this["fileName"];}
  21:         set{this["fileName"] = value;}
  22:     }
  23:     public override IResourceProvider CreateProvider()
  24:     {
  25:         return new XmlResourceProvider(this.FileName);
  26:     }
  27: }

 

三、两个重要的类型:NameTypeConfigurationElement和NameTypeConfigurationElementCollection<T>

接下来介绍两个重要的类型,第一个是ResourceProviderConfigurationElement的基类:NameTypeConfigurationElement。顾名思义,NameTypeConfigurationElement就是具有两个基本配置属性Name和Type的配置元素(ConfigurationElement),其定义如下。方法DeserializeElement定义出来用于解决非识别配置项的反序列化问题。

   1: public class NameTypeConfigurationElement : ConfigurationElement
   2: {    
   3:     [ConfigurationProperty("name", IsRequired = true, IsKey = true)]
   4:     public string Name
   5:     {
   6:         get{return (string)this["name"];}
   7:         set{this["name"] = value;}
   8:     }
   9:     [ConfigurationProperty("type", IsRequired = true)]
  10:     public string TypeName
  11:     {
  12:         get{return (string)this["type"];}
  13:         set{this["type"] = value;}
  14:     }
  15:     public Type Type
  16:     {
  17:         get{return Type.GetType(this.TypeName);}
  18:     }
  19:     public void DeserializeElement(XmlReader reader)
  20:     {
  21:         base.DeserializeElement(reader, false);
  22:     }
  23: }

另一个类型就是NameTypeConfigurationElement的配置元素集合(ConfigurationElementCollection):NameTypeElementCollection<T>。应该说它是整个配置体系的核心,其全部定义如下所示。

   1: public class NameTypeElementCollection<T> : ConfigurationElementCollection where T : NameTypeConfigurationElement
   2: {
   3:     protected override ConfigurationElement CreateNewElement()
   4:     {
   5:         return Activator.CreateInstance<T>();
   6:     }
   7:     protected override object GetElementKey(ConfigurationElement element)
   8:     {
   9:         return (element as NameTypeConfigurationElement).Name;
  10:     }
  11:     protected virtual Type RetrieveConfigurationElementType(XmlReader reader)
  12:     {
  13:         Type configurationElementType = null;
  14:         if (reader.AttributeCount > 0)
  15:         {
  16:             for (bool go = reader.MoveToFirstAttribute(); go; go = reader.MoveToNextAttribute())
  17:             {
  18:                 if ("type".Equals(reader.Name))
  19:                 {
  20:                     Type providerType = Type.GetType(reader.Value, false);
  21:                     Attribute attribute = Attribute.GetCustomAttribute(providerType, typeof(ConfigurationElementTypeAttribute));
  22:                     if (attribute == null)
  23:                     {
  24:                         throw new ConfigurationErrorsException("No ConfigurationElementTypeAttribute is applied.");
  25:                     }
  26:                     configurationElementType = ((ConfigurationElementTypeAttribute)attribute).ConfigurationElementType;
  27:                     break;
  28:                 }
  29:             }
  30:             reader.MoveToElement();
  31:         }
  32:         return configurationElementType;
  33:     }
  34:     protected override bool OnDeserializeUnrecognizedElement(string elementName, XmlReader reader)
  35:     {
  36:         if (base.AddElementName.Equals(elementName))
  37:         {
  38:             Type configurationElementType = this.RetrieveConfigurationElementType(reader);
  39:             var currentElement = (T)Activator.CreateInstance(configurationElementType);
  40:             currentElement.DeserializeElement(reader);
  41:             base.BaseAdd(currentElement, true);
  42:             return true;
  43:         }
  44:         return base.OnDeserializeUnrecognizedElement(elementName, reader);
  45:     }
  46:     public T GetConfigurationElement(string name)
  47:     {
  48:         return  (T)this.BaseGet(name);           
  49:     }
  50: }

对于配置我们应该有这样的认识:我们通过相应的类型来定义配置文件中的某个XML元素,在进行读取的时候实际上就是一个反序列化的工作。而对于成功进行序列化和反序列化,其根本前提是确定目标类型,因为类型描述了元数据。带着这个结论再来看看我们的以XML表示的配置和ResourceSettings的定义,我们会发现一个问题:ResourceSetting的Providers属性的类型是NameTypeElementCollection<ResourceProviderConfigurationElement>,配置元素类型ResourceProviderConfigurationElement是一个抽象类型。而我们需要将具体的ResourceProvider配置反序列化成DbResourceProviderConfigurationElement和XmlResourceProviderConfigurationElement,而整个配置系统似乎找不到这个两个类型的影子。如果不能预先确定配置元素需要反序列化成的真实类型,整个配置的读取将会失败。具体来说,它不能识别DbProvider元素的connectionStringName属性,和XmlProvider的fileName属性,因为基类ResourceProviderConfigurationElement没有相关属性的定义。

既然在默认情况下具体ResourceProvider的配置元素不能被反序列化,它们属于不可识别元素(Unrecognized Element),那么我们只要手工对其实施反序列化,具体做法就是重写ConfigurationElementCollection的OnDeserializeUnrecognizedElement方法。但是即使手工进行反序列化,也需要确定具体的配置元素类型,这又如何解决呢?如果你足够仔细的话,在定义DbResourceProvider和XmlResourceProvider的时候,在类上面应用了一个特殊的自定义特性:ConfigurationElementTypeAttribute,它建立起了具体ResourceProvider和对应配置元素之间的匹配关系。

   1: [ConfigurationElementType(typeof(DbResourceProviderConfigurationElement))]
   2: public class DbResourceProvider : IResourceProvider
   3: {
   4:     //...
   5: }

而这个ConfigurationElementTypeAttribute定义非常简单,仅仅定义一个用于表示配置元素类型的ConfigurationElementType属性,该属性在构造函数中初始化。

   1: [AttributeUsage( AttributeTargets.Class)]
   2: public class ConfigurationElementTypeAttribute: Attribute
   3: {
   4:     public Type ConfigurationElementType { get; private set; }
   5:     public ConfigurationElementTypeAttribute(Type configurationElementType)
   6:     {
   7:         this.ConfigurationElementType = configurationElementType;
   8:     }
   9: }

由于每个具体的ResourceProvider都具有这样一个ConfigurationElementTypeAttribute来指定对应的ConfigurationElement类型,那么我们就可以反射来为反序列化确定配置元素的目标类型了。这样的操作实现在RetrieveConfigurationElementType方法中。

四、ResourceProviderFactory的定义

NameTypeElementCollection<T>通过重写OnDeserializeUnrecognizedElement方法,以及借助于ConfigurationElementTypeAttribute特性,解决了对不可识别元素的解析问题。而具体的ResourceProviderConfigurationElement都实现了CreateProvider方法来创建对应的ResourceProvider,那么ResourceProviderFactory的实现就非常简单了。

   1: public static class ResourceProviderFactory
   2: {
   3:     public static IResourceProvider GetResourceProvider()
   4:     {
   5:         ResourceSettings settings = ResourceSettings.GetConfiguration();
   6:         return GetResourceProvider(settings.DefaultProvider);
   7:     }
   8:     public static IResourceProvider GetResourceProvider(string name)
   9:     {
  10:         ResourceSettings settings = ResourceSettings.GetConfiguration();
  11:         return settings.Providers.GetConfigurationElement(name).CreateProvider();
  12:     }
  13: }

 

五、补充

经常关注我博客朋友应该知道本人对微软开源框架EnterLib有一定的了解。熟悉EnterLib的朋友经常诟病于它繁琐的配置,这确实是一个问题。不过这从另一个方面说明了EnterLib底层配置系统的强大,不然很难支持如此复杂的配置。对于学习自定义配置,了解EnterLib配置体系的实现是一个不错的途径。实际上,本篇文章关于“不可识别配置元素的解析”的解决方案就是来源于EnterLib。

 

Advertisements

ASP.NET自动给URL加上超链接

作为一个程序员,在完成设计后还要根据程序的情况以及用户的反映不断对程序进行改进,这样才能不断地完善自己的作品。笔者在制作完软件商务网的 论坛后,发现人们总喜欢在帖子中加上各种有用的URL链接或Email地址。而笔者当初设计时没有考虑到这一点,使得这些URL链接或Email地址只能 以文字的形式而并不是以超链接的形式显示,其它浏览帖子的人还必须把这些URL链接拷贝到浏览器中或把Email地址拷贝到Outlook中才能转到相应 的链接地址或发送电子邮件到相应的Email地址。

发现这个问题后,笔者即着手进行解决。首先是从网上查找有关这方面的现在代码,可惜的是,在搜索引擎上反复查找也没有发现这方面的文章。后来一想,干脆自己用ASP.NET编写一个。

要想自动显示超链接的关键在于如何能正确识别超链接,毫无疑问的,最有效的方法是用正则表达式。正则表达式是由普通字符(例如字符 a 到 z)以及特殊字符(称为元字符)组成的文字模式,描述了一种字符串匹配的模式,可以用来检查一个串是否含有某种子串、将匹配的子串做替换或者从某个串中取 出符合某个条件的子串等。。NET基础类库中包含有一个名字空间和一系列可以充分发挥规则表达式威力的类,用它就可以自动探测出文字中的URL链接或 Email地址。

下面具体讲讲如何用ASP.NET(C#)一步步实现我们的目的:

//首先,要想在ASP.NET(C#)中使用正则表达式就必须把 System.Text.RegularExpressions 这个命名空间包含进来
using System.Text.RegularExpressions;
//第二步是用正则表达式识别URL超链接:
Regex urlregex = new Regex(@”(http:\/\/([\w.]+\/?)\S*)”,RegexOptions.IgnoreCase|RegexOptions.Compiled);
//这里的代码是用正则表达式识别Email地址:
Regex emailregex = new Regex(@”([a-zA-Z_0-9.-]+\@[a-zA-Z_0-9.-]+\.\w+)”,RegexOptions.IgnoreCase|RegexOptions.Compiled);
//第三步,当程序已经识别出URL超链接或Email地址后,//必须用〈a href=……〉超链接〈/a〉对这些超链接进行替换,//这样才能把这些文字显示为链接的形式。我这里把它们全部包含在函数中:
private void Button1_Click(object sender, System.EventArgs e){string strContent = InputTextBox.Text;Regex urlregex = new Regex(@”(http:\/\/([\w.]+\/?)\S*)”,RegexOptions.IgnoreCase| RegexOptions.Compiled);strContent = urlregex.Replace(strContent,”〈a href=\”\” target=\”_blank\”〉〈/a〉”);Regex emailregex = new Regex(@”([a-zA-Z_0-9.-]+\@[a-zA-Z_0-9.-]+\.\w+)”,RegexOptions.IgnoreCase| RegexOptions.Compiled);strContent = emailregex.Replace(strContent, “〈a href=mailto:〉〈/a〉”);lbContent.Text += “〈br〉”+strContent;}
//通过以上几步,你就可以在网页上自动显示超链接以及Email地址了。

来自:http://www.oschina.net/code/snippet_54100_3371