65.9K
CodeProject 正在变化。 阅读更多。
Home

Cfg-NET

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.89/5 (27投票s)

2015 年 1 月 9 日

Apache

4分钟阅读

viewsIcon

59411

downloadIcon

611

一种 .NET 配置处理器的替代方案

Build status NuGet Cfg-Net

引言

这是一个 开源 的 .NET 配置处理器,已获得 Apache 2 许可。

配置

  • 可由最终用户编辑
  • 减少重新编译的需要
  • 可与其他配置共存

一个配置处理器

  • 易于使用
  • 支持集合
  • 验证并报告错误和警告
  • 提供对 null 的保护
  • 允许您将配置存储在任何您想要的位置(例如:Web、文件、字符串)
  • 可扩展
  • 可组合
  • 小巧(约 67 KB)
  • 零依赖
  • 可移植(.NETStandard1.0,兼容 PCL)
  • 可在 Nuget 上找到

配置

开箱即用,Cfg-NET 支持 XML 和 JSON 配置。

XML 示例

<cfg>
    <fruit>
        <add name="apple">
            <colors>
                <add name="red" />
                <add name="yellow" />
                <add name="green" />
            </colors>
        </add>
        <add name="banana">
            <colors>
                <add name="yellow" />
            </colors>
        </add>
    </fruit>
</cfg>

或者,如果您更喜欢 JSON

{
    "fruit": [
        { 
            "name":"apple",
            "colors": [
                {"name":"red"},
                {"name":"yellow"},
                {"name":"green"}
            ]
        },
        {
            "name":"banana",
            "colors": [
                {"name":"yellow"}
            ]
        }
    ]
}

代码

在代码中,您希望处理一个与之对应的 C# 模型,如下所示

using System.Collections.Generic;

class Cfg {
    public List<Fruit> Fruit { get; set; }
}

class Fruit {
    public string Name { get; set; }
    public List<Color> Colors {get; set;}
}

class Color {
    public string Name {get; set;}
}

为了让上述模型与 Cfg-NET 一起工作,请让每个类继承 CfgNode 并用 Cfg 自定义属性装饰属性

using System.Collections.Generic;
using Cfg.Net;

class Cfg : CfgNode {
    [Cfg]
    public List<Fruit> Fruit { get; set; }
}

class Fruit : CfgNode {
    [Cfg]
    public string Name { get; set; }
    [Cfg]
    public List<Color> Colors {get; set;}
}

class Color : CfgNode {
    [Cfg]
    public string Name {get; set;}
}

设计配置

继承 CfgNode 会为您提供 LoadCheckSerialize 方法。

Cfg 属性添加了验证和修改说明。一个属性具有以下内置选项

  • value,例如默认
  • toLowertoUpper
  • trimtrimStarttrimEnd
  • required
  • unique(唯一)
  • domain(域),具有 delimiter(分隔符)和 ignoreCase(忽略大小写)选项
  • minLength(最小长度)和/或 maxLength(最大长度)
  • minValue(最小值)和/或 maxValue(最大值)

如果我们想确保在我们的配置中定义了某些水果,我们会像这样向水果列表添加 required=true

class Cfg : CfgNode {
    [Cfg(required=true)] // THERE MUST BE SOME FRUIT!
    public List<Fruit> Fruit { get; set; }
}

如果我们想确保水果名称是唯一的,我们可以像这样向水果名称属性添加 unique=true

class Fruit : CfgNode {
    [Cfg(unique=true)] // THE FRUIT MUST BE UNIQUE!
    public string Name { get; set; }
    [Cfg]
    public List<Color> Colors {get; set;}
}

如果我们想控制使用哪些颜色,我们可以像这样向颜色名称属性添加 domain="red,green,etc"

class Color : CfgNode {
    [Cfg(domain="red,yellow,green,blue,purple,orange")]
    public string Name {get; set;}
}

加载配置

现在我们有了模型以及我们选择的 JSON 或 XML 配置,我们可以像这样将配置加载到模型中

// let's say the configuration is in the xml variable
var cfg = new Cfg();
cfg.Load(xml);

检查配置

加载配置时,Cfg-NET 会检查错误和/或警告。

加载后,请务必使用 Errors()Warnings() 方法检查模型是否存在任何问题

//LOAD CONFIGURATION
var cfg = new Cfg();
cfg.Load(xml);

/* CHECK FOR WARNINGS */
Assert.AreEqual(0, cfg.Warnings().Length);

/* CHECK FOR ERRORS */
Assert.AreEqual(0, cfg.Errors().Length);

