自动生成强类型 AppSettings 类

发布于 2024-08-07 21:44:07 字数 1722 浏览 6 评论 0原文

首先是问题:

这可能吗?我的灵感来自 Joe Wrobel 的作品(被遗忘的 Codeplex 项目)。在这里,您为提供者创建配置文件,并为其创建强类型,从而有效地为 Profile 类创建外观。

现在是背景故事!

我真的不喜欢魔法弦。它们非常糟糕,并且在更新应用程序时可能会导致一些严重的问题。使用过 PHP 和 ColdFusion 等语言后,我知道很容易将它们放入您的应用程序中,然后忘记它们,直到您需要更改一种语言为止。然后你必须找出它们的每一个变体并相应地改变它们。

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

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

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

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

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

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

Here's the question first:

Is this possible? I'm taking my inspiration from Joe Wrobel's work (a redux of the forgotten Codeplex project). Here, you do your work on creating your profile for the provider, and it does the legwork of creating the strong typing for it, effectively creating a facade for the Profile class.

And now the back story!

I really don't like magic strings. They're pretty bad and can cause some serious issues when it comes to updating your application. Having worked in languages like PHP and ColdFusion, I know that it's easy to put them into your application and forget about them until you need to change one. And then you have to hunt each and every variation of them down and alter them accordingly.

.NET really isn't that much better if you follow the 'out of the box' application templates. Lots of examples out there use the appsettings in the web.config to store various settings. This is indeed a fine place to store, and is perfect for most applications. Problems start to arise however, when you start calling these directly - for example ConfigurationManager.AppSettings["MyAppSetting"]. Then you're not really any better off than a PHP user as you're back to using magic strings.

This is where facades come in. Facades offer a way of creating a strongly-typed object from a magic string in one place, and having the developer reference that from the rest of the application.

Now, instead of using a web.config to contain my appsettings, I use a database to hold them all. On application start, the name/value combos are retrieved, and are then sequentially added to the ConfigurationManager.AppSettings via Set. No biggie (apart from the problem I had earlier!).

This 'application facade' is accessible by my data layer, service layer and presentation layer and holds things like the application mode, which service endpoint to use yada yada yada and limits the need for having to hunt for many magic strings, down to two magic strings - one (the name) in the facade, and the other (the name and value) in the point of creation (which, for me is the db).

This facade class will eventually get pretty big and I'll eventually get tired of having to update both of them.

So what I'd like to do is have an ApplicationFacade class which auto-generates every time a build is done. And now back to the beginning... Is this possible?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

深爱成瘾 2024-08-14 21:44:07

您还可以使用 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 文件 并包含到您想要放置生成文件的项目中。
在每个构建上重新生成类 在这里查看我的答案
模板从值中识别 string、datetime、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

#>

You could also use CodeSmith templates for this purpose. Advantage is that you can set in template file properties to be regenerated on each build (set BuildAction = "Complile")

Edited
I also looked for such solution. After googling I found base T4 template to generate such a class.
I have redesigned it and you can find it below.

Template is generating wrapper class for appSetting section from your Web.config/App.config file

Suppose you have following lines of settings in config file

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

After processing template you will get following class

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;

    }
}

Save following code to *.tt file and include to your project where you want to put generated file.
To regenerate class on each build see my answer here
Template recognize string, datetime, int and bool types from values

<#@ 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

#>
瞄了个咪的 2024-08-14 21:44:07

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

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

You could do this with a pre-build step. This is reasonably easy to do -- just write a program or script or template that regenerates the class, and call it in your pre-build event -- but this will give you red wigglies and no intellisense on any new members until the class gets regenerated.

A slightly more manual, but probably more convenient, approach would be to create a T4 template and include that in your project. You would however need to remember to re-transform the template every time you added a new setting. Would this be too onerous?

阳光的暖冬 2024-08-14 21:44:07

@Cheburek 干得好

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

@Cheburek Great work

Here is C# port

<#@ 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);
    }
#>
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文