使用Unity将对象注入到IValueConverter实例中

7

我在一个Silverlight 5项目中有一个IValueConverter实例,它可以将自定义数据转换为不同的颜色。我需要从数据库中读取实际的颜色值(因为这些值可以由用户编辑)。

由于Silverlight使用异步调用通过Entity Framework从数据库加载数据,所以我创建了一个简单的存储库,它保存来自数据库的值。

接口:

public interface IConfigurationsRepository
{
    string this[string key] { get; }
}

实现方式:
public class ConfigurationRepository : IConfigurationsRepository
{
    private readonly TdTerminalService _service = new TdTerminalService();

    public ConfigurationRepository()
    {
        ConfigurationParameters = new Dictionary<string, string>();
        _service.LoadConfigurations().Completed += (s, e) =>
            {
                var loadOperation = (LoadOperation<Configuration>) s;
                foreach (Configuration configuration in loadOperation.Entities)
                {
                    ConfigurationParameters[configuration.ParameterKey] = configuration.ParameterValue;
                }
            };
    }

    private IDictionary<string, string> ConfigurationParameters { get; set; }

    public string this[string key]
    {
        get
        {
            return ConfigurationParameters[key];
        }
    }
}

现在我想使用Unity将我的存储库实例注入到IValueConverter实例中...
App.xaml.cs:
private void RegisterTypes()
{
    _container = new UnityContainer();
    IConfigurationsRepository configurationsRepository = new ConfigurationRepository();
    _container.RegisterInstance<IConfigurationsRepository>(configurationsRepository);
}

IValueConverter:

public class SomeValueToBrushConverter : IValueConverter
{
    [Dependency]
    private ConfigurationRepository ConfigurationRepository { get; set; }

    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
       switch ((SomeValue)value)
        {
            case SomeValue.Occupied:
                return new SolidColorBrush(ConfigurationRepository[OccupiedColor]);
            default:
                throw new ArgumentException();
        }
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

问题在于,我在转换器实例中无法获得相同的Unity容器(即存储库未注册)。

你的转换器实例是如何创建的?你是在XAML中设置它吗? - Jehof
是的。我在 XAML 对象的绑定中设置了值转换器(TextBox 的前景)。 - froeschli
2个回答

9

可以使用 MarkupExtension 来从 DI 容器中解析依赖项:

public class IocResolver : MarkupExtension
{
    public IocResolver()
    { }

    public IocResolver(string namedInstance)
    {
        NamedInstance = namedInstance;
    }

    [ConstructorArgument("namedInstance")]
    public string NamedInstance { get; set; }

    public override object ProvideValue(IServiceProvider serviceProvider)
    {
        var provideValueTarget = (IProvideValueTarget)serviceProvider
            .GetService(typeof(IProvideValueTarget));

        // Find the type of the property we are resolving
        var targetProperty = provideValueTarget.TargetProperty as PropertyInfo;

        if (targetProperty == null)
            throw new InvalidProgramException();

        Debug.Assert(Resolve != null, "Resolve must not be null. Please initialize resolving method during application startup.");
        Debug.Assert(ResolveNamed != null, "Resolve must not be null. Please initialize resolving method during application startup.");

        // Find the implementation of the type in the container
        return NamedInstance == null
            ? (Resolve != null ? Resolve(targetProperty.PropertyType) : DependencyProperty.UnsetValue)
            : (ResolveNamed != null ? ResolveNamed(targetProperty.PropertyType, NamedInstance) : DependencyProperty.UnsetValue);
    }

    public static Func<Type, object> Resolve { get; set; }
    public static Func<Type, string, object> ResolveNamed { get; set; }
}

在应用程序启动时必须初始化IocResolver,如下所示:

IocResolver.Resolve = kernel.Get; 
IocResolver.ResolveNamed = kernel.GetNamed;
// or what ever your DI container looks like

之后,您可以在XAML中使用它来注入依赖项:

<!-- Resolve an instance based on the type of property 'SomeValueToBrushConverter' -->
<MyConverter SomeValueToBrushConverter="{services:IocResolver}" />

<!-- Resolve a named instance based on the type of property 'SomeValueToBrushConverter' and the name 'MyName' -->
<MyConverter SomeValueToBrushConverter="{services:IocResolver  NamedInstance=MyName}" />

0
根据您的评论,您需要使用ServiceLocator来获取ConfigurationRepository的实例,因为Converter的实例不是由Unity创建的,而是由Silverlight/XAML引擎创建的。
因此,用DependencyAttribute装饰的属性将不会被注入。
c#
public class SomeValueToBrushConverter : IValueConverter
{
    public SomeValueToBrushConverter(){
      ConfigurationRepository = ServiceLocator.Current.GetInstance<ConfigurationRepository>();
    }

    private ConfigurationRepository ConfigurationRepository { get; set; }
}

在你的RegisterTypes方法中,你需要配置ServiceLocator。
_container = new UnityContainer();
UnityServiceLocator locator = new UnityServiceLocator(_container);
ServiceLocator.SetLocatorProvider(() => locator);

2
这不是“依赖注入”而是“服务定位器”反模式的使用。这行代码不容易进行测试。 - Gena Verdel
@GenaVerdel 是的,你说得对,为了使它可测试,你可以添加另一个构造函数来传递 IConfigurationRepository 的实例。他的问题不是关于依赖注入或服务定位器,而是如何将值注入到 IValueConverter 中。没有其他方法可以使用 ServiceLocator 和 DefaultCtor 来注入依赖项,因为实例是由 WPF 使用默认构造函数创建的。 - Jehof
你说得对,XAML中实例化的IValueConverter不具备IoC感知能力。因此,我建议在出现外部依赖问题时尽量避免使用它们。 我建议在这种情况下使用Dependency Property。这种方法既可测试,又可提供预期的功能。 如果这个解决方案对你不起作用,可以切换到Ambient Context。 - Gena Verdel

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接