Nexith
Nexith

Reputation: 475

Resolve constructor argument with parameter from base class

I have a custom ASP.NET MVC controller that retrieves operations from the user service. I want to pass the operations property to the scenario service using dependency injection.

public abstract class BaseController : Controller {
    protected IUserService userService;
    public OperationWrapper operations { get; private set; }
    public BaseController(IUserService userService) {
        this.userService = userService;
        this.operations = userService.GetOperations(HttpContext.Current.User.Identity.Name);
    }
}

public abstract class ScenarioController : BaseController {
    protected IScenarioService scenarioService;
    public ScenarioController(IScenarioService scenarioService, IUserService userService)
        : base(userService) {
        this.scenarioService = scenarioService;
    }
}

public class ScenarioService : IScenarioService {
    private OperationWrapper operations;
    public ScenarioService(OperationWrapper operations) {
        this.repo = repo;
        this.operations = operations;
    }
}

Here is my Windsor installer.

public class Installer : IWindsorInstaller {
    public void Install(IWindsorContainer container, IConfigurationStore store) {
        container.Register(Classes.FromThisAssembly()
                        .BasedOn<IController>());

        container.Register(Classes.FromThisAssembly()
                            .Where(x => x.Name.EndsWith("Service"))
                            .WithService.DefaultInterfaces()
                            .LifestyleTransient());
    }
}

I pretty sure I've done something similar with Ninject a couple of years back. What do I need to add to the installer in order to make this work? Is it even possible?

Upvotes: 3

Views: 632

Answers (3)

eouw0o83hf
eouw0o83hf

Reputation: 9588

There are a few of options here:

1. Use LifeStylePerWebRequest() and UsingFactoryMethod()

First, you could register an OperationWrapper as LifestylePerWebRequest() and inject it into both the BaseController and ScenarioService. Windsor will let you register the dependency with a factory method for creating it, which can in turn call other services which have been registered.

container.Register(Component.For<OperationWrapper>()
                            .LifestylePerWebRequest()
                            .UsingFactoryMethod(kernel =>
                            {
                               var userService = kernel.Resolve<IUserService>();
                               try
                               {
                                  return userService.GetOperations(
                                               HttpContext.Current.User.Identity.Name);
                               }
                               finally
                               {
                                  kernel.ReleaseComponent(userService);
                               }
                            }));

So, every time Windsor is asked for an OperationWrapper, it will run that call against an instance if IUserService, giving it the Name of the current User. By binding the lifestyle to LifestylePerWebRequest(), you can verify that each request will get its own instance of the OperationWrapper and it won't bleed across requests.

(The only edge case you'd run into is one where a user becomes authenticated mid-request and the OperationWrapper needs to be adjusted as a result. If that's a normal-path use case, this may need some re-thinking.)

Then, modify your base controller to take that registered object in as a dependency:

public abstract class BaseController : Controller {
    protected IUserService userService;
    protected OperationWrapper operations;
    public BaseController(IUserService userService, OperationWrapper operations) {
        this.userService = userService;
        this.operations = operations;
    }
}

2. Use Method Injection

It looks like OperationWrapper is some sort of context object, and those can sometimes be injected into the method instead of into the constructor.

For instance, if your method was:

int GetTransactionId() { /* use OperationWrapper property */ }

You could just modify the signature to look like:

int GetTransactionId(OperationWrapper operations) { /* use arg */ }

In this situation, it makes sense to use it if a small-ish subset of your service's methods use that dependency. If the majority (or totality) of methods need it, then you should probably go a different route.

3. Don't use DI for OperationWrapper at all

In situations where you have a highly-stateful contextual object (which it seems like your OperationWrapper is), it frequently just makes sense to have a property whose value gets passed around. Since the object is based on some current thread state and is accessible from everywhere in any subclassed Controller, it may be right to just keep the pattern you have.

If you can't answer the question "What am I unable to do with OperationWrapper now that DI is going to solve for me?" with anything but "use the pattern/container," this may be the option for this particular situation.

Upvotes: 1

David Osborne
David Osborne

Reputation: 6791

Create a class that inherits from DefaultControllerFactory. Something like this will do:

public class WindsorControllerFactory : DefaultControllerFactory
{
    public WindsorControllerFactory(IKernel kernel)
    {
        _kernel = kernel;
    }

    protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
    {
        if (controllerType == null)
        {
            throw new HttpException(
                404,
                String.Format(
                    CultureInfo.CurrentCulture,
                    "The controller for path '{0}' was not found or does not implement IController.",
                    requestContext.HttpContext.Request.Path
                    )
                );
        }

        return (IController)_kernel.Resolve(controllerType);    
    }

    public override void ReleaseController(IController controller)
    {
        Kernel.ReleaseComponent(controller);
    }

    private readonly IKernel _kernel;

    private IKernel Kernel
    {
        get { return _kernel; }
    }
}

In the Application_Start method of your MvcApplication class add the following:

var container = new WindsorContainer();

container.Install(FromAssembly.This());

ControllerBuilder.Current.SetControllerFactory(
    new WindsorControllerFactory(container.Kernel)
);

This should work with your existing installer and get you to the point where Windsor will start resolving your dependencies for you. You might have to fill-in a few gaps, but you'll get the point.

I've borrowed heavily from: https://github.com/castleproject/Windsor/blob/master/docs/mvc-tutorial-intro.md

Be wary of using IDependencyResolver as it doesn't make provision for releasing what's resolved.

Upvotes: 0

Disappointed
Disappointed

Reputation: 1120

You should set dependency resolver in Application_Start method of global.asax

System.Web.MVC.DependencyResolver.SetResolver(your windsor resolver)

Upvotes: 0

Related Questions