【问题标题】:Auto-generate a strongly-typed AppSettings class自动生成强类型 AppSettings 类
【发布时间】:2010-12-07 16:56:22
【问题描述】:

首先是问题:

这可能吗?我的灵感来自Joe Wrobel's work(被遗忘的Codeplex project 的还原版)。在这里,您为提供者创建配置文件,它为它创建强类型,有效地为 Profile 类创建外观。

现在是背景故事!

我真的不喜欢magic strings。它们非常糟糕,在更新应用程序时可能会导致一些严重问题。曾使用 PHP 和 ColdFusion 等语言工作过,我知道很容易将它们放入您的应用程序中并在需要更改之前忘记它们。然后你必须追捕它们的每一个变化并相应地改变它们。

如果您遵循“开箱即用”的应用程序模板,.NET 并没有那么好。许多示例使用 web.config 中的 appsettings 来存储各种设置。这确实是一个存储的好地方,非常适合大多数应用程序。然而,当您开始直接调用它们时,问题开始出现 - 例如ConfigurationManager.AppSettings["MyAppSetting"]。那么你并没有比 PHP 用户更好,因为你又开始使用魔法字符串了。

这就是facades 的用武之地。Facades 提供了一种在一个地方从魔术字符串创建强类型对象的方法,并让开发人员从应用程序的其余部分引用该对象。

现在,我不再使用 web.config 来包含我的 appsettings,而是使用数据库来保存它们。在应用程序启动时,检索名称/值组合,然后通过Set 依次添加到ConfigurationManager.AppSettings。没什么大不了的(除了我之前的problem!)。

我的数据层、服务层和表示层可以访问这个“应用程序外观”,并保存诸如应用程序模式、使用 yada yada yada 的服务端点之类的东西,并限制了必须寻找许多魔术字符串的需要,向下到两个魔术字符串 - 一个(名称)在外观中,另一个(名称和值)在创建点(对我来说是 db)。

这个外观类最终会变得相当大,我最终会厌倦不得不更新它们。

所以我想做的是有一个 ApplicationFacade 类,它会在每次构建完成时自动生成。现在回到开头……这可能吗?

