Find if request is child action request before controller context is available
Asked Answered
R

2

5

In a simple mvc 4 app I installed Ninject.MVC3 nuget package.

This is my controller, very basic, ISomeClass is being injected in the constructor by ninject.

public class HomeController : Controller
{
    private readonly ISomeClass _someClass;

    public HomeController(ISomeClass someclass)
    {
        _someClass = someclass;
    }

    public ActionResult Index()
    {
        return View();
    }

    [ChildActionOnly]
    public PartialViewResult MiniView()
    {
        return PartialView("miniview", _someClass.GetName());
    }
}

This is SomeClass

public class SomeClass : ISomeClass
{
    private readonly string _someName;

    public SomeClass(string someName)
    {
        _someName = someName;
    }

    public string GetName()
    {
        return _someName;
    }
}

In Index.cshtml view I have

@{ Html.RenderAction("MiniView","Home"); }

Now in NinjectWebCommon when I go to register the service I need to know if the request was a child action request or not. Like when I call Html.RenderAction. This is what I am trying but it is not working.

kernel.Bind<ISomeClass>().To<SomeClass>()
      .WithConstructorArgument("someName", c => IsChildAction(c) ? "Child" : "Nope");

IsChildAction method - Always returns false.

private static bool IsChildAction(Ninject.Activation.IContext c)
{
   var handler = HttpContext.Current.Handler;

   /*Cant do this, ChildActionMvcHandler is internal*/        
   return handler is System.Web.Mvc.Html.ChildActionExtensions.ChildActionMvcHandler;

//OR

   //This is how ControllerContext.IsChildAction gets its value in System.Web.Mvc but      
   //RouteData.DataTokens is empty for me       
   return ((MvcHandler)handler).RequestContext.RouteData.DataTokens
                              .ContainsKey("ParentActionViewContext");
}  

Any ideas if this can be done?

ps: this is not actual code, just trying something. Is this something I should definately not do? Why?

Reproachful answered 21/2, 2014 at 8:52 Comment(0)
R
7

I ended up checking if the current request has a previous handler. Seems like it is being set only on child actions.

HttpContext.Current.PreviousHandler != null && 
HttpContext.Current.PreviousHandler is MvcHandler;
Reproachful answered 26/2, 2014 at 17:5 Comment(1)
(HttpContext.Current.PreviousHandler as MvcHandler) != nullFreeliving
W
1

If the IsChildAction is something only known at runtime and within the controller, I suggest you don't pass an instance of SomeClass. Pass a factory instead and build that instance when you need it using the factory. I think that approach would work best in your case.

For using the factory you can use Ninject.Extensions.Factory or implement one yourself:

public class SomeClassFactory
{
    private readonly IKernel _kernel;

    public SomeClassFactory(IKernel kernel)
    {
        _kernel = kernel;
    }

    public SomeClass Create(string name, bool isChild)
    {
        var childString = (isChild) ? "Child" : "Nope";
        return _kernel.Get<SomeClass>(new ConstructorArgument("someName", childString));
    }
}

UPDATE: After you said a Factory wouldn't work, the only way I could figure out for you to have access to the RequestContext during controller creation would be with a custom controller factory. You won't be able to get it statically in your bindings, I'm afraid.

The code below performs resolution on your SomeClass at runtime depending on whether the RequestContext contains the information about child action. It uses a custom IControllerFactory which certainly could have a better implementation, but it is enough to show how it can be done.

internal class CustomControllerFactory : DefaultControllerFactory
{
    internal const string ParentActionViewContextToken = "ParentActionViewContext";
    private readonly IResolutionRoot _resolutionRoot;

    public CustomControllerFactory(IResolutionRoot resolutionRoot)
    {
        _resolutionRoot = resolutionRoot;
    }

    public override IController CreateController(RequestContext requestContext, string controllerName)
    {
        //You can improve this later if you want -> you'll need to figure out if your controller will fit into this case
        //You can use marker interfaces, common supertype, etc... that's up to you
        if (controllerName.Equals("home", StringComparison.InvariantCultureIgnoreCase))
        {
            var controllerType = typeof (HomeController);
            var isChild = requestContext.RouteData.DataTokens.ContainsKey(ParentActionViewContextToken);

            var constructorArgument = new ConstructorArgument("someName", (isChild) ? "Child" : "Nope");
            var requestForDependency = _resolutionRoot.CreateRequest(typeof(IServiceClient), null, new Parameter[] { constructorArgument }, true, true);
            var dependency = _resolutionRoot.Resolve(requestForDependency).SingleOrDefault();

            return (IController)_resolutionRoot.Get(controllerType, new ConstructorArgument("service", dependency));
        }

        //Will go through the default pipeline (IDependencyResolver will be called, not affecting DI of other controllers)
        return base.CreateController(requestContext, controllerName);
    }
} 

Make sure you bind it:

kernel.Bind<IControllerFactory>().To<CustomControllerFactory>();

Wiseman answered 21/2, 2014 at 14:53 Comment(4)
Thank you for your answer. I did think about the factory approach. But I need to figure out if this is a child request during or prior to the controller creation(for eg like in a custom controller factory), so passing a factory didnt work. I tried to simplify the question here, apologize if it caused confusion.Reproachful
Are you going to need this for specific controllers or for all of them? I have a solution in mind but I'd like to know thatWiseman
Will need this for all of them.Reproachful
Then it is even easier, you don't need the above check. Please see if the answer fits :)Wiseman

© 2022 - 2024 — McMap. All rights reserved.