简体   繁体   中英

Injecting data resolved models at runtime with Simple Injector

I am building an app with Simple Injector in WPF and I have run into the issue where I want to resolve my Models to inject into my ViewModels at runtime. For instance, say I have a window to edit users that requires a ViewModel with a User Model. This user model is selected from a list and then injected into the ViewModel's constructor. How would I get this data into the ViewModel with Simple Injector? Is that even possible? I figure if it is possible I would use some form of factory maybe? I was debating on using Simple Injector for everything except my MVVM components as a workaround--that is unless I can find a good solution.

If I should post some example code or anything like that to give a more clear definition of the problem, let me know. This is my first run with a DI Container and I want to understand how to best design my application with one.

Using the prefered approach what you're aiming for is indeed not possible. That is because your design is not optimal. As you can read here injecting runtime data in the components of your application is an anti-pattern.

The model for editing your users is runtime data and should thus flow through your application via method injection.

So you could solve this like:

public interface IViewModel<T>
{
    void EditItem(T item);
}

class UserEditViewModel : IViewModel<User>
{
    public void EditItem(User item)
    {
        // bind properties etc...
    }
}

You would typically inject the IViewModel<User> in your user master viewmodel.

Based on this and this design you can imagine a far more flexible solution. If you would see editing a user as performing a command which you just fire and returns when it's finished, you could define a infrastructal component a la the QueryProcessor from the link. This would become an EditProcessor which handles all your edit operations in your application. An implementation is pretty simple and would look something like:

class EditProcessor : IEditProcessor
{
    private readonly Container container;

    public EditProcessor(Container container)
    {
        this.container = container;
    }

    public void EditItem<T>(T item)
    {
        var viewModel = typeof (IViewModel<>).MakeGenericType(typeof (T));
        dynamic instance = container.GetInstance(viewModel);

        instance.EditItem((dynamic) item);
    }
}

Now everywhere you need to edit some model, you only inject IEditProcessor and call

this.editProcessor.EditItem(yourItem);

This simple implementation of an EditProcessor can be extended will all kinds of nice features like making a backup of the item before editing, so the user could cancel the editform for example.

If you use some MVVM toolkit to bind your view to your viewmodel this would also be the place to bring your DI container and the MVVM toolkit interact with eachother nicely.

You shouldn't inject models into your ViewModel.

Sounds more like you need an event aggregator/messanger. You would inject the event aggregator into your view models and subscribe to a certain event and rise it form another ViewModel.

public class CustomersViewModel 
{
    private readonly IEventAggregator eventAggregator;

    public CustomersViewModel(IEventAggregator eventAggregator) 
    {
        if(eventAggregator==null) 
        {
            throw new ArgumentNullException("eventAggregator");
        }

        this.eventAggregator = eventAggregator;
    }

    private Customer selectedCustomer;
    public Customer SelectedCustomer
    {
        get { return selectedCustomer; }
        set 
        {
            if(selectedCustomer!=value) 
            {
                selectedCustomer = value;
                eventAggregator.Publish(new CustomerSelectedEvent(selectedCustomer);
            }
        }
    }
}

public class CustomerOrdersViewModel 
{
    private readonly IEventAggregator eventAggregator;
    private readonly IOrderRepository orderRepository;

    private ObservableCollection<Order> orders;
    public ObservableCollection<Order> Orders {
        get { return orders; } 
        set 
        {
            if(orders != value)
            {
                orders = value;
                OnPropertyChanged("Orders");
            }
        }
    }

    public CustomerDetailViewModel(IEventAggregator eventAggregator, IOrderRepository orderRepository) 
    {
        if(eventAggregator==null) 
        {
            throw new ArgumentNullException("eventAggregator");
        }

        this.eventAggregator = eventAggregator;
        this.eventAggregator.Subscribe<CustomerSelectedEvent>(OnCustomerSelected);
        ...
    }

    private async void OnCustomerSelected(CustomerSelectedEvent event) 
    {
        Orders = new ObservableCollection<Order>(await orderRepository.GetOrdersByCustomer(event.Customer.Id);
    }
}

Where the SelectedCustomerEvent would be a simple POCO class.

public class SelectedCustomerEvent 
{
    public Customer Customer { get; }

    public SelectedCustomerEvent(Customer customer) 
    {
        // C# 6.0, otherwise add a private setter
        Customer = customer;
    }
}

The implementation of the event aggregator is a bit out of the scope, but there are frameworks that come with messanger/event aggregators (Prism PubSubEvents for example).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM