Using Unity to inject objects into IValueConverter instance
Asked Answered
I

2

7

I have an instance of IValueConverter in a Silverlight 5 project, which converts custom data into different colors. I need to read the actual color values from a database (as these can be edited by the user).

Since Silverlight uses asynchronous calls to load the data through Entity Framework from the database, I created a simple repository, which holds the values from the db.

The interface:

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

The implementation:

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];
        }
    }
}

Now I would like to use Unity to inject this instance of my repository into the IValueConverter instance...

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();
    }
}

The problem is, that I do not get the same Unity-Container in the converter instance (ie. the repository is not registered).

Indefinite answered 23/4, 2012 at 9:36 Comment(2)
How is an instance of your converter created? Do you set it in XAML?Homoousian
Yes. I set the value converter in the binding of the XAML-object (Foreground of a TextBox).Indefinite
H
0

According to your comment, you need to use a ServiceLocator to get an instance of your ConfigurationRepository, cause the instance of the Converter isn´t created by Unity, but by the Silverlight/XAML engine.

So your property that is decorated with the DependencyAttribute won´t be injected.

c#

public class SomeValueToBrushConverter : IValueConverter
{
    public SomeValueToBrushConverter(){
      ConfigurationRepository = ServiceLocator.Current.GetInstance<ConfigurationRepository>();
    }

    private ConfigurationRepository ConfigurationRepository { get; set; }
}

In your RegisterTypes method you need to configure the ServiceLocator:

_container = new UnityContainer();
UnityServiceLocator locator = new UnityServiceLocator(_container);
ServiceLocator.SetLocatorProvider(() => locator);
Homoousian answered 23/4, 2012 at 12:15 Comment(3)
This is not a "Dependency Injection" but "Service Locator" antipattern usage. This line of code is not easily testable.Hayseed
@GenaVerdel yes you are right, to make it testable you can add another constructor to pass in an instance of IConfigurationRepository. His question is not about Dependency Injectior or ServiceLocator, but how to inject values into an IValueConverter. And there´s no other way to use a ServiceLocator and DefaultCtor to inject dependencies cause the instance is created by WPF using the default ctor.Homoousian
You're correct, IValueConverter instantiated in XAML is IoC-unaware. That's why I recommend to refrain from using them at all whenever an external dependency issue arises. I would recommend using Dependency Property in this particular case. This approach will be testable while still providing the expected functionality. If this solution does not work for you, switch to Ambient Context.Hayseed
N
9

It is possible to use a MarkupExtension for resolving the dependencies from an DI container:

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; }
}

The IocResolver have to be initialized during application startup like:

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

After that, you can use it in XAML to inject dependency in 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}" />
Nyeman answered 12/1, 2017 at 11:18 Comment(0)
H
0

According to your comment, you need to use a ServiceLocator to get an instance of your ConfigurationRepository, cause the instance of the Converter isn´t created by Unity, but by the Silverlight/XAML engine.

So your property that is decorated with the DependencyAttribute won´t be injected.

c#

public class SomeValueToBrushConverter : IValueConverter
{
    public SomeValueToBrushConverter(){
      ConfigurationRepository = ServiceLocator.Current.GetInstance<ConfigurationRepository>();
    }

    private ConfigurationRepository ConfigurationRepository { get; set; }
}

In your RegisterTypes method you need to configure the ServiceLocator:

_container = new UnityContainer();
UnityServiceLocator locator = new UnityServiceLocator(_container);
ServiceLocator.SetLocatorProvider(() => locator);
Homoousian answered 23/4, 2012 at 12:15 Comment(3)
This is not a "Dependency Injection" but "Service Locator" antipattern usage. This line of code is not easily testable.Hayseed
@GenaVerdel yes you are right, to make it testable you can add another constructor to pass in an instance of IConfigurationRepository. His question is not about Dependency Injectior or ServiceLocator, but how to inject values into an IValueConverter. And there´s no other way to use a ServiceLocator and DefaultCtor to inject dependencies cause the instance is created by WPF using the default ctor.Homoousian
You're correct, IValueConverter instantiated in XAML is IoC-unaware. That's why I recommend to refrain from using them at all whenever an external dependency issue arises. I would recommend using Dependency Property in this particular case. This approach will be testable while still providing the expected functionality. If this solution does not work for you, switch to Ambient Context.Hayseed

© 2022 - 2024 — McMap. All rights reserved.