Sometimes you will build custom controls that you wish to create in XAML, but those controls may have a dependency on a service which you want to be provided by your dependency injection container.
Assuming your control is declared like this:
class CurrencyControl : Control
{
public ICurrencyConverter CurrencyConverter { get; set; }
public ICurrencyDescriptor FromCurrency { get; set; }
public ICurrencyDescriptor ToCurrency { get; set; }
}
(Of course, each of those properties would be dependency properties.)
You could declare it in XAML:
<z:CurrencyControl
x:Name="_currency"
FromCurrency="{Binding Path=SelectedProduct.Currency}"
ToCurrency="{Binding Path=UserProfile.Currency}"
/>
And then set the property using codebehind:
public Window1()
{
InitializeComponent();
_currency.CurrencyConverter = ServiceLocator.Current.GetInstance<ICurrencyConverter>();
}
A nicer way to do this is to use a MarkupExtension to tell the container to resolve the service. It might look like this:
<z:CurrencyControl
CurrencyConverter="{z:ResolveDependency Type={x:Type z:ICurrencyConverter}}"
FromCurrency="{Binding Path=SelectedProduct.Currency}"
ToCurrency="{Binding Path=UserProfile.Currency}"
/>
Or, since the markup extension has access to the property that the value is being set on, you could infer the correct service type to resolve based on the property type, leaving:
<z:CurrencyControl
CurrencyConverter="{z:ResolveDependency}"
FromCurrency="{Binding Path=SelectedProduct.Currency}"
ToCurrency="{Binding Path=UserProfile.Currency}"
/>
Such a markup extension would look like this:
[MarkupExtensionReturnType(typeof(object))]
public class ResolveDependency : MarkupExtension
{
public ResolveDependency()
{ }
public ResolveDependency(Type type)
{
Type = type;
}
[ConstructorArgument("type")]
public Type Type { get; set; }
public override object ProvideValue(IServiceProvider serviceProvider)
{
if (serviceProvider != null)
{
var provideValueTarget = (IProvideValueTarget)serviceProvider.GetService(typeof(IProvideValueTarget));
if (provideValueTarget != null)
{
var targetObject = provideValueTarget.TargetObject as DependencyObject;
if (targetObject != null)
{
// If the type isn't specified, infer it from the property being assigned
var resolve = Type;
if (resolve == null)
{
if (provideValueTarget.TargetProperty is DependencyProperty)
{
resolve = ((DependencyProperty) provideValueTarget.TargetProperty).PropertyType;
}
}
if (resolve == null)
{
throw new Exception("...");
}
return ServiceLocator.Current.GetInstance(resolve);
}
}
}
return null;
}
}
In fact, you could go so far as to resolve a whole control this way:
<ContentPresenter Content="{z:ResolveDependency Type={x:Type z:IUserView}}" />
You can go further by adding a design-time value, for when your IOC container is not available:
public Type DesignTime { get; set; }
// In your MarkupExtension:
// At design time, return an instance of the DesignTime type
var isDesignMode = DesignerProperties.GetIsInDesignMode(targetObject);
if (isDesignMode && DesignTime != null)
{
try
{
return Activator.CreateInstance(DesignTime, true);
}
catch (Exception ex)
{
throw new Exception(string.Format("An exception occured while evaluating {0}. The DesignTime type '{1}' could not be instantiated. Please see the InnerException for more details. Message: {2}", this.ToString(), DesignTime.ToString(), ex.Message), ex);
}
}
Making the XAML:
<z:CurrencyControl
CurrencyConverter="{z:ResolveDependency Type={x:Type z:ICurrencyConverter}, DesignTime={x:Type z:DesignerCurrencyConverter}"
FromCurrency="{Binding Path=SelectedProduct.Currency}"
ToCurrency="{Binding Path=UserProfile.Currency}"
/>
If your IOC container is not available as a static object, one approach would be to use an attached inherited dependency property, and to set that at the root of your Window.
Filed under: WPF | 7 Comments »