This project is read-only.

WPF ViewModels and Design Time Data with DataContext

Mar 23, 2015 at 6:07 PM
Hi

I've looked at the example for using this in a WPF application over at read the docs - http://simpleinjector.readthedocs.org/en/latest/wpfintegration.html. However, I am wanting to use Design Time data in my Views.

My view models are using a data service that is injected into the constructor. This has a design time mock implementation and a run time get data from Sql Server implementation.

So for example ProjectDashboardViewModel has a single constructor that takes IProjectService as a parameter. There are two concrete implementations of this :

MockProjectStore
SqlProjectStore

So my boot strap code (in Program.cs) is as follows:
private static SimpleInjector.Container Bootstrap()
        {
            var container = new SimpleInjector.Container();

            // resources
            ResourceManagerService.RegisterManager("WmxResources", AttestedDev.Wmx.App.LocalResources.WmxResources.ResourceManager, true);

            // view models and views
            container.Register<ProjectDashboardViewModel>();
            container.Register<ProjectDashboardView>();
            container.Register<MainWindow>();

            // other services
            container.RegisterSingle<IMessengerService, Messenger>();
            
            if (DesignerProperties.GetIsInDesignMode(new DependencyObject()))
            {
                container.Register<ILogger, NoLogger>();
                container.Register<IProjectService, MockProjectStore>();
            }
            else
            {
                container.Register<ILogger, NoLogger>();
                container.Register<IProjectService>(
                    () => new SqlProjectStore(
                        "name=MyEFModel"),
                        SimpleInjector.Lifestyle.Transient);
            }

            container.Verify();

            return container;
        }
I cannot have a DataContext set in xaml if I do it this way:
<Page.DataContext>
        <vm:ProjectDashboardViewModel />
    </Page.DataContext>
There is an error message with this indicating ProjectDashboardViewModel does not have any accessible constructors which is true - there is no default constructor and the only constructor is:

public ProjectDashboardViewModel(IProjectService projectService)


Can I get design time data into my design time views?

Thanks

Andez
Mar 23, 2015 at 7:52 PM
Please can you show the whole exception/stack trace?
Mar 23, 2015 at 8:50 PM
Hi qujck,

This is the design time having the issue. The bootstrap will only run when the application is run.

The DataContext sample above is how I would do it typically. Alternatively if I wrapped up the View Models in a view model locator.

If your interested in the error message then it is just in the designer:

The name "ProjectDashboardViewModel" does not exist in the namespace "clr-namespace:ACME.Wm.ViewModel.Project". ACME.App\Views\Project\ProjectDashboardView.xaml

I'm still pretty new to WPF and just read a book on SOLID. I can see how the injection works and it is really neat. I was hoping you guys had something nice for SimpleInjector that could handle the scenario of Design Time data.

I put together a simple view model locator that I was trying to set design time bindings to.
public class DesignTimeViewModel
    {
        private readonly SimpleInjector.Container _container ;

        public DesignTimeViewModel()
        {
            _container = new SimpleInjector.Container();

            // view models and views
            _container.Register<ProjectDashboardViewModel>(SimpleInjector.Lifestyle.Transient);
            _container.Register<ProjectDashboardView>();
            _container.Register<MainWindow>();
            _container.Register<ILogger, NoLogger>();
            _container.Register<IProjectService, MockProjectStore>();
        }

        public ProjectDashboardViewModel ProjectDashboardViewModel
        {
            get
            {
                var model = _container.GetInstance<ProjectDashboardViewModel>();
                model.Load();
                return model;
            }
        }
    }
With the DataContext set to d:DataContext="{Binding Source=DesignTimeViewModel, Path=ProjectDashboardViewModel}" - but that does not present any design time data. I just need some ideas what the best way to do this would be.

Thanks

Andez
Mar 24, 2015 at 12:07 AM
I think this is kind of what I was wanting:

Deeper integration SI with WPF

I can now put the same extension class in - ContainerExtension.

And declare the design time DataContext as:
d:DataContext="{ext:Container Type=vm:ProjectDashboardViewModel}"
I changed the Program.cs to the following:
static class Program
    {
        [STAThread]
        static void Main()
        {
            var container = Bootstrap();

            RunApplication(container);
        }

        private static SimpleInjector.Container Bootstrap()
        {
            var container = new Bootstrap();
            container.Verify();

            return container;
        }

        private static void RunApplication(SimpleInjector.Container container)
        {
            try
            {
                var app = new App();
                var mainWindow = container.GetInstance<MainWindow>();

                if (app.Resources.Contains("BootstrapViewModelLocator"))
                    app.Resources.Remove("BootstrapViewModelLocator");

                mainWindow.PageHost.Navigate(container.GetInstance<ProjectDashboardView>());
                app.Run(mainWindow);
            }
            catch (Exception ex)
            {
                if (container != null)
                    container.GetInstance<ILogger>().Error("Application failed to start", ex);
            }
        }
    }
