How to use WPF controls with Simple Injector dependencies

青春壹個敷衍的年華 提交于 2019-12-18 08:26:52

问题


I'd like to use Dependency Injection in a scenario where I have to inject resources into GUI-controls. As that might be the wrong Place, I have some reasons to do it here and not in a view model (eg. I need Window handles and such).

Constructor parameter injection seems to be the preferred way. As most of you know WPF controls must have a parameter-less constructors, otherwise the XAML does not work and for the current scenario I love to keep my XAML since it contains some name registrations and bindings.

So: How can I use constructor-DI in a WPF+XAML scenario and (if possible in the case of Simple Injector)?

Does a Markup extension exist or can the XAML parser be made Container-Aware and accept parameter-having constructors as controls?

Scheme example:

<Grid>
 <gg:WhateverResourceNeedingViewer ItemSource={Binding Items}/>
</Grid>

And:

public class WhateverResourceNeedingViewer : ItemsControl
{
   public WhateverResourceNeedingViewer(Dep1 d, DepResource d2)
   {
   ...
   }
...
}

回答1:


It is good practice to not only build your viewmodels using the SOLID design principles but to do this in your views also. The usage of usercontrols can help you with this.

Downside of the approach you're suggesting, if technically possible, is that this design will violate SRP and OCP.

SRP because all dependencies your usercontrol needs must be injected in the consuming window/view while this view probably does not need (all of) these dependencies.

And OCP because every you add or delete a dependency from your usercontrol you also need to add or delete this from the consuming window/view.

With usercontrols you're able to compose the view just as you compose your other classes like services, command- and queryhandlers, etc. When it comes to dependency injection the place for composing your application is the composition root

ContentControls in WPF are all about 'composing' your view from other 'content' in your application.

A MVVM tool like Caliburn Micro typically uses contentcontrols to inject a usercontrol view (read: xaml without code behind) with it's own viewmodel. As a matter of fact, when using a MVVM you would build all views in the application from the usercontrols class, as a best practice.

This could look something like this:

public interface IViewModel<T> { }

public class MainViewModel : IViewModel<Someclass>
{
    public MainViewModel(IViewModel<SomeOtherClass> userControlViewModel)
    {
        this.UserControlViewModel = userControlViewModel;
    }

    public IViewModel<SomeOtherClass> UserControlViewModel { get; private set; }
}

public class UserControlViewModel : IViewModel<SomeOtherClass>
{
    private readonly ISomeService someDependency;

    public UserControlViewModel(ISomeService someDependency)
    {
        this.someDependency = someDependency;
    }
}

And the XAML for the MainView:

// MainView
<UserControl x:Class="WpfUserControlTest.MainView"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Grid>
        <ContentControl Name="UserControlViewModel" />
    </Grid>
</UserControl>

// UserControl View
<UserControl x:Class="WpfUserControlTest.UserControlView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Grid>
        <TextBlock Text="SomeInformation"/>
    </Grid>
</UserControl>

The result will be that the MainView is displayed in a window where the DataContext of that window is set to the MainViewModel. The contentcontrol will be filled with the UserControlView with its DataContext set to the UserControlViewModel class. This automagically happens because the MVVM tool will bind the viewmodels to corresponding views using Convention over configuration.

If you don't use a MVVM tool but directly inject your dependencies in the code behind of your window class you simply can follow the same pattern. Use a ContentControl in your view, just as the above example and inject the UserControl (with a constructor containing parameters as you wish) in the constructor of the window. Then just set the Content property of the ContentControl to the injected instance of your UserControl.

That would look like:

public partial class MainWindow : Window
{
    public MainWindow(YourUserControl userControl)
    {
        InitializeComponent();
        // assuming you have a contentcontrol named 'UserControlViewModel' 
        this.UserControlViewModel.Content = userControl;
    }

    // other code...
}



回答2:


This may be considered an anti-pattern - on a number of levels - (see Ric's answer for details) but if you just want to get this working, wish to be pragmatic and have a simple use-case, I would suggest a static DI resolver. This comes in super handy for legacy components or situations like this where one is constrained by the underlying architecture.

/// <summary>
///     Provides static resolution of Simple Injector instances.
/// </summary>
public class ServiceResolver
{
    private Container Container { get; }
    private static ServiceResolver Resolver { get; set; }

    public ServiceResolver(Container container)
    {
        Container = container;
        Resolver = this;
    }

    public static T GetInstance<T>()
    {
        if (Resolver == null) throw new InvalidOperationException($"{nameof(ServiceResolver)} must be constructed prior to use.");
        return (T) Resolver.Container.GetInstance(typeof(T));
    }
}

Usage, from your example:

public WhateverResourceNeedingViewer()
{
    InitializeComponent();

    // Resolve view model statically to fulfill no-arg constructor for controls
    DataContext = ServiceResolver.GetInstance<DepResource>();
}


来源:https://stackoverflow.com/questions/32307033/how-to-use-wpf-controls-with-simple-injector-dependencies

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!