Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dependency Injection - passing parameters to constructed types

I'm introducing Dependency Injection to my project. I've got a dialog window, which serves as an editor to either new or existing entity. Its ViewModel looks like the following:

public class ContractWindowViewModel
{
    private IRepository<Contract> contractRepository;
    private Contract model;
    private bool isNew;

    public ContractWindowViewModel(Contract contract, IRepository<Contract> contractRepository)
    {
        if (contract == null)
            throw new ArgumentNullException(nameof(contract));
        if (contractRepository == null)
            throw new ArgumentNullException(nameof(contractRepository));

        this.contractRepository = contractRepository;
        this.model = contract;
        this.isNew = false;
    }

    public ContractWindowViewModel(IRepository<Contract> contractRepository)
    {
        this.contractRepository = contractRepository;
        this.model = new Contract();
        this.isNew = true;
    }

    // (...)
}

The plan is to inject IRepository<Contract>. But sometimes I need to pass Contract to the ViewModel (if I want to edit an existing one) or not (if I want to create new one). How should I do that using Unity?

like image 782
Spook Avatar asked Oct 11 '15 12:10

Spook


People also ask

What is constructor dependency injection?

Dependency injection (DI) is a process whereby objects define their dependencies, that is, the other objects they work with, only through constructor arguments, arguments to a factory method, or properties that are set on the object instance after it is constructed or returned from a factory method.

What is the right way to inject dependency?

Constructor injection should be the main way that you do dependency injection. It's simple: A class needs something and thus asks for it before it can even be constructed. By using the guard pattern, you can use the class with confidence, knowing that the field variable storing that dependency will be a valid instance.


1 Answers

Using the DI container from your code (e.g. in a button event handler) is called Service Location and is considered to be an anti-pattern.

You should instead have an factory that allows you to create your view model from inside your button handler (or your ViewModel command).

Here is an example of a factory:

public interface IContractWindowViewModelFactory
{
    ContractWindowViewModel CreateForNewContract();

    ContractWindowViewModel CreateForExistingContract(Contract existing_contract);
}

public class ContractWindowViewModelFactory : IContractWindowViewModelFactory
{
    private readonly IRepository<Contract> m_Repository;

    public ContractWindowViewModelFactory(IRepository<Contract> repository)
    {
        m_Repository = repository;
    }

    public ContractWindowViewModel CreateForNewContract()
    {
        return new ContractWindowViewModel(m_Repository);
    }

    public ContractWindowViewModel CreateForExistingContract(Contract existing_contract)
    {
        return new ContractWindowViewModel(existing_contract, m_Repository);
    }
}

Now you need to inject IContractWindowViewModelFactory into the class that needs to be able to create the ContractWindowViewModel view model (e.g. via constructor injection).

Since you are using a DI container, you need to register IContractWindowViewModelFactory with ContractWindowViewModelFactory in the Composition Root. You also need to register IRepository<Contract> (which I am guessing you have already done).

Now, inside your button handler (or command handler), you can use the factory to create a ContractWindowViewModel for a new or existing Contract.

If for some reason, you still want to use the container from the button handler (which I encourage you not to do), then you can use named registrations like this:

In your composition root:

container.RegisterType<ContractWindowViewModel>(
    "ForNew",
    new InjectionConstructor(
        new ResolvedParameter<IRepository<Contract>>()));

container.RegisterType<ContractWindowViewModel>(
    "ForExisting",
    new InjectionConstructor(
        new ResolvedParameter<Contract>(),
        new ResolvedParameter<IRepository<Contract>>()));

In your handler, you can use this for new contracts:

var viewmodel_for_new = container.Resolve<ContractWindowViewModel>("ForNew");

Or this for existing contracts:

Contract existing_contract = ...
var viewmodel_for_existing = container.Resolve<ContractWindowViewModel>(
    "ForExisting",
    new ParameterOverride("contract", existing_contract));
like image 107
Yacoub Massad Avatar answered Nov 11 '22 02:11

Yacoub Massad