/* EVERYTHING IS AWESOME!!! */

根据约定,错误意味着配置无效。警告是您应该处理的事情,但程序仍应正常运行。

应将错误和警告报告给最终用户,以便他们修复。以下是一些示例错误

删除必需的水果,然后……

fruit 必须在 cfg 中填充。

再添加一个苹果,然后……

fruit 中重复 nameapple

添加粉色……

name 中发现无效值 pink。有效域为:red、yellow、green、purple、blue、orange。

如果 Cfg-NET 没有报告问题,则表示您的配置有效。您可以无忧无虑地遍历您的水果及其颜色

var cfg = new Cfg();
cfg.Load(xml);
    
foreach (var fruit in cfg.Fruit) {
    foreach (var color in fruit.Colors) {
        /* use fruit.Name and color.Name... */  
    }
}

您永远不必担心被 Cfg 装饰的列表为 null,因为它们在加载配置时被初始化了。此外,如果您设置了默认值(例如 [Cfg(value="default")]),则属性永远不会为 null

.NET Fiddle 上玩转苹果和香蕉。

自定义

Cfg 属性的可选属性提供了简单的验证。如果这不够,您还可以通过其他方式进行扩展

  1. 覆盖 PreValidate()
  2. 覆盖 Validate()
  3. 覆盖 PostValidate()

PreValidate()

如果您想在验证之前修改配置,请像这样覆盖 PreValidate()

protected override void PreValidate() {
    if (Provider == "Bad Words") {
        Provider = "Good Words. Ha!";
        Warn("Please watch your language.");
    }
}

Validate()

要执行涉及多个属性的验证,请像这样覆盖 Validate()

public class Connection : CfgNode {
    [Cfg(required = true, domain = "file,folder,other")]
    public string Provider { get; set; }
    
    [Cfg()]
    public string File { get; set; }
    
    [Cfg()]
    public string Folder { get; set; }
    
    /* CUSTOM VALIDATION */
    protected override void Validate() {
        if (Provider == "file" && string.IsNullOrEmpty(File)) {
            Error("file provider needs file attribute.");
        } else if (Provider == "folder" && string.IsNullOrEmpty(Folder)) {
            Error("folder provider needs folder attribute.");
        }
    }
}

当您覆盖 Validate 时,请使用 Error()Warn() 方法添加问题。

PostValidate()

覆盖 PostValidate 可让您在验证后运行代码。您可以检查 Errors() 和/或 Warnings() 并进行进一步的准备。

protected override void PostValidate() {
    if (Errors().Length == 0) {
        /* make further preparations... */
    }
}

自定义

如果属性和方法不够,您可以在模型的构造函数中注入自定义器(例如:实现 ICustomizer 的类)。

序列化

将配置加载到代码后,您可以使用 Serialize() 将其序列化回字符串。

// load
var cfg = new Cfg();
cfg.Load(xml);

// modify
cfg.Fruit.RemoveAll(f => f.Name == "apple");
cfg.Fruit.Add(new Fruit {
    Name = "plum",
    Colors = new List<Color> {
        new Color { Name = "purple" }
    }
});

// serialize
var result = cfg.Serialize();

这将产生一个结果

<cfg>
    <fruit>
        <add name="banana">
            <colors>
                <add name="yellow" />
            </colors>
        </add>
        <add name="plum">
            <colors>
                <add name="purple" />
            </colors>
        </add>
    </fruit>
</cfg>

使用代码配置和检查

加载配置很棒。但是,有时您需要用代码编写配置,并且仍然能够检查它是否存在错误和/或警告。为此,只需按您喜欢的方式创建对象,然后运行 Check 方法。

var cfg = new Cfg {
    Fruit = new List<Fruit> {
        new Fruit {
            Name = "Apple",
            Colors = new List<Color> {
                new Color {Name = "red"},
                new Color {Name = "aqua"}
            }
        }
    }
};

// Instead of using Load(), use Check()
cfg.Check();

// I put an error in there on purpose (hint: aqua is invalid)
Assert.AreEqual(1, cfg.Errors().Length);

结论

因此,如果您需要为您的程序提供真正出色的配置,请尝试 Cfg-NET。我在几乎所有编写的程序中都使用它,并且对此非常满意。感谢您花时间阅读本文。我非常感谢您的点赞和反馈。

鸣谢

  • 修改自 此处NanoXmlParser
  • 修改自 此处fastJSON
  • WebUtility.HtmlDecode 的 .NET 源代码 在此处,用作参考。
© . All rights reserved.