自动生成强类型的AppSettings类

时间:2009-10-18 00:56:13

标签: c# asp.net t4 appsettings facade

首先是问题:

这可能吗?我从Joe Wrobel's work(被遗忘的Codeplex project的减少)中获取灵感。在这里,您完成了为提供者创建配置文件的工作,并为其创建强类型的工作,有效地为Profile类创建了一个外观。

现在是背景故事!

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

如果您遵循“开箱即用”的应用程序模板,.NET确实没那么好。很多例子都使用web.config中的appsetting来存储各种设置。这确实是一个存储的好地方,非常适合大多数应用程序。但是,当您开始直接调用这些问题时,问题就会开始出现 - 例如ConfigurationManager.AppSettings["MyAppSetting"]。然后,当你回到使用魔术字符串时,你并没有比PHP用户更好。

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

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

这个“应用程序外观”可以通过我的数据层,服务层和表示层访问,并保存应用程序模式,使用yada yada yada的服务端点,并限制必须寻找许多魔术字符串,两个魔术字符串 - 一个(名称)在立面中,另一个(名称和值)在创建点(对我来说是db)。

这个门面类最终会变得非常大,我最终会厌倦不得不更新它们。

所以我想要做的是拥有一个ApplicationFacade类,每次构建完成后都会自动生成。现在又回到了开始......这可能吗?

3 个答案:

答案 0 :(得分:7)

您也可以使用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上重新生成类 模板从值

识别字符串,日期时间,整数和布尔类型
<#@ 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

#>

答案 1 :(得分:1)

您可以使用预构建步骤执行此操作。这很容易做 - 只需编写一个程序或脚本或模板来重新生成类,并在预构建事件中调用它 - 但这会给你带来红色的颤抖,并且在任何新成员之前都没有智能感知,直到课程获得再生。

稍微更省略,但可能更方便的方法是创建T4 template并在项目中包含它。但是,每次添加新设置时,您都需要记住重新转换模板。这太麻烦了吗?

答案 2 :(得分:1)

@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);
    }
#>