2

I read a book about Dependency injection from Mark Seemann and Steven van Deursen and I'm trying to practice what I learned by programming a WPF application in C#.

In my application I need to create custom UserControls at runtime based on what the user is doing. Each custom UserControl naturally needs its own DataContext.

So my custom UserControls might look like this:

class ImageComponent : UserControl
{
    public ImageComponent(object dataContext)
    {
        InitializeComponent();
        DataContext = dataContext;
    }
}

class TextComponent : UserControl
{
    public TextComponent(object dataContext)
    {
        InitializeComponent();
        DataContext = dataContext;
    }
}

and of course the view models. For simplicity I omitted all dependencies if any

class ImageComponentVM : ViewModelBase
{
    // Image property...
}

class TextComponentVM : ViewModelBase
{
     // Text property...
}

Now that I have defined the UserControls and their view models, I need to create their instances somewhere.

I was thinking about something like this, so that the component producer wouldn't have to decide what type it needs to create. The component producer would also contain other logic which I again omitted. Instead of the 'Type' as the Dictionary's key, enum would also do the thing.

class ComponentProducer : IComponentProducer
{
    private Dictionary<Type, Func<object>> dataContextsDictionary;
    private Dictionary<Type, Func<object, FrameworkElement>> componentDictionary;

    public ComponentProducer(Dictionary<Type, Func<object>> dataContextsDictionary,
                             Dictionary<Type, Func<object, FrameworkElement>> componentDictionary)
    {
        this.dataContextsDictionary = dataContextsDictionary;
        this.componentDictionary = componentDictionary;
    }

    public FrameworkElement Produce(..data..)
    {
        var dataContext = dataContextsDictionary[data.something.GetType()].Invoke();
        var component componentDictionary[data.something.GetType()].Invoke(dataContext);
        return component;
    }
}

And in the composition root, I would have something as follows:

    var datacontextDictionary = new Dictionary<Type, Func<object>>
        {
            { typeof(Image), () => new ImageComponentVM() },
            { typeof(string), () => new TextComponentVM() }
        };
    var componentDictionary = new Dictionary<Type, Func<object, FrameworkElement>>
        {
            { typeof(Image), (dataContext) => new ImageComponent(dataContext) },
            { typeof(string), (dataContext) => new TextComponent(dataContext) }
        };
    var componentProducer = new ComponentProducer(componentDictionary);

My current solution looks similar to what I demonstrated. I would like to know different (and right) approaches, how this kind of problem is solved using dependency injection.

Thanks

Steven
  • 166,672
  • 24
  • 332
  • 435
