【问题标题】:Designing a settings structure设计设置结构
【发布时间】:2010-12-06 12:14:52
【问题描述】:

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

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

看起来像这样

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 中,需要某种设置分组,因此发票设置将显示在一个组中。我可以为某些设置引入前缀,然后根据前缀(另一种约定)呈现出来。

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

【问题讨论】:

    标签: c#


    【解决方案1】:

    你把我弄丢了……

    我看到你使用了某种容器,那么为什么不每次你需要引用它时注入该设置类的单例实例呢?静态类 + 方法不利于单元测试(您需要这样做)。

    另外,我不明白您为什么要使用反射/字符串匹配来设置存储/检索。如果您确实有大量设置并且它们之间具有复杂的分组,那么您将需要花时间提出适当的 DAL。

    请注意,您的“键”(例如,FirmSettings_ShowDocumentTaxStatement_Title)不包含命名空间,因此如果两个类具有相同的名称和相同的方法,您最终会遇到难以捕捉的错误。这只是一个简单的场景。我的观点是,为了识别而匹配您的类+方法名称的字符串不是一个好主意。 (因为我假设您有一个大而复杂的项目来求助于这种设置管理。)

    最后,“我不知道除了使用反射之外,您还能如何在运行时为静态(或常规)属性赋值。”您可以使用类/方法/属性属性并让工厂类抽出(在您的情况下)您所需设置类的单例。相应的数据库列/行关联信息可以包含在属性中。

    附:反射在性能方面很好。只是不要使用静态类,而是使用单例,并在设置类的初始化时执行后台任务。一旦启动了单例,您就不必再次初始化它。但无论你做什么,我强烈建议你丢失与类/方法名匹配的字符串。

    附言查看 AoP / 策略注入(或者它是否干预了 Microsoft Unity DI 容器)。我相信这些可能对你有帮助?

    P.P.P.S.最后进入 3 post script 是英文不好...

    【讨论】:

    • 感谢您对单身人士的意见,绝对值得一试并尝试走这条路。我实际上提出了一个关于单例或静态类stackoverflow.com/questions/4382537/… 的并发问题的新问题
    【解决方案2】:

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

    参见this关于反射和性能的综合文章。

    【讨论】:

    • 我在这里看不到使用反射导致性能问题
    • @Kev 一般来说,反射速度较慢,应避免。在我的选择中存储设置不需要反思。它可以通过许多不同的方式完成。
    • 已经决定使用 DB,所以这里不会有任何变化。由于易于维护,我们希望数据库中的所有内容。
    • 现在进行反射:我不知道除了使用反射之外,您还能如何在运行时为静态(或常规)属性赋值。而且我们确实需要设置属性,因为使用魔术字符串并通过访问 ConfigurationManager.AppSettings 从配置文件中读取它们容易出错且不可取。之前我应该​​指出,强类型设置是要求之一。
    猜你喜欢
    • 2017-06-27
    • 2017-08-19
    • 1970-01-01
    • 1970-01-01
    • 2013-02-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多