结合 Castle Windsor 与配置数据

Combining Castle Windsor with configuration data

在我的项目中,我有一些对象用作多个服务的一部分。以下是该架构的一个简短示例(有意省略了接口):

public class Plc : IPlc
{
    public Plc(string connectionString) { ... }
}

public class PlcRegister
{
    public string Address { get; set; }
    public int Length { get; set; }
}

public class PlcJobWriter : IPlcJobWriter
{
    public PlcJobWriter(IPlc plc,
        PlcRegister commandRegister,
        PlcRegister statusRegister,
        ...)
    { ... }
}

public class JobService : IJobService
{
    public JobService(Dictionary<string, PlcJobWriter> plcJobWriters) { ... }
}

public class PlcProgramWriter : IPlcProgramWriter
{
    public PlcProgramWriter(IPlc plc,
        PlcRegister commandRegister,
        PlcRegister statusRegister,
        ...)
    { ... }
}

public class ProgramService : IProgramService
{
    public ProgramService(List<PlcProgramWriter> plcProgramWriters) { ... }
}

我在生产中得到的是一个或多个PLC。每个 Plc 将有一个 PlcProgramWriter 和一个或多个 PlcJobWriter。任何程序更新都将写入所有 PlcProgramWriters(因此 ProgramService 中的 plcProgramWriters 参数和作业将写入匹配的 PlcJobWriter,匹配 plcJobWriters 中的键在 JobService 构造函数中。

我想要 Plc connectionString 所需的值以及 PlcJobWriterPlcProgramWriter 的 commandRegister 和 statusRegister 所需的值可通过 app.config 配置,以及PlcJobWriter 用于查找匹配的 JobWriter。

我可以只定义我自己的配置模式并编写代码来处理配置数据,但我更感兴趣的是如果我想让 Windsor 处理所有这些数据,我可以采取什么方法。我不介意添加基础设施来实现这一点,但我根本找不到此类解决方案的任何示例(可能是在寻找错误的东西)。

这并没有真正回答所问的问题,但是如果通过输入对 web.config 值的访问权限满足了您的要求之一,那么您有两个选择。

一个, 你可以创建一个派生自 ConfigurationSection 的 class,这需要你在 web.config <configSections>,呃,节,像这样:

<section name="sectionName" type="Fully.Qualified.Namespace.CustomSettings, YourAssemblyName" />

然后该部分本身看起来像:

<customSettings propertyName1="somevalue" />

它的优点是能够通过数据注释强制执行一些规则。这是我曾经为 Akismet 助手使用的一个实现:

public class AkismetSettings : ConfigurationSection
{
    private static AkismetSettings settings = ConfigurationManager.GetSection("akismet") as AkismetSettings;

    public static AkismetSettings Settings
    {
        get
        {
            return settings;
        }
    }

    [ConfigurationProperty("key", IsRequired = true)]
    public string Key
    {
        get { return (string)this["key"]; }
        set { this["key"] = value; }
    }


    [ConfigurationProperty("registeredsite", IsRequired = true)]
    public string RegisteredSite
    {
        get { return (string)this["registeredsite"]; }
        set { this["registeredsite"] = value; }
    }
}

及其相关部分:

<akismet key="0000000000" registeredsite="http://example.com/" />

然后我会像这样使用它:

var helper = new AkismetHelper(AkismetSettings.Settings.Key, AkismetSettings.Settings.RegisteredSite);

我对助手没有持久的需求,通过 Windsor 解析实例似乎有些过分,所以我只是在需要时才重新安装一个。

二, 我更喜欢的选项是简单地创建一个 class 从 <appSettings> 读取相关值并进行必要的类型转换。

这是我目前使用的精简版。

public class AppSettings
{
    public static int MinPasswordLength
    {
        get
        {
            //  Recommended minimum password length.
            //  See https://www.owasp.org/index.php/Password_length_%26_complexity
            int min = 8;

            if (!string.IsNullOrWhiteSpace(ConfigurationManager.AppSettings["app.minPasswordLength"]))
            {
                min = int.Parse(ConfigurationManager.AppSettings["app.minPasswordLength"]);
            }

            return min;
        }
    }

    /* ... */

    public static string Find(string key)
    {
        return ConfigurationManager.AppSettings[key] ?? string.Empty;
    }
}

您可以使用相同的想法为 <connectionStrings> 部分编写一个更简洁的访问器。如果你想使用 <appSettings>,但不想在 web.config 中有很多值,记住 <appSettings> 有一个可以使用的 file 属性会有所帮助link 到另一个 .config 文件,该文件还包含 <appSettings> 部分:

<appSettings file="settings.config">

我认为这应该可以通过以下方法实现:

首先,您需要在 web.config 中的 configSections 中使用它。名字可以是任何东西:

<section name="castle" type="Castle.Windsor.Configuration.AppDomain.CastleSectionHandler, Castle.Windsor" />

然后在 web.config 中创建包含 DI 配置的 castle 部分。根据您的代码和对您的要求的描述,我已尝试在此处提供最好的示例。重要的一点是您使用 ${ } 语法通过它们的 ID 引用其他组件,类型名称将需要完全限定名称空间和程序集,并且如果您为每个接口定义了多个实现那么你可能必须通过按键而不是接口类型来解决。

<castle>
    <properties>
        <connectionString>YourConnectionStringHere</connectionString>
    </properties>

    <components>
         <component id="PlcA" service="Namespace.IPlc, AssemblyName" type="Namespace.Plc, AssemblyName">
             <parameters>
                 <connectionString>${connectionString}</connectionString>
             </parameters>
         </component>
         <!-- Other IPlc implementations as required -->

         <component id="PlcJobWriterA" service="Namespace.IPlcJobWriter, AssemblyName" type="Namespace.PlcJobWriter, AssemblyName">
              <parameters>
                  <plc>${PlcA}</plc>
                  <commandRegister>
                      <parameters>
                          <address>CommandRegisterAddress</address>
                          <length>CommandRegisterLength</length>
                      <parameters>
                   </commandRegister>
                   <statusRegister>
                      <parameters>
                          <address>StatusRegisterAddress</address>
                          <length>StatusRegisterLength</length>
                      <parameters>
                   </statusRegister>
              </parameters>
         </component>
         <!-- Other PlcJobWriters as required -->
         <!-- PlcProgramWriter can be configured in the same way -->

         <component id="JobService" service="Namespace.IJobService, AssemblyName" type="Namespace.JobService, AssemblyName">
             <parameters>
                 <plcJobWriters>
                     <dictionary keyType="System.String" valueType="Namespace.IPlcJobWriter, AssemblyName">
                         <entry key="JobWriterA">${PlcJobWriterA}</entry>
                         <!-- Other entries as required -->
                     </dictionary>
                 </plcJobWriters>  
             </parameters>
         </component>

         <component id="ProgramService" service="Namespace.IProgramService, AssemblyName" type="Namespace.ProgramService, AssemblyName">
             <parameters>
                 <plcProgramWriters>
                     <list>
                         <item>${ProgramWriterA}</item>
                         <!-- Other items as required -->
                     </list>
                 </plcProgramWriters>
             </parameters>
         </component>
    </components>
</castle>

然后您将在创建 Windsor 容器时将配置部分的名称传递给它:

using Castle.Core.Resource;
using Castle.Windsor;
using Castle.Windsor.Configuration.Interpreters;

...

using (var castleConfig = new ConfigResource("castle"))
{
     container = new WindsorContainer(new XmlInterpreter(castleConfig));
}   

显然这不是一个完整的工作解决方案,但希望它能说明如何使用 Castle 的 XML 配置。 Castle 的错误消息往往可以提供有关任何错误信息的信息。可以在此处找到更多信息:http://docs.castleproject.org/Windsor.XML-Registration-Reference.ashx