2010-12-06 66 views
4

我有一个问题,我想为我的应用程序设计一个设置结构,它需要尽可能优化本地化,扩展和分组。我想按照实体类型分组设置(您可以将其视为每个控制器的分组设置)。设置会显示给用户,所以每个设置都需要一个漂亮的标题和描述,两者都需要进行本地化。新的设置只能由开发人员引入,需要重新编译。设计一个设置结构

我想到的是一个将设置公开为静态属性的类,因此它们可以方便地在整个应用程序中以静态方式使用。这些设置会在第一次构造类时发生(这是在请求设置时发生的),我使用数据库存储设置并使用反射将它们分配给它们在运行时的相应属性。

它看起来像这样

public class FirmSettings 
{ 
    private static IFirmSettingsRepository _repository { get; set; } 

    public static bool ShowInvoicePaymentDetails { get; set; } 

    public static bool ShowInvoiceDiscountValue { get; set; } 

    public static bool ShowDocumentComment { get; set; } 

    public static bool ShowDocumentTaxStatement { get; set; } 

    public static bool ShowDocumentAuthor { get; set; } 

    #region Constructors 

    /// <summary> 
    ///  Initializes a new instance of the <see cref = "FirmSettings" /> class. 
    /// </summary> 
    static FirmSettings() 
    { 
     Load(); 
    } 

    #endregion 

    #region Load Settings 

    public static void Load() 
    { 
     _repository = MvcApplication.Container.Get<IFirmSettingsRepository>(); 
     Type settingsType = typeof (FirmSettings); 

     //------------------------------------------------------------ 
     // Enumerate through individual settings nodes 
     //------------------------------------------------------------ 
     StringDictionary dic = _repository.LoadSettings(); 

     if (dic == null) 
     { 
      Save(); // prepares the settings with blank settings 
      dic = _repository.LoadSettings(); // reload 
     } 

     foreach (string key in dic.Keys) 
     { 
      //------------------------------------------------------------ 
      // Extract the setting's name/value pair 
      //------------------------------------------------------------ 
      string name = key; 
      string value = dic[key]; 

      //------------------------------------------------------------ 
      // Enumerate through public properties of this instance 
      //------------------------------------------------------------ 
      foreach (PropertyInfo propertyInformation in settingsType.GetProperties(BindingFlags.Public | 
                        BindingFlags.Static)) 
      { 
       //------------------------------------------------------------ 
       // Determine if configured setting matches current setting based on name 
       //------------------------------------------------------------ 
       if (propertyInformation.Name.Equals(name, StringComparison.OrdinalIgnoreCase)) 
       { 
        //------------------------------------------------------------ 
        // Attempt to apply configured setting 
        //------------------------------------------------------------ 
        try 
        { 
         if (propertyInformation.CanWrite) 
         { 
          propertyInformation.SetValue(typeof (FirmSettings), 
                 Convert.ChangeType(value, propertyInformation.PropertyType, 
                      CultureInfo.CurrentCulture), null); 
         } 
        } 
        catch 
        { 
         // TODO: Log exception to a common logging framework? 
        } 
        break; 
       } 
      } 
     } 

     // perform resave if there are any new settings 
     Save(); 
    } 

    #endregion 

    #region Save settings 

    /// <summary> 
    ///  Saves the settings to disk. 
    /// </summary> 
    public static void Save() 
    { 
     StringDictionary dic = new StringDictionary(); 
     Type settingsType = typeof (FirmSettings); 

     //------------------------------------------------------------ 
     // Enumerate through settings properties 
     //------------------------------------------------------------ 
     foreach (PropertyInfo propertyInformation in settingsType.GetProperties(BindingFlags.Public | 
                       BindingFlags.Static)) 
     { 
      //------------------------------------------------------------ 
      // Extract property value and its string representation 
      //------------------------------------------------------------ 
      object propertyValue = propertyInformation.GetValue(typeof (FirmSettings), null); 

      string valueAsString; 
      //------------------------------------------------------------ 
      // Format null/default property values as empty strings 
      //------------------------------------------------------------ 
      if (propertyValue == null || propertyValue.Equals(Int32.MinValue) || propertyValue.Equals(Single.MinValue)) 
      { 
       valueAsString = String.Empty; 
      } 
      else 
      { 
       valueAsString = propertyValue.ToString(); 
      } 
      //------------------------------------------------------------ 
      // Write property name/value pair 
      //------------------------------------------------------------ 
      dic.Add(propertyInformation.Name, valueAsString); 
     } 

     _repository.SaveSettings(dic); 
    } 

    #endregion 
} 

每个设置存储在DB的属性名称(装载我们忽略的情况下)的小写版本。本地化字符串也是如此,例如,将存储为FirmSettings_ShowDocumentTaxStatement_TitleFirmSettings_ShowDocumentTaxStatement_Desc。 (约定)

但是这种方法并不解决分组问题。在UI中,需要某种设置分组,因此“发票”设置将显示在组中。我可以为某些设置引入一个前缀,然后根据前缀(另一个约定)将其呈现出来。

你喜欢这种方法吗?如果不是,你怎么做?这种方法有很多约定,这就是困扰我的东西,只是一点点。

回答

1

你失去了我在这里...

我看你用某种类型的容器,那么你为什么不只是每次你需要参考它的时候注入该设置类的单一实例?静态类+方法对单元测试是不利的(你需要这样做)。

此外,我不明白你为什么要使用反射/字符串匹配来设置存储/检索。如果你真的有大量的设置和复杂的分组,你需要投入时间想出一个合适的DAL。

只需注意,您的“关键”(例如FirmSettings_ShowDocumentTaxStatement_Title)不包含名称空间,所以如果两个类具有相同的名称和相同的方法,则最终会出现难以捕获的错误。这只是一个简单的情况。我的观点是,为了识别目的,匹配你的类+方法名的字符串不是一个好主意。 (因为我假设你有一个大型复杂项目来诉诸这种设置管理。)

最后,“我不知道如何在运行时为静态(或常规)属性赋值其他而不是使用反射。“你可以使用类/方法/属性属性,并有一个工厂类泵出(在你的情况下)你想要的设置类的单例。适当的DB列/行关联信息可以包含在属性中。

P.S.反射在性能方面没有问题。只是不要使用静态类,而是使用单例,并在初始化设置类时执行后台任务。一旦启动单身人士,您不必再次初始化它。但是无论你做什么,我强烈建议你丢失与类/方法名称匹配的字符串。

P.P.S.研究AoP /政策注入(或者是干预Microsoft Unity DI容器)。我相信这些对你有帮助?

P.P.P.S.最后到3后脚本是坏的英语...

0

恕我直言,解决方案太复杂。试着想出更简单的东西。首先不要使用反射(性能的原因),其次我会使用配置文件(XML),而不是DB应用程序/用户设置。

请参阅this有关反射和性能的综合文章。

+1

我看不到使用反射导致性能问题在这里 – 2010-12-06 14:54:10

+0

@Kev一般来说反射比较慢,应该避免。将设置存储在我的视窗中并不需要反思。它可以用许多不同的其他方式完成。 – 2010-12-06 14:59:21