Allow constructor argument with parameter from base class

I have a custom ASP.NET MVC controller that fetches operations from a custom service. I want to pass an operations property to a script 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'm sure I did something similar with Ninject a couple of years ago. What do I need to add to the installer to make this work? Is it possible?

+3


source to share


3 answers


There are several options here:

1. Use LifeStylePerWebRequest()

andUsingFactoryMethod()

First you can register OperationWrapper

as LifeStylePerWebRequest()

and enter it in BaseController

both ScenarioService

. Windsor will allow you to register a dependency with a factory method to create it, which in turn can call other registered services.

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 requested OperationWrapper

, it runs this call against the instance, if IUserService

, by specifying its Name

current one User

. By linking lifestyle with LifeStylePerWebRequest()

, you can verify that each request will get its own instance OperationWrapper

and will not miss requests.

(The only edge case you OperationWrapper

run into is where the user becomes authenticated by the middle query and needs to be adjusted as a result . If this is a use case in the normal path, this might require some re-use of -thinking.)

Then change your base controller to accept the registered object 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 the Injection method



It looks like they OperationWrapper

are some sort of context object and they can sometimes be injected into this method rather than into the constructor.

For example, if your method was:

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

      

You can simply change the signature so that it looks like this:

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

      

In this situation, it makes sense to use it if a small part of your maintenance method uses this dependency. If most (or all) of the methods require it, then you should probably take a different route.

3. Don't use DI for OperationWrapper

at all

In situations where you have a high context state object (which seems to be yours OperationWrapper

), it often makes sense to have a property whose value is passed. Since the object is based on some current thread state and is available everywhere, in any subclass Controller

, it might be right to just keep the template you have.

If you can't answer the question "What can't I do with OperationWrapper

now that DI is going to solve it for me?" with anything other than "using a template / container" this might be an option for this particular situation.

+1


source


You have to set dependency resolver in Application_Start method for global.asax



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

      

0


source


Create a class that inherits from DefaultControllerFactory

. Something like this would 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 Application_Start

your class method MvcApplication

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 starts resolving your dependencies for you. You may have to fill in some gaps, but you get the point.

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

Be careful with using IDependencyResolver as it does not deal with the release of what is allowed.

0


source







All Articles