Reputation: 13551
I have a traditional MVVM approach, so for example a view model called 'PatientManagementViewModel' that is used by a view called 'PatientManagementView'. Everything is injected using MEF, so I don't create any instance myself.
Now suppose that 'PatientManagementViewModel' has a property Patients, which is an ObervableCollection of 'PatientViewModel'. What I do now to create an instance of 'PatientViewModel' and pass the selected patient is like:
var patientViewModel = _container.GetExportedValue<IPatientViewModel>();
patientViewModel.Patient = patient;
This works, however, I was wondering if this makes sense. It would be nicer to pass a patient to the constructor because 'PatientViewModel' cannot exist without a Patient:
var patientViewModel = new PatientViewModel(patient);
but then I can't use dependency injection.
So the question is: does it make sense to inject a subviewmodel, or should I only inject the main view model, and instantiate all subviewmodels without injection?
Upvotes: 4
Views: 1466
Reputation: 20756
You can do the following. You can create your child view model using usual constructor and then call ComposeParts
on the instance to inject everything:
var patientViewModel = new PatientViewModel(patient);
_container.ComposeParts(patientViewModel);
But doing this every time is not very good for a variety of reasons. To address this scenario and to encapsulate usage of MEF I created a helper service for creating view models. It is called IViewModelFactory
and here is how it looks:
[Export(typeof(IViewModelFactory))]
[PartCreationPolicy(CreationPolicy.Shared)]
internal class ViewModelFactory : IViewModelFactory
{
[ImportingConstructor]
public ViewModelFactory(CompositionContainer container) {
Contract.Requires(container != null);
Container = container;
}
protected CompositionContainer Container { get; private set; }
public T Create<T>(params object[] args) where T : class {
T result;
try {
bool populateDependencies = false;
if (args == null || args.Length == 0) {
// There are no parameters for contructor, so
// try to create an instance by asking the container.
result = Container.GetExportedValueOrDefault<T>();
if (result == null) {
// The view model is not exported. Just create an instance using reflection
// and then populate all the dependencied using the container.
result = Activator.CreateInstance<T>();
populateDependencies = true;
}
}
else {
// There are constructor parameters. Create an instance using those parameters
// and then populate all the dependencied using the container.
result = (T)Activator.CreateInstance(typeof(T), args);
populateDependencies = true;
}
// Populate dependencies if needed
if (populateDependencies) {
Container.ComposeParts(result);
}
// Initialize the object if applicable
var initializable = result as IInitializable;
if (initializable != null) {
initializable.Initialize();
}
}
catch (Exception ex) {
throw new ViewModelCreationException(
string.Format(
"Unable to create and configure an instance of view model of type {0}. An error occured. See inner exception for details.",
typeof (T)), ex);
}
return result;
}
}
Using this factory you can create child view models like this:
var patientViewModel = ViewModelFactory.Create<PatientViewModel>(patient);
The downside here is that, when you use constructor parameters, you loose compile-time check for the parameters' types, count, order etc.
Upvotes: 1