【问题讨论】:

    标签: c# asp.net t4 appsettings facade


    【解决方案1】:

    您也可以为此目的使用 CodeSmith 模板。优点是您可以在模板文件中设置要在每次构建时重新生成的属性(设置 BuildAction = "Complile")

    已编辑 我也在寻找这样的解决方案。谷歌搜索后,我找到了生成这样一个类的基本 T4 模板。 我重新设计了它,你可以在下面找到它。

    模板正在为您的 Web.config/App.config 文件中的 appSetting 部分生成包装类

    假设您在配置文件中有以下几行设置

      <appSettings>
        <add key="PageSize" value="20" />
        <add key="CurrentTheme" value="MyFavouriteTheme" />
        <add key="IsShowSomething" value="True" />
      </appSettings>
    

    处理模板后,您将获得以下课程

    namespace MyProject.Core
    {
        /// <remarks>
        /// You can create partial class with the same name in another file to add custom properties
        /// </remarks>
        public static partial class SiteSettings 
        {
            /// <summary>
            /// Static constructor to initialize properties
            /// </summary>
            static SiteSettings()
            {
                var settings = System.Configuration.ConfigurationManager.AppSettings;
                PageSize = Convert.ToInt32( settings["PageSize"] );
                CurrentTheme = ( settings["CurrentTheme"] );
                IsShowSomething = Convert.ToBoolean( settings["IsShowSomething"] );
            }
    
            /// <summary>
            /// PageSize configuration value
            /// </summary>
            public static readonly int PageSize;
    
            /// <summary>
            /// CurrentTheme configuration value
            /// </summary>
            public static readonly string CurrentTheme;
    
            /// <summary>
            /// IsShowSomething configuration value
            /// </summary>
            public static readonly bool IsShowSomething;
    
        }
    }
    

    将以下代码保存到 *.tt 文件并包含到您要放置生成文件的项目中。 在每个构建上重新生成类see my answer here 模板从值中识别字符串、日期时间、int 和 bool 类型

    <#@ assembly name="System.Core" #>
    <#@ assembly name="System.Xml" #>
    <#@ assembly name="System.Xml.Linq" #>
    <#@ import namespace="System" #>
    <#@ import namespace="System.Text" #>
    <#@ import namespace="System.IO" #>
    <#@ import namespace="System.Linq" #>
    <#@ import namespace="System.Xml.Linq" #>
    <#@ import namespace="Microsoft.VisualBasic" #>
    <#@ template language="VB" debug="True" hostspecific="True"  #>
    <#@ output extension=".Generated.cs" #>
    <#
        Dim projectNamespace as String = "MyProject.Core"
        Dim className as String = "SiteSettings"
        Dim fileName as String = "..\..\MyProject.Web\web.config"
    
        Init(fileName)  
    
    #>
    //------------------------------------------------------------------------------
    // FileName = <#= path #>
    // Generated at <#= Now.ToLocaltime() #>
    //
    // <auto-generated>
    //     This code was generated by a tool.
    //
    //     Changes to this file may cause incorrect behavior and will be lost if
    //     the code is regenerated.
    //     
    //    NOTE: Please use the Add a Reference to System.Configuration assembly if 
    //          you get compile errors with ConfigurationManager
    // </auto-generated>
    //------------------------------------------------------------------------------
    
    using System;
    using System.Configuration;
    
    namespace <#= projectNamespace #>
    {
        /// <remarks>
        /// You can create partial class with the same name in another file to add custom properties
        /// </remarks>
        public static partial class <#= className #> 
        {
            /// <summary>
            /// Static constructor to initialize properties
            /// </summary>
            static <#= className #>()
            {
                var settings = System.Configuration.ConfigurationManager.AppSettings;
    <#= AddToCostructor(path) #>        }
    
    <#= RenderApplicationSettings(path) #>  }
    }
    
    <#+ 
        Dim path as String = ""
        Dim doc as XDocument = Nothing
    
        Public Sub Init(fileName as String)
            Try
                path = Host.ResolvePath(fileName)
                If File.Exists(path) Then
                    doc = XDocument.Load(path)
                End If
            Catch
                path = "<< App.config or Web.config not found within the project >>"
            End Try     
        End Sub
    
        Public Function AddToCostructor(ByVal path as String) as String                 
            If doc Is Nothing Then Return ""
    
            Dim sb as New StringBuilder()
    
            For Each result as XElement in doc...<appSettings>.<add>            
                sb.Append(vbTab).Append(vbTab).Append(vbTab)
                sb.AppendFormat("{0} = {1}( settings[""{0}""] );", result.@key, GetConverter(result.@value))
                sb.AppendLine()
            Next
    
            Return sb.ToString()
    
        End Function
    
        Public Function RenderApplicationSettings(ByVal path as String) as String
            If doc Is Nothing Then Return ""
    
            Dim sb as New StringBuilder()       
    
            For Each result as XElement in doc...<appSettings>.<add>    
                dim key = result.@key
                sb.Append(vbTab).Append(vbTab)
                sb.Append("/// <summary>").AppendLine()
                sb.Append(vbTab).Append(vbTab)
                sb.AppendFormat("/// {0} configuration value", key).AppendLine()            
                sb.Append(vbTab).Append(vbTab)
                sb.Append("/// </summary>").AppendLine()
                sb.Append(vbTab).Append(vbTab)
                sb.AppendFormat("public static readonly {0} {1}; ", GetPropertyType(result.@value), key)    
                sb.AppendLine().AppendLine()
            Next
    
            Return sb.ToString()
    
        End Function
    
        Public Shared Function GetConverter(ByVal prop as String) as String     
            If IsNumeric(prop) Then Return "Convert.ToInt32"
            If IsDate(prop) Then Return "Convert.ToDateTime"
            dim b as Boolean
            If Boolean.TryParse(prop, b) Then Return "Convert.ToBoolean"        
            Return ""
        End Function
    
        Public Shared Function GetPropertyType(ByVal prop as String) as String
            If IsNumeric(prop) Then Return "int"
            If IsDate(prop) Then Return "DateTime"
            dim b as Boolean
            If Boolean.TryParse(prop, b) Then Return "bool"
            Return "string"
        End Function
    
    #>
    

    【讨论】:

    • 这是一个有趣的想法,但老实说我不是 CodeSmith 的忠实粉丝。最后,我编写了一个我自己的类,在任何情况下都需要它,因为我的应用程序无法推断我的 appsettings 是什么类型。
    • 这是一个非常有趣的解决方案!它似乎遇到问题的一件事(这是 VBs IsNumeric)是“0,5,0”,VB 认为它是一个数值,尽管我不确定如何!
    • 有兴趣了解这种 IsNumeric 行为。这似乎是允许用户输入“1,000,000”这样的解决方案,但它很难看。如果您处理诸如“0,5,0”之类的字符串,则可以增强数字检查。 IE。您可以使用以下正则表达式模式 \d+(?:[,\.]\d+)?它必须只允许单个点或逗号
    【解决方案2】:

    您可以通过预构建步骤来做到这一点。这相当容易做到——只需编写一个程序或脚本或模板来重新生成类,并在你的预构建事件中调用它——但这会给你带来红色的摆动,并且在类得到之前对任何新成员都没有智能感知重新生成。

    更手动但可能更方便的方法是创建一个T4 template 并将其包含在您的项目中。但是,您需要记住每次添加新设置时都重新转换模板。会不会太繁重了?

    【讨论】:

    • 感谢您的回复!实际上,我宁愿无论我做什么,填充外观类的整个过程都是自动的,开发人员不需要任何工作。是否有任何您知道的现有资源可以做到这一点?
    • 嗯,既然你在 ASP.NET 中,你也许可以使用 System.Web.Compilation 命名空间来做到这一点,我认为这是 ASP.NET 网站项目用来创建它们的配置文件类。不幸的是,这超出了我的知识范围,我什至不确定它是否适用于 Web 应用程序(基于 .csproj)项目。对不起。您的另一个选择是查看 VS 自定义工具 (IVsSingleFileGenerator),但它们是为不同的场景设计的,只有在您的设置定义保存在自己的特殊文件中时才会起作用。
    • 这 - blog.jqweb.ca/?p=44 - 看起来像我可以使用的东西,但我需要适应是否从数据库中提取,而不是从 web.config...我会更新 if/当我找到解决方案时。
    • 另外,正如我所提到的,T4 模板不会自动更新生成的代码(至少我相信——如果我错了,请更正!)。因此,您需要记住在更改设置架构时重新运行模板。
    • J Wynia 为此创建了一个 T4 模板 - 请参阅 wynia.org/wordpress/2010/04/… 要自动运行 T4 模板,您可以使用 Chirpy chirpy.codeplex.com
    【解决方案3】:

    @Cheburek Great work

    这里是 C# 端口

    <#@ assembly name="System.Core" #>
    <#@ assembly name="System.Xml" #>
    <#@ assembly name="System.Xml.Linq" #>
    <#@ import namespace="System" #>
    <#@ import namespace="System.Text" #>
    <#@ import namespace="System.IO" #>
    <#@ import namespace="System.Linq" #>
    <#@ import namespace="System.Xml.Linq" #>
    <#@ template language="C#" debug="True" hostspecific="True"  #>
    <#@ output extension=".Generated.cs" #>
    <#
        var projectNamespace = "SandBoxLib";
        var className  = "AppSettings";
        var fileName  = "app.config";
    
        Init(fileName);
    
    #>
    //------------------------------------------------------------------------------
    // FileName = <#= path #>
    // Generated at <#= DateTime.UtcNow.ToLocalTime() #>
    //
    // <auto-generated>
    //     This code was generated by a tool.
    //
    //     Changes to this file may cause incorrect behavior and will be lost if
    //     the code is regenerated.
    //     
    //    NOTE: Please use the Add a Reference to System.Configuration assembly if 
    //          you get compile errors with ConfigurationManager
    // </auto-generated>
    //------------------------------------------------------------------------------
    
    using System;
    
    namespace <#= projectNamespace #>
    {
        /// <remarks>
        /// You can create partial class with the same name in another file to add custom properties
        /// </remarks>
        public static partial class <#= className #> 
        {
            /// <summary>
            /// Static constructor to initialize properties
            /// </summary>
            static <#= className #>()
            {
                var settings = System.Configuration.ConfigurationManager.AppSettings;
    <#= AddToCostructor() #>        }
    
    <#= RenderApplicationSettings() #>  }
    }
    
    <#+ 
        private string path = "";
        private XDocument doc;
    
        public void Init(string fileName){
            try{
                path = Host.ResolvePath(fileName);
                if (File.Exists(path)){
                    doc = XDocument.Load(path);
                }
            }
            catch{
                path = "<< App.config or Web.config not found within the project >>";
            }
        }
    
        public string AddToCostructor(){
            if (doc == null) return "";
    
            var sb = new StringBuilder();
    
            foreach (var elem in doc.Descendants("appSettings").Elements()){
                var key = GetAttributeValue(elem, "key");
                var val = GetAttributeValue(elem, "value");
                sb.Append("\t").Append("\t").Append("\t");
                sb.AppendFormat("{0} = {1}( settings[\"{0}\"] );", key, GetConverter(val));
                sb.AppendLine();
            }
    
            return sb.ToString();
        }
    
        public string RenderApplicationSettings(){
            if (doc == null) return "";
    
            var sb = new StringBuilder();
    
            foreach (var elem in doc.Descendants("appSettings").Elements()){    
                var key = GetAttributeValue(elem, "key");
                var val = GetAttributeValue(elem, "value");
    
                sb.Append("\t").Append("\t");
                sb.AppendFormat("public static readonly {0} {1}; ", GetPropertyType(val), key);
                sb.AppendLine().AppendLine();
            }
    
            return sb.ToString();
        }
    
        public string GetConverter(string value){
            if (IsNumeric(value)) return "Convert.ToInt32";
            if (IsDate(value)) return "Convert.ToDateTime";
            if (IsBool(value)) return "Convert.ToBoolean";
            return "string";
        }
    
        public string GetPropertyType(string value){
            if (IsNumeric(value)) return "int";
            if (IsDate(value)) return "DateTime";
            if (IsBool(value)) return "bool";
            return "string";
        }
    
        private string GetAttributeValue(XElement elem, string attributeName){
            return elem.Attribute(attributeName).Value;
        }
    
        private bool IsNumeric(string value){
            return int.TryParse(value, out var r);
        }
    
        private bool IsDate(string value){
            return DateTime.TryParse(value, out var r);
        }
    
        private bool IsBool(string value){
            return Boolean.TryParse(value, out var r);
        }
    #>
    

    【讨论】:

    • 我建议不要在此答案中使用 IsNumeric 函数,除非您绝对确定您只会使用整数。 VB.NET IsNumeric 实现是不同的,它处理大范围的值(有时会产生意想不到的效果)。我想你也可以做一个Parse,然后检查结果.toString().ToLowerInvariant()是否匹配value.toLowerInvariant()
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-09-05
    • 2022-11-03
    • 2015-04-07
    • 2018-10-29
    • 2014-07-21
    • 1970-01-01
    相关资源
    最近更新 更多