I have 2 bootstrap classes to deal with my registrations:
public class Bootstrap : SimpleInjector.Container
    {
        public Bootstrap()
        {
            // resources
            ResourceManagerService.RegisterManager("WmxResources", ACMEApp.App.LocalResources.ACMEAppResources.ResourceManager, true);

            // view models and views
            Register<ProjectDashboardViewModel>(SimpleInjector.Lifestyle.Transient);
            Register<ProjectDashboardView>();
            Register<MainWindow>();

            // other services
            RegisterSingle<IMessengerService, Messenger>();
            
            if (DesignerProperties.GetIsInDesignMode(new DependencyObject()))
            {
                Register<ILogger, NoLogger>();
                Register<IProjectService, MockProjectStore>();
            }
            else
            {
                Register<ILogger, NoLogger>();
                Register<IProjectService>(
                    () => new SqlProjectStore(
                        "name=ACMEAppModel"),
                        SimpleInjector.Lifestyle.Transient);
            }
        }
    }
And then a design-time purposeful class to access the view model:
public class BootstrapViewModelLocator : Bootstrap
    {
        private static readonly BootstrapViewModelLocator _instance;

        static BootstrapViewModelLocator()
        {
            if (_instance == null)
                _instance = new BootstrapViewModelLocator();
        }

        public static object GetViewModelInstance(Type t)
        {
            return _instance.GetInstance(t);
        }

        public ProjectDashboardViewModel ProjectDashboardViewModel
        {
            get
            {
                return this.GetInstance<ProjectDashboardViewModel>();
            }
        }
    }
With the extension class to resolve the view model type via DI:
public class ContainerExtension : MarkupExtension
    {
        public Type Type { get; set; }

        // ProvideValue, which returns an object instance from the container
        public override object ProvideValue(IServiceProvider serviceProvider)
        {
            if (serviceProvider == null)
                throw new ArgumentNullException("serviceProvider");

            if (Type == null)
                throw new NullReferenceException("Type");

            return BootstrapViewModelLocator.GetViewModelInstance(Type);
        }
    }
So my ViewModel declaration is as recommended:
public class ProjectDashboardViewModel : ViewModelBase
{
        public ProjectDashboardViewModel(IProjectService projectService)
        {
            // do something
        }
}
And View (Page)
public partial class ProjectDashboardView : Page
    {
        public ProjectDashboardView(ProjectDashboardViewModel model)
        {
            InitializeComponent();
            this.DataContext = model;
        }
    }
I'm just trying to fine tune this now. Any recommendations appreciated.

Thanks

Andez
Mar 24, 2015 at 12:07 AM
I think this is kind of what I was wanting:

Deeper integration SI with WPF

I can now put the same extension class in - ContainerExtension.

And declare the design time DataContext as:
d:DataContext="{ext:Container Type=vm:ProjectDashboardViewModel}"
I changed the Program.cs to the following:
static class Program
    {
        [STAThread]
        static void Main()
        {
            var container = Bootstrap();

            RunApplication(container);
        }

        private static SimpleInjector.Container Bootstrap()
        {
            var container = new Bootstrap();
            container.Verify();

            return container;
        }

        private static void RunApplication(SimpleInjector.Container container)
        {
            try
            {
                var app = new App();
                var mainWindow = container.GetInstance<MainWindow>();

                if (app.Resources.Contains("BootstrapViewModelLocator"))
                    app.Resources.Remove("BootstrapViewModelLocator");

                mainWindow.PageHost.Navigate(container.GetInstance<ProjectDashboardView>());
                app.Run(mainWindow);
            }
            catch (Exception ex)
            {
                if (container != null)
                    container.GetInstance<ILogger>().Error("Application failed to start", ex);
            }
        }
    }
I have 2 bootstrap classes to deal with my registrations:
public class Bootstrap : SimpleInjector.Container
    {
        public Bootstrap()
        {
            // resources
            ResourceManagerService.RegisterManager("WmxResources", ACMEApp.App.LocalResources.ACMEAppResources.ResourceManager, true);

            // view models and views
            Register<ProjectDashboardViewModel>(SimpleInjector.Lifestyle.Transient);
            Register<ProjectDashboardView>();
            Register<MainWindow>();

            // other services
            RegisterSingle<IMessengerService, Messenger>();
            
            if (DesignerProperties.GetIsInDesignMode(new DependencyObject()))
            {
                Register<ILogger, NoLogger>();
                Register<IProjectService, MockProjectStore>();
            }
            else
            {
                Register<ILogger, NoLogger>();
                Register<IProjectService>(
                    () => new SqlProjectStore(
                        "name=ACMEAppModel"),
                        SimpleInjector.Lifestyle.Transient);
            }
        }
    }
And then a design-time purposeful class to access the view model:
public class BootstrapViewModelLocator : Bootstrap
    {
        private static readonly BootstrapViewModelLocator _instance;

        static BootstrapViewModelLocator()
        {
            if (_instance == null)
                _instance = new BootstrapViewModelLocator();
        }

        public static object GetViewModelInstance(Type t)
        {
            return _instance.GetInstance(t);
        }

        public ProjectDashboardViewModel ProjectDashboardViewModel
        {
            get
            {
                return this.GetInstance<ProjectDashboardViewModel>();
            }
        }
    }
