如何处理在运行时使用依赖项注入加载的配置类?

时间:2016-01-10 18:23:07

标签: c# mvvm dependency-injection

我目前正尝试使用依赖注入,到目前为止,我喜欢。但是,有一件事我无法理解我现在的解决方案似乎错了。

我正在使用WPF,MVVM,我注入的许多类需要一个项目配置类的实例,在用户在应用程序中创建或打开一个新项目之前,该实例未初始化。

所以我目前的解决方案是使用带有加载/保存方法的“ConfigurationHandler”和一个在加载后保存配置类实例的属性。我将ConfigurationHandler注入其他类,然后他们可以在加载后访问配置。但是让那些永远不应该保存/加载配置的类处理整个“ConfigurationHandler”并且100%他们只是用它来访问配置实例似乎很奇怪:

var configuration = configurationHandler.Configuration; 

另一个问题是,如果他们在加载之前尝试访问配置,他们将获得异常(不应该真正发生,因为在创建/加载项目之前你不能做任何事情,但仍然如此)。

但我能想到的唯一其他解决方案是在创建/打开项目后使用“初始化”方法,但这看起来同样糟糕。

那么你通常如何处理这样的案件?

编辑:应该添加此配置类处理项目路径,项目名称等信息,这与依赖注入本身无关。

2 个答案:

答案 0 :(得分:0)

考虑使用Ambient Context方法,而不是构造函数注入。

  

我们将讨论的最后一种DI是使依赖性可用   通过静态访问器。它也被称为注射   环境背景。在实施跨领域问题时使用它。

如果需要访问您的配置的类在不同的层或库中具有不同的类型,这是一个很好的选择 - 这是一个真正的跨领域问题。

Quote source

示例,基于[.NET中的依赖注入] [2]

中的经典时间提供程序
abstract class CustomConfiguration
{
    //current dependency stored in static field
    private static CustomConfiguration current;

    //static property which gives access to dependency
    public static CustomConfiguration Current
    {
        get
        {
            if (current == null)
            {
                //Ambient Context can't return null, so we assign a Local Default
                current = new DefaultCustomConfiguration();
            }

            return current;
        }

        set
        {
            //allows to set different implementation of abstraction than Local Default
            current = (value == null) ? new DefaultCustomConfiguration() : value;
        }
    }

    //service which should be override by subclass
    public virtual string SomeSetting { get; }
}

//Local Default
class DefaultCustomConfiguration : CustomConfiguration
{
    public override string SomeSetting
    {
        get { return "setting"; }
    }
}

用法

CustomConfiguration.Current.SomeSetting;

可以使用其他DI Patterns,但需要更改需要它的类。如果配置是一个跨领域问题,环境上下文可能是最合适的。

构造函数注入示例

public SomeClass(IConfiguration config)
{
}

物业注入

public SomeClass()
{
   IConfiguration configuration { get; set; }
}

方法注入

public SomeClass()
{
   public void DoSomethingNeedingConfiguation(IConfiguration config)
   {
   }
}

还有服务定位器,但服务定位器是(IMO)anti-pattern

答案 1 :(得分:0)

如果您的配置是静态的(读取:它仅在启动应用程序期间读取,例如从project.jsonWeb.Config),您也可以在应用启动期间/组合根目录中进行设置。

新的ASP.NET 5大量使用它并且它运行良好。基本上,您将拥有一个IConfiguration<T>接口和一个POCO类,您可以在应用启动期间设置该类,并将其解析/注入您的服务。

public interface IConfiguration<T> where T : class
{
    T Configuration { get; }
}

这是默认实现

public interface DefaultConfiguration<T> where T : class
{
    private readonly T configuration;
    public T Configuration { 
        return configuration;
    }

    public DefaultConfiguration<T>(T config) 
    {
        this.configuration = this.configuration;
    }
}

和你的POCO课程

public class AppConfiguration 
{
    public string OneOption { get; set; }
    public string OtherOption { get; set; }
}

在你的作品根目录中,你会注册它,比如

// read Web.Config
Configuration rootWebConfig = System.Web.Configuration.WebConfigurationManager.OpenWebConfiguration(null);

container.AddSingleton<IConfiguration<AppConfiguration>>(new DefaultConfiguration<AppConfiguration>(
    new AppConfiguration
    {
        OneOption = rootWebConfig.AppSettings.Settings["oneSetting"],
        OtherOption = rootWebConfig.AppSettings.Settings["otherSetting"],
    })
);

最后,您必须在服务中声明的是

public class MyService : IMyService 
{
    public MyService(IUserRepository, IConfiguration<AppConfiguration> appConfig) 
    {
        ...
        if(appConfig.OneOption=="someValue") {
            // do something
        };
    }
}

最后,如果编写像

这样的扩展方法,可以使配置更容易一些
public static class MyContainerExtension 
{
    public static void Configure<T>(this IMyContainer container, Action<T> config) where T : class, new()
    {
        var t = new T();
        config(t);
        container.AddSingelton<IConfiguration<T>>(t);
    }
}

然后您需要做的就是

container.Configure<AppConfiguration>(
    config => 
    {
        config.OneOption = rootWebConfig.AppSettings.Settings["oneSetting"],
        config.OtherOption = rootWebConfig.AppSettings.Settings["otherSetting"],
    })
);

进行设置