In MVVM there are lots of elaborate ways to make sure our XAML’s DataContext is correctly instantiated, and if you are using IoC then you should be letting the IoC container instantiate your viewmodel, which doesn’t fit naturally into the XAML view paradigm.
An easy way to do this is to create a XAML resource that will resolve your ViewModel for you, via your IoC container.
Many MVVM frameworks offer this feature, but I wanted one to specifically work with Autofac, so I have written on for this purpose.
Here’s my data provider class
public class AutofacDataProvider : DependencyObject { public static readonly DependencyProperty AutofacContainerProperty = DependencyProperty.Register("AutofacContainer", typeof(IContainer), typeof(AutofacDataProvider), new PropertyMetadata(null, AutofacValuesChanged)); public IContainer AutofacContainer { get { return (IContainer)GetValue(AutofacContainerProperty); } set { SetValue(AutofacContainerProperty, value); } } public static readonly DependencyProperty DataTypeNameProperty = DependencyProperty.Register("DataTypeName", typeof(string), typeof(AutofacDataProvider), new PropertyMetadata(null, AutofacValuesChanged)); public string DataTypeName { get { return (string)GetValue(DataTypeNameProperty); } set { SetValue(DataTypeNameProperty, value); } } private static readonly DependencyPropertyKey DataPropertyKey = DependencyProperty.RegisterReadOnly("Data", typeof(object), typeof(AutofacDataProvider), new PropertyMetadata(null)); public static readonly DependencyProperty DataProperty = DataPropertyKey.DependencyProperty; public object Data { get { return (object)GetValue(DataProperty); } private set { SetValue(DataProperty, value); } } private static void AutofacValuesChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var container = d.GetValue(AutofacContainerProperty) as IContainer; var typeName = d.GetValue(DataTypeNameProperty) as string; if (container == null || string.IsNullOrEmpty(typeName)) return; var assembly = Assembly.GetExecutingAssembly(); var type = assembly.GetType(typeName, false, true); if (type == null) return; var data = container.Resolve(type); d.SetValue(DataPropertyKey, data); } }
here’s an example of a viewmodel I want to instantiate
public interface IMainViewModel { string Name { get; set; } } public class MainViewModel : IMainViewModel, INotifyPropertyChanged { private string name = "test name"; public string Name { get { return name; } set { name = value; OnPropertyChanged("Name"); } } public event PropertyChangedEventHandler PropertyChanged; protected virtual void OnPropertyChanged (string propertyName) { PropertyChangedEventHandler handler = PropertyChanged; if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName)); } }
Here is where I wire up my container (you probably wouldn’t do it like this but this will do for our example)
public partial class App : Application { public static IContainer Container { get; private set; } protected override void OnStartup(StartupEventArgs e) { base.OnStartup(e); var builder = new ContainerBuilder(); builder.RegisterType<MainViewModel>().As<IMainViewModel>(); Container = builder.Build(); } }
And here it is in my XAML
<Window x:Class="WpfApplication7.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:app="clr-namespace:WpfApplication7" Title="MainWindow" Height="350" Width="525"> <Window.Resources> <app:AutofacDataProvider DataTypeName="WpfApplication7.IMainViewModel" AutofacContainer="{Binding Source={x:Static app:App.Container}}" x:Key="DataSource" /> </Window.Resources> <Grid DataContext="{Binding Source={StaticResource DataSource}, Path=Data}"> <TextBlock Text="{Binding Name}" /> </Grid> </Window>
Filed under: C#, MVVM, WPF