With the extension class to resolve the view model type via DI:
public class ContainerExtension : MarkupExtension
    {
        public Type Type { get; set; }

        // ProvideValue, which returns an object instance from the container
        public override object ProvideValue(IServiceProvider serviceProvider)
        {
            if (serviceProvider == null)
                throw new ArgumentNullException("serviceProvider");

            if (Type == null)
                throw new NullReferenceException("Type");

            return BootstrapViewModelLocator.GetViewModelInstance(Type);
        }
    }
So my ViewModel declaration is as recommended:
public class ProjectDashboardViewModel : ViewModelBase
{
        public ProjectDashboardViewModel(IProjectService projectService)
        {
            // do something
        }
}
And View (Page)
public partial class ProjectDashboardView : Page
    {
        public ProjectDashboardView(ProjectDashboardViewModel model)
        {
            InitializeComponent();
            this.DataContext = model;
        }
    }
I'm just trying to fine tune this now. Any recommendations appreciated.

Thanks

Andez
Mar 24, 2015 at 12:33 AM
What you are trying to do is possible. The question is, if what you want to do is not going to lead you straight into a maintenance problem, but more about that later.

The XAML designer isn't capable of creating an instance which has a non default constructor. The trick is to use a ViewModelLocator class which exposes a property for each ViewModel which you want to use as DesignInstance of your DataContext. You could also use an IEnumerable of some kind, but I think this will become even more fragile. By adding this ViewModelLocator class to the App Resources you can bind your DesignTime datacontext to the view using pretty straightforward binding concepts.

I created a POC project (starting with the code from the documentation) for this to test, so let me share some code. I changed the Bootstrap() method to:
public static Container Bootstrap(bool isInDesignMode)
{
    var container = new Container();

    if (isInDesignMode)
    {
        container.RegisterSingle<IUserRepository, DesignUserRepository>();
    }
    else
    {
        container.RegisterSingle<IUserRepository, SqlUserRepository>();
    }

    container.Register<MainWindow>();
    container.Register<MainWindowViewModel>();
            
    container.Verify();

    return container;
}
In the normal flow (coming from the static void Main this method is called with isInDesignMode = false.

I added a ViewModelLocator class:
public class ViewModelLocator
{
    private Container container;

    public ViewModelLocator()
    {
        this.container = Program.Bootstrap(isInDesignMode: true);
    }

    public MainWindowViewModel MainWindowViewModel
    {
        get { return this.container.GetInstance<MainWindowViewModel>(); }
    }
}
Now we can add this class as a resource to the Application object in App.xaml file:
<Application.Resources>
    <local:ViewModelLocator x:Key="ViewModelLocator"/>
</Application.Resources>
Now we can use this class in our MainWindow to have designtime support on our bindings and show the data coming from a 'fake' dataservice implementation:
<Window x:Class="WpfTest.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        mc:Ignorable="d"
        d:DataContext="{Binding MainWindowViewModel, Source={StaticResource ViewModelLocator}}"
        >
    <Grid>
      <TextBox Text="{Binding Path=User, Mode=OneWay}" />
    </Grid>
</Window>
For the sake of completeness my DesignUSerRepository now looks like this:
internal class DesignUserRepository : IUserRepository
{
    public string UserName { get { return "DesignTimeUser is here"; } }
}
This is all there is to it. It probably works in most cases.

If you want to mimic loading of data which would be normally called in a e.g. LoadData() method, you can add a service to your project which returns if DesignMode is currently active:
internal class DesignModeDeterminationService
{
    public bool IsInDesignMode
    {
        get
        {
        return (bool)DesignerProperties.IsInDesignModeProperty.GetMetadata(typeof(DependencyObject)).DefaultValue;
        }
    }
}

    //register like
    container.RegisterSingle(new DesignModeDeterminationService());
And use this in the constructor of some ViewModel;
    public MainWindowViewModel(
        IUserRepository userRepository,
        DesignModeDeterminationService designModeDeterminationService)
    {
        this.userRepository = userRepository;
#if DEBUG
        if (designModeDeterminationService.IsInDesignMode)
        {
            this.LoadData();
        }
#endif
    }
So that all being said some remarks from my side on willing to do this or not.

First off all, the ViewModelLocator class will grow rapidly. Or you need to find a way to do this in smarter way using an IEnumerable or Dictionary.

Secondly, instead of mocking your DataService you could also mock the viewmodel, or create a second implementation using only the properties which need to be bound. These implementations would be typically very small considering that creating a SOLID design which would lead to small ViewModels. Such a viewmodel can be bound to the view far easier at designtime using a DesignInstance like this:
d:DataContext="{d:DesignInstance local:YourMockedViewModel, IsDesignTimeCreatable=True}"
Last but not least. You can leave the design time support all together and use some MVVM toolkit like Caliburn Micro to bind your controls using Convention Over Configuration and write a unit test if all properties are actually in the viewmodel. This will provide a great feeling of flexibility, speed of development and still ensures all binding will be there at runtime. How the view will look at runtime is missing in this setup, but from my experience it is hardly ever possibly to create testdata that will actually mimic the data you get to show in production.