Ladislav Ondris
  • 1,246
  • 10
  • 19
  • Passing viewmodels to control constructors is extremely bad practice. I suggest you us MVVM instead, and provide the usercontrols with their DataContexts via templating. If you want to inject dependencies into viewmodels by whatever means, that's another matter. – 15ee8f99-57ff-4f92-890c-b56153 Jul 23 '19 at 19:31
  • 1
    Stating that something is bad practice without explaining why is not useful. – Robert Harvey Jul 23 '19 at 19:32
  • 1
    @RobertHarvey *Why* is it not useful? HA HA HA get it? Anyway, it's bad practice because view-centered or view-first design creates endless complications with enabling the viewmodels to communicate with each other. – 15ee8f99-57ff-4f92-890c-b56153 Jul 23 '19 at 19:35
  • 1
    @EdPlunkett MVVM doesn't dictate how you should create your view models. Passing the viewmodel to the view via the constructor is in many scenarios useful, especially in dependency injection. In my opinion, it is the right way to inject viewmodels to views using DI. Even Mark and Steven do it this way in the book I mentioned in the question. – Ladislav Ondris Jul 23 '19 at 19:52
  • @LadislavOndris I don't dictate how you should create your viewmodels either. It's not generally productive to reinvent the wheel. Can you describe a scenario where your approach has some advantage relative to using data templates? – 15ee8f99-57ff-4f92-890c-b56153 Jul 23 '19 at 19:58
  • @EdPlunkett Let's say you need to pass a dependency via constructor injection to your view model. Then you have to create the view model first and then pass it to the view via constructor injection. If I misunderstood you, please explain what you meant. – Ladislav Ondris Jul 23 '19 at 20:06
  • 1
    Are we talking WPF? Controls are declared using XAML. The `DataContext` is inherited. You set the `DataContext` of your main view in your bootstrapper (or App.xaml.cs). You can use composition and let the main view model expose specialized view models that controls and templates can bind to. For more complex scenarios you might consider a view model locator which you add to your App.xaml. – BionicCode Jul 23 '19 at 20:10
  • 1
    Viewmodels create their own child viewmodels (somebody else must create the top level viewmodel). They can pass their children any constructor parameters they like, with the usual caveat that viewmodels should never have references to any UI objects. Inject whatever you like. In WPF, we ordinarily create child views with data templating, which requires the view to have a parameterless constructor. You can write implicit datatemplates which create a particular view for a particular viewmodel type. You can also use explicit datatemplates, when appropriate. – 15ee8f99-57ff-4f92-890c-b56153 Jul 23 '19 at 20:13
  • [Here's a basic minimal example of an implicit datatemplate](https://rachel53461.wordpress.com/2011/05/08/simplemvvmexample/). I can't say there'll never be a reason to write my own view class factory, but the built-in templating is a very powerful feature, which plays very well with all the other parts of WPF. People should learn it well before reinventing it. I'm not familiar with the book you're working with, but people publish some weird stuff sometimes. You never know. Again, I stress: I'm not militating against IoC, I'm militating against WPF views with constructor parameters. – 15ee8f99-57ff-4f92-890c-b56153 Jul 23 '19 at 20:20
  • @EdPlunkett Of course I use templating. It is not a problem at all. What I am learning is dependency injection (in this case in conjuction with MVVM). That is the goal. – Ladislav Ondris Jul 23 '19 at 20:27
  • 1
    _"In my application I need to create custom UserControls at runtime based on what the user is doing. Each custom UserControl naturally needs its own DataContext."_ To make it more clear: your approach is wrong. You have to focus on the data not on controls. _Data_ is created or added dynamically. You then use binding and a predefined `DataTemplate` to create the data view. You add the _data_ to a collection that is bound to an `ItemsControl`. – BionicCode Jul 23 '19 at 20:27
  • The `ItemsControl` uses the predefined item template to (dynamically) create the view and maybe uses a `DataTemplateSelector`. The `DataContext` is the data itself or a parent's `DataContext.` – BionicCode Jul 23 '19 at 20:28
  • 2
    Dependency injection is not reasonable for view components. Use it for view model and model. WPF is emphasizing XAML to create the view. The XAML parser handles a lot. Object instantiation too. If you would create all the instances yourself (using C#) you would also need to create the whole UI tree manually (using C#). You don't want to do this, I am pretty sure. – BionicCode Jul 23 '19 at 20:36
  • @EdPlunkett I am not that new to WPF. But I am always open to new ideas, approaches etc. so I am glad you posted. – Ladislav Ondris Jul 23 '19 at 20:36
  • @BionicCode I think I understand what you're saying. I will look into it tomorrow and I will let you know how I am doing. Thank you for now. – Ladislav Ondris Jul 23 '19 at 20:41
  • 1
    Note that the source code of the UWP application of section 7.2 of [DI PP&P](https://manning.com/seemann2) can be [found on GitHub](https://github.com/DependencyInjection-2nd-edition/codesamples/tree/master/UWPProductManagementClient/src) with the Composition Root being located [here](https://github.com/DependencyInjection-2nd-edition/codesamples/blob/master/UWPProductManagementClient/src/ProductManagement.UWPClient/App.xaml.cs#L69). – Steven Jul 24 '19 at 10:04
  • 1
    @BionicCode You were completely right. I was thinking about it wrong. I was creating controls in code which I shouldn't do. Now I have only view models representing data and DataTemplates in view. In the beginning I thought my case was somewhat special and that it was excusable to create controls in code, but wasn't. Thank you guys. – Ladislav Ondris Jul 26 '19 at 20:45
  • @EdPlunkett above comment applies to you as well. – Ladislav Ondris Jul 26 '19 at 20:46
  • 1
    @LadislavOndris WPF is a heck of a learning curve. Cheers! – 15ee8f99-57ff-4f92-890c-b56153 Jul 26 '19 at 20:54

0 Answers0