Class resolution with configurable parameter in Simple Injector

I am creating an MVVM WPF application using Simple Injector as a DI container. Now I am having some problems when I try to resolve the view from Simple Injector because I need to pass a parameter to my constructor at build time (not when registering the view in the container, so this does not apply: Simple injector input values ​​to the constructor ) ...

What I need is something like this:

var item = container.GetInstance<MyType>(myParameter);

      

I've read several places that this is not possible in Simple Injector because it shouldn't be done (including here: https://simpleinjector.codeplex.com/discussions/397080 ).

Is this true, and if so, how can I do it?


Background information

I have a collection of models and models with multiple views that are viewed by a specific key and the parameter I want to pass to the view is the key for the view model. I found this necessary because viewmodels and models are used in many places in the application and need to stay in sync / be the same if they have the same key. I don't think I can use all the visibility to solve this problem, and I don't know the keys when I register with the container. I also figured out that the approach ViewModelLocator

might be the ServiceLocator pattern (anti-?), However, this is currently the best I have.

Currently my constructor looks like this and I want the IViewModelLocator to be resolved while I pass the key:

public FillPropertiesView(IViewModelLocator vml, object key)
{
    // .. Removed code

    // Assign the view model
    var viewModel = vml.GetViewModel<FillPropertiesViewModel>(key);
    DataContext = viewModel;
}

      

IViewModelLocator

looks like this (and a similar interface exists for models).

public interface IViewModelLocator
{
    // Gets the view model associated with a key, or a default
    // view model if no key is supplied
    T GetViewModel<T>(object key = null) where T : class, IViewModel;
}

      

Now I have the following questions:

  • What is the best way to resolve the view using the view model key?
  • Do I have to do some kind of refactoring to enable this?
  • I missed some of the DI container capabilities since I created my own dictionary based on ViewModelLocator

    ?

Extended information

I showed the ViewModelLocator above and the reason I use this is to keep compatibility (it basically just gives me design time data when opened in Blend). The runtime view model could be the same for each view instance (key independent) if I didn't have to open different windows at the same time (see next paragraph). However, the problem described above is the same when the ViewModel retrieves the model and the ModelLocator requires a key to retrieve the existing model if it exists.

It is part of a PowerPoint oriented VSTO application (which affects some parts of the design). When an object is selected on the screen, the taskbar opens (this is basically the FillPropertiesView described above). The selected object has a key that is supplied in the view so that the view will retrieve the correct view model from the ViewModelLocator. Then the view model will get a reference to the model using the IModelLocator (similar to IViewModelLocator) and the same key. At the same time, the controller will retrieve the model from the ModelLocator using the same key. The controller listens for changing events from the model and updating objects on the screen. The same process is replicated whenever a new object is selected and multiple windows can open at the same time,that interact with the same or different objects at the same time (that is, multiple taskbars with unique view models).

So far I have resolved the view using a parameterless constructor without parameters and then after that injected the view model with a method call:

// Sets the view model on a view
public void SetViewModel(IViewModelLocator vml, object key)
{
    // Assign the view model
    _viewModel = vml.GetViewModel<FillPropertiesViewModel>(key);
    DataContext = _viewModel;
}

      

I've never had to register a view with a container, but resolved a specific type like this:

string key = "testkey" // read from the selected object
var view = container.GetInstance<FillPropertiesView>();
var vml = container.GetInstance<IViewModelLocator>();
view.SetViewModel(vml, key);

      

This problem came up when I tried to refactor this so that I didn't have to call the SetViewModel () method every time and manually resolve the view models etc. It got very messy when I also had to do this manual initiation on the model view in order to initiate the model in the same way.


ViewModelLocator

The ViewModelLocator currently works as a wrapper around the DI container, meaning view models are registered with Simple Injector.

Registrations look like this (in the CompositionHost class):

container.RegisterSingle<IViewModelLocator, ViewModelLocator>();
container.RegisterSingle<IModelLocator, ModelLocator>();

      

The implementation looks like this:

// Base implementation used by ViewModelLocator and ModelLocator
public class ServiceLocator<TService> where TService : class
{
    private readonly Dictionary<CombinedTypeKey, TService> _instances =
        new Dictionary<CombinedTypeKey, TService>();

    // Gets a service instance based on the type and a key.
    // The key makes it possible to have multiple versions of the same service.
    public T GetInstance<T>(object key = null) where T : class, TService
    {
        var combinedKey = new CombinedTypeKey(typeof(T), key);

        // Check if we already have an instance
        if (_instances.ContainsKey(combinedKey))
        {
            return _instances[combinedKey] as T;
        }

        // Create a new instance
        // CompositionHost is a static reference to the DI container (and 
        // should perhaps be injected, however, that is not the main issue here)
        var instance = CompositionHost.GetInstance<T>();
        _instances.Add(combinedKey, instance);
        return instance;
    }

    // A combined key to ease dictionary operations
    private struct CombinedTypeKey
    {
        private readonly object _key;
        private readonly Type _type;

        public CombinedTypeKey(Type type, object key)
        {
            _type = type;
            _key = key;
        }

        // Equals and GetHashCode() are overridden
    }
}

public class ViewModelLocator : IViewModelLocator
{

    private readonly ServiceLocator<IViewModel> _viewModelLocator; 

    public ViewModelLocator(ServiceLocator<IViewModel> locator)
    {
        _viewModelLocator = locator;

        // Dummy code that registers design time data is removed
    }

    // IViewModel is just an empty interface implemented by the view models
    public T GetViewModel<T>(object key = null) where T : class, IViewModel
    {
        return _viewModelLocator.GetInstance<T>(key);
    }

}

      

+3


source to share


1 answer


Including a service locator in your classes will (almost) never be like this, because it disallows compile-time dependency time checking and run-time dependency analysis . For this reason, I can also advise registering ALL of your root types (e.g. your views), since otherwise Simple Injector stays in the dark and cannot advise you on the possible possible configurations you might have.

Since you have View + ViewModel pairs that are always cached together, but may depend on a model instance that are reused by multiple View + ViewModel pairs, I suggest the following design.

Define abstraction for views and view models:

public interface IView<TModel>
{
    IViewModel<TModel> ViewModel { get; }
}

public interface IViewModel<TModel>
{
    TModel Model { get; set; }
}

      

Define an abstraction for getting / caching a view by key.

public interface IViewProvider<TView, TModel> where TView : IView<TModel>
{
    TView GetViewByKey(object key);
}

      

With these abstractions, your view might look like this:

public class FillPropertiesView : IView<FillPropertiesModel>
{
    public FillPropertiesView(FillPropertiesViewModel viewModel)
    {
        this.ViewModel = viewModel;
    }

    public IViewModel<FillPropertiesModel> ViewModel { get; private set; }
}

      



And your controllers can depend on an abstraction IViewProvider<TView, TModel>

so that they can reload the view when a new key is entered:

public class FillPropertiesController : Controller
{
    IViewProvider<FillPropertiesView, FillPropertiesModel> viewProvider;
    FillPropertiesView view;

    public FillPropertiesController(
        IViewProvider<FillPropertiesView, FillPropertiesModel> provider) {
        this.viewProvider = provider;
    }

    public void Reinitialize(object key) {
        this.view = this.viewProvider.GetViewByKey(key);
    }
}

      

The implementation for IViewProvider<TView, TModel>

might look like this:

public class ViewProvider<TView, TModel> : IViewProvider<TView, TModel> 
    where TView : class, IView<TModel> {
    Dictionary<object, TView> views = new Dictionary<object, TView>();
    Container container;
    IModelProvider<TModel> modelProvider;

    public ViewProvider(Container container,
        IModelProvider<TModel> modelProvider) {
        this.container = container;
        this.modelProvider = modelProvider;
    }

    public TView GetViewByKey(object key) {
        TView view;

        if (!this.views.TryGetValue(key, out view)) {
            this.views[key] = view = this.CreateView(key);
        }

        return view;
    }

    private TView CreateView(object key) {
        TView view = this.container.GetInstance<TView>();
        view.ViewModel.Model = this.modelProvider.GetModelByKey(key);
        return view;
    }
}

      

This implementation depends on abstraction (formerly undefined) IModelProvider<TModel>

. This is basically your old one ModelLocator

, but by using a generic type you can make the implementation much easier because we can have one instance of that type for the TModel (the same as for the ViewProvider), which saves you the trouble of doing that something with the preservation of elements with the combination {Type + key}.

You can register it all like this:

Assembly asm = Assembly.GetExecutingAssembly();

container.RegisterManyForOpenGeneric(typeof(IView<>), asm);
container.RegisterManyForOpenGeneric(typeof(IViewModel<>), asm);
container.RegisterOpenGeneric(typeof(IViewProvider<,>), 
    typeof(ViewProvider<,>), Lifestyle.Singleton);
container.RegisterOpenGeneric(typeof(IModelProvider<>), 
    typeof(ModelProvider<>), Lifestyle.Singleton);

var controllers =
    from type in asm.GetTypes()
    where type.IsSubClassOf(typeof(Controller))
    where !type.IsAbstract
    select type;

controllers.ToList().ForEach(t => container.Register(t));

container.Verify();

      

With, RegisterManyForOpenGeneric

you let Simple Injector search for the supplied assemblies to find implementations of this open generic abstraction, and Simple Injector will register them for you. With, RegisterOpenGeneric

you specify a public abstraction and tell Simple Injector which implementation to use when a private version of that abstraction is requested. The last line searches your application for all types of controllers and registers them with the system.

+3


source







All Articles