4
votes

I have an ASP.NET MVC 3 application and am using Ninject for injecting dependencies into my classes.

Actions on the Controllers pass ViewModels (that do not contain logic) to the view layer.

When a HTTP form is POSTed MVC 3 creates a ViewModel instance and binds the incoming POST data to the ViewModel's properties. MVC 3 uses a class called DefaultModelBinder to create the instance and perform the binding.

Most of my ViewModels share a dependency which I do not really want to set from each individual Controller method (DRY principle).

Therefore, I have created a customized subclass of DefaultModelBinder as follows:

using System;

using System.Web.Mvc;

namespace Application.Classes {

    public sealed class CustomModelBinder : DefaultModelBinder {

        private readonly IDependencyResolver DependencyResolver;

        public CustomModelBinder( IDependencyResolver dependencyResolver ) {

            DependencyResolver = dependencyResolver;

        }

        protected override object CreateModel( ControllerContext controllerContext , ModelBindingContext modelBindingContext , Type modelType ) {

            return DependencyResolver.GetService( modelType );

        }

    }

}

And I have set it to replace the DefaultModelBinder as follows (in Global.asax.cs):

protected void Application_Start() {

    // ...

    ModelBinders.Binders.DefaultBinder = new CustomModelBinder( DependencyResolver.Current );

    // ...

}

By doing this when a Controller method receives a ViewModel, it will be one that was built by Ninject using the binding I sepecified in my NinjectModule. The ViewModel now receives it's dependencies injected into it's constructor.

Is this an appropriate use of the Service Locator Pattern?

CLARIFICATION/UPDATES:

3
I am curious which dependency your viewmodels could require, because a viewmodel should be as stupid as possible. And you could use ninject to inject the dependency into your controller which in turn could pass it to the constructor of the viewmodel.Peter
@Peter Well, you don't have access to the constructor of the ViewModel from the Controller in MVC 3 (unless I am missing something). The ViewModels (the ones that MVC 3 creates for you as an Action parameter) are created in a "Black Box" called DefaultModelBinder.FantasticJamieBurns
@Peter I am open to explore the dependency in the ViewModel. But I do really want to understand if this CustomModelBinder is an appropriate use of the Service Locator Pattern (I am trying to get my head around many of these concepts!).FantasticJamieBurns
OK, so the dependency is a ViewModelBase.AccountInformation which is received in the constructor of an abstract class ViewModelBase. It is used on all "logged in" pages to display how much Credit an account has left, and their renewal date.FantasticJamieBurns
I've added my answer, hopefully Its made things a bit clearer. If you still have questions, I would really like to discuss them.Peter

3 Answers

5
votes

From what I understand so far is that you have a ViewModel which inherits from ViewModelBase to expose AccountInformation on all Views where a user is logged in.

Unless I completely misunderstood, this is my point of view:
The AccountInformation should only be used for displaying purposes. So it should not be a problem when the default ModelBinder does not instantiate it when a post to your action occurs. I encourage you to fetch the AccountInformation again using the information you have eg. Controller.User and your database. A registration/profile page is the only place where you'd want this information to come from POST variables. You could cache this information per user if necessary.

As I said in the comments, ViewModels should be as stupid as possible. They should only contain the properties with their types and metadata regarding validation etc.

All the logic you'd want to put in a view, goes into the controller.

So to conclude; There shouldn't be the need to use a Service Locator in your ModelBinder.

2
votes

View Models should not have dependecies. They are just dumb data containers with no functionallity. Use Filters for cross cutting concerns instead.

0
votes

have you considered using Ninject for MVC3? You can download it from here

Then in your global.asax inherit from NinjectHttpApplication:

public class MvcApplication : NinjectHttpApplication
{

Create a method to override kernel:

protected override IKernel CreateKernel()
{
   return new StandardKernel(new NinjectRepositoryModule(),
                             new NinjectAggregateServiceModule());
}

NijectRepositoryModule is where I bind my interfaces to concrete implementations:

public class NinjectRepositoryModule: NinjectModule
{
        public override void Load()
        {
            Bind<IContactsRepository>().To<EFContactRepository>();
        }
}