Is there a better way to do IOC in asp.net-mvc?

204 Views Asked by At

I have an asp.net-mvc site and I am using LinFu to do IOC. I ran into an issue where a number of actions have a dependency that I want to inject into the controller but i only want to initialize the dependency if i call the action that depends on it.

so in my controller I have this code in my controller:

   public PersonController
   {

    private IPeopleImporter _peopleImporter;

    public override void Initialize(LinFu.IoC.Interfaces.IServiceContainer source)
    {
        _peopleImporter= source.GetService<IPeopleImporter>();
        base.Initialize(source);
    }

    public JsonResult GetDetails(int id)
    {
        var p = _peopleImporter.Get(id);
        var personDetails = new {p.Id, p.FirstName, p.LastName, StandardId = p.StandardIdLogin, p.PersonNumber};
        return Json(personDetails);
    }
   }

to initiatize PeopleImporter is pretty expensive so my issue is that I want to solve two things:

  1. I want to make the implementatioo of IPeopleImporter "pluggable" so I can IOC in the interface into the controller

  2. I have a lot of actions so I don't want the cost of initiatizing the IPeopleImporter if the user never calls the specific action that needs it. It seems like in the code above I do that initiatization on every call of PersonController

My initiatization code is like this:

this.AddService(typeof(IPeopleImporter), typeof(DatabaseImporter), LifecycleType.Singleton);

This seems like common pattern / issue. Is there a recommended solution. in the meantime, the alternative (to avoid the performance hit is to simple "new" up the concete implmentation inside the controller (and avoid IOC) ?

3

There are 3 best solutions below

0
On BEST ANSWER

You can use next trick for your task. You can inject not instance of IPeopleImporter but factory of this type:

private readonly Func<IPeopleImporter> _peopleImporterFactory;

and use this factory in your action where you need it:

var peopleImporter = __peopleImporterFactory();

example:

 public PersonController

{

private readonly Func<IPeopleImporter> _peopleImporterFactory;

public PersonController(Func<IPeopleImporter> peopleImporterFactory)
{
    _peopleImporterFactory = peopleImporterFactory;
}

public JsonResult GetDetails(int id)
{
    var peopleImporter = _peopleImporterFactory();
    var p = peopleImporter.Get(id);
    var personDetails = new {p.Id, p.FirstName, p.LastName, StandardId = p.StandardIdLogin, p.PersonNumber};
    return Json(personDetails);
}

}

0
On

I usually solve this by injecting dependencies as parameters to my actions. Common or cheap dependencies can be injected at the class level while unusual or expensive ones come in as parameters.

Here is some sample code for Enterprise Library Unity, but you can adapt it:

public class UnityActionInvoker : ControllerActionInvoker
{
    readonly IUnityContainer container;

    public UnityActionInvoker(IUnityContainer container)
    {
        this.container = container;
    }

    protected override object GetParameterValue(ControllerContext controllerContext, ParameterDescriptor parameterDescriptor)
    {
        Type parameterType = parameterDescriptor.ParameterType;

        if (parameterType != typeof(string) && !parameterType.IsValueType && container.IsRegistered(parameterType))
        {
            return container.Resolve(parameterType).AssertNotNull();
        }

        return base.GetParameterValue(controllerContext, parameterDescriptor);
    }
}

You can hook this ControllerActionInvoker in by setting Controller.ActionInvoker in the constructor of your controller (or in the common base class of all your controllers).

And here is how your controller might look like:

   public PersonController
   {
    public JsonResult GetDetails(int id, IPeopleImporter _peopleImporter /*injected*/)
    {
        ...
    }
   }
3
On

The first step would be into putting this action into a separate controller. So that you don't have to pay the initialization price for other actions in this controller.

Then use real IoC pattern, not Service Locator that you are currently using which is considered as an anti-pattern. In IoC the controller shouldn't know anything about the specific DI framework being used. In IoC the controller receives all dependencies as constructor arguments

public PersonsController: Controller
{
    private readonly IPeopleImporter _peopleImporter;
    public PersonsController(IPeopleImporter peopleImporter)
    {
        _peopleImporter = peopleImporter;
    }

    public ActionResult GetDetails(int id)
    {
        var p = _peopleImporter.Get(id);
        var personDetails = new { p.Id, p.FirstName, p.LastName, StandardId = p.StandardIdLogin, p.PersonNumber };
        return Json(personDetails, JsonRequestBehavior.AllowGet);
    }
}