UPDATE: Using Dependency Injection on ViewModelBuilder
On a previous piece of code, dependency injection should have been used on my ViewModelBuilder. Today, I provide a cleaner way to do my ViewModelBuilder.
A while back, I explained how to create a ViewModelBuilder using a DefaultViewModelFactory when creating ViewModels for my Views. The article where I explained this was called The Skinniest ASP.NET MVC Controller You've Ever Seen, Part 1.
Quite a mouth-full, right?
I've been using reflection for my ViewModel Factory. One location to build and return my ViewModels.
Here was the code in question (or "code smell").
Classes\DefaultViewModelFactory.cs
using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using ThinController.Interfaces; namespace ThinController.Classes { public class DefaultViewModelFactory : IViewModelFactory { public TViewModel GetViewModel<TController, TViewModel>(TController controller) { var viewModels = GetSystemTypes(controller, typeof(TViewModel)); var modelBuilders = GetSystemTypes(controller, typeof(IViewModelBuilder<TController, TViewModel>)); var typeName = typeof(TViewModel).Name; var model = (from t in viewModels where typeof(TViewModel).IsAssignableFrom(t) && t.Name.Equals(typeName) let strategy = (TViewModel)Activator.CreateInstance(t) select strategy).FirstOrDefault(); var modelBuilder = (from t in modelBuilders where typeof(IViewModelBuilder<TController, TViewModel>) .IsAssignableFrom(t) let strategy = (IViewModelBuilder<TController, TViewModel>) Activator.CreateInstance(t) select strategy).FirstOrDefault(); // Redirect and assist developers in adding their own modelbuilder/viewmodel if (modelBuilder == null) throw new Exception( String.Format( "Could not find a ModelBuilder with a {0} Controller/{1} ViewModel pairing. Please create one.", typeof(TController).Name, typeof(TViewModel).Name)); return modelBuilder.Build(controller, model); }
public TViewModel GetViewModel<TController, TViewModel, TInput>(TController controller, TInput data) { var viewModels = GetSystemTypes(controller, typeof(TViewModel)); var modelBuilders = GetSystemTypes(controller, typeof(IViewModelBuilder<TController, TViewModel, TInput>)); var typeName = typeof(TViewModel).Name; var model = (from t in viewModels where typeof(TViewModel).IsAssignableFrom(t) && t.Name.Equals(typeName) let strategy = (TViewModel)Activator.CreateInstance(t) select strategy).FirstOrDefault(); var modelBuilder = (from t in modelBuilders where typeof(IViewModelBuilder<TController, TViewModel, TInput>) .IsAssignableFrom(t) let strategy = (IViewModelBuilder<TController, TViewModel, TInput>) Activator.CreateInstance(t) select strategy).FirstOrDefault(); // Redirect and assist developers in adding their own modelbuilder/viewmodel if (modelBuilder == null) throw new Exception( String.Format( "Could not find a ModelBuilder with a {0} Controller/{1} ViewModel/{2} TInput pairing. Please create one.", typeof(TController).Name, typeof(TViewModel).Name, typeof(TInput).Name)); return modelBuilder.Build(controller, model, data); } private IEnumerable<Type> GetSystemTypes<TController>(TController controller, Type type) { // First, get the types of the executing assembly var currentAssemblyTypes = Assembly .GetExecutingAssembly() .GetTypes() .Where(e => type.IsAssignableFrom(e)) .ToList(); // Then the calling assembly currentAssemblyTypes.AddRange( Assembly .GetCallingAssembly() .GetTypes() .Where(e => type.IsAssignableFrom(e)) .ToList() ); return currentAssemblyTypes.Distinct(); } } }
Yes, it is a mess...and a "code smell!"
If you're wondering what I'm talking about when I say "Code Smell," please refer to the book Refactoring: Improving the Design Of Existing Code. This is one of the books that every .NET developer should own!
Enter Dependency Injection
I've always liked Ninject and it hasn't done me wrong yet. I finally looked the code over one last time and decided to refactor it so it wouldn't look like such a mess.
First, I used Nuget to grab Ninject (Install-Package Ninject
) and the Ninject Extension Conventions (Install-package Ninject.extensions.conventions
).
Once I had Ninject installed, I was ready to start refactoring the code.
Here is the refactored code:
ViewModelBuilder\ViewModelFactory.cs
public class DefaultViewModelFactory: IViewModelFactory { public TViewModel GetViewModel<TController, TViewModel>(TController controller) { TViewModel model; IViewModelBuilder<TController, TViewModel> modelBuilder; using (var kernel = new StandardKernel()) { kernel.Bind(x => x.FromThisAssembly() .SelectAllClasses() .BindAllInterfaces()); model = kernel.Get<TViewModel>(); modelBuilder = kernel.Get<IViewModelBuilder<TController, TViewModel>>(); } // Redirect and assist developers in adding their own modelbuilder/viewmodel if (modelBuilder == null) throw new Exception( String.Format( "Could not find a ModelBuilder with a {0} Controller/{1} ViewModel pairing. Please create one.", typeof (TController).Name, typeof (TViewModel).Name)); return modelBuilder.Build(controller, model); } public TViewModel GetViewModel<TController, TViewModel, TInput>(TController controller, TInput data) { TViewModel model; IViewModelBuilder<TController, TViewModel, TInput> modelBuilder; using (var kernel = new StandardKernel()) { kernel.Bind(x => x.FromThisAssembly() .SelectAllClasses() .BindAllInterfaces()); model = kernel.Get<TViewModel>(); modelBuilder = kernel.Get<IViewModelBuilder<TController, TViewModel, TInput>>(); } // Redirect and assist developers in adding their own modelbuilder/viewmodel if (modelBuilder == null) throw new Exception( String.Format( "Could not find a ModelBuilder with a {0} Controller/{1} ViewModel/{2} TInput pairing. Please create one.", typeof (TController).Name, typeof (TViewModel).Name, typeof (TInput).Name)); // if (model == null) return null; return modelBuilder.Build(controller, model, data); } }
Removing and optimizing code is one of the greatest feelings, isn't it?
This should give you an idea of how you can replace most reflection calls with dependency injection.
Since these ViewModels are contained and ONLY used for the Views, they are already compiled inside this assembly. We aren't going outside of the application for any injected ViewModels so why make it any more complicated.
The type of ViewModel and ViewModelBuilder are easy to instantiate using Ninject. The two methods in the class are required because one contains an input parameter and the other doesn't.
I did need a little bit of help with the Ninject and it's specific way of binding all of the classes to interfaces so that Ninject wouldn't scream about classes not being self-bindable.
This line took care of that:
kernel.Bind(x => x.FromThisAssembly() .SelectAllClasses() .BindAllInterfaces()
);
After I added this in, everything functioned as before...
...only with less code and more manageable code!
Conclusion
Dependency Injection is becoming so important that the next version of ASP.NET MVC will have dependency injection baked right into ASP.NET MVC.
If you are still skeptical about dependency injection, I would seriously rethink about getting into dependency injection. I can describe it in two words:
They are Decoupled Factories!
The code above is proof of that.
Was there a better way to perform the code above? Did the bind need moved to another part of my application? Post your comments below.