Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I access the MethodInfo.ReturnType for an ActionDescriptor within the ActionExecutingContext?

I have an ActionFilterAttribute on my application that is used to redirect users when it detects that the user is not authenticated. Within the filter, I would like to detect when the ReturnType for an action is a JsonResult.

As a workaround, I initially created a custom attribute of IsJsonResult, and decorated the JsonResult methods in my solution with that attribute. This works, and is implemented in the action filter as follows:

public class CheckUser : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext actionExecutingContext)
    {
        base.OnActionExecuting(actionExecutingContext);
        object[] customAttributes = actionExecutingContext.ActionDescriptor.GetCustomAttributes(true);
        bool isJsonResult = customAttributes.FirstOrDefault(a => a.GetType() == typeof(IsJsonResult)) != null;

        if (isJsonResult)
        {
            return; // Don't perform any additional checks on JsonResult requests.
        }

        // Additional checking code omitted.
    }
}

That works, but I dislike the idea of decorating all of the JsonResult actions in this project. That could easily fail if a new JsonResult gets added to the project, and we forget to decorate it accordingly.

Moreover, I can see that the ReturnType with a name of "JsonResult" is in the debugger within the actionExecutingContext object shown above. Here is the path shown in the watch window:

actionExecutingContext > ActionDescriptor > [System.Web.Mvc.ReflectedActionDescriptor] > MethodInfo > ReturnType > FullName

That FullName property has a value of "System.Web.Mvc.JsonResult".

So it would seem that I could extract that value directly from the actionExecutingContext object, and create a supporting method to return a bool indicator. To achieve this, here is the code that I've written.

    private bool isReturnTypeJson(ActionExecutingContext actionExecutingContext)
    {
        string actionName = actionExecutingContext.ActionDescriptor.ActionName;
        string controllerName = actionExecutingContext.ActionDescriptor.ControllerDescriptor.ControllerName;
        Type controllerType = actionExecutingContext.Controller.GetType();
        try
        {
            // Only effective when the actionName is not duplicated in the controller.
            Type returnType = controllerType.GetMethod(actionName).ReturnType;
            return (returnType.Name == "JsonResult");
        }
        catch (AmbiguousMatchException)
        {
            // Using LINQ, can I filter this collection to isolate just the methods
            // that have the same name as the "actionName" variable above?
            MethodInfo[] methodsInfoCollection = controllerType.GetMethods(BindingFlags.Public | BindingFlags.Instance);

            // Attempted code from https://stackoverflow.com/questions/15283158/net-mvc-counting-action-methods-in-web-application
            //var info = typeof(controllerType)
            //        .Assembly.GetTypes()
            //        .Where(t => typeof(Controller).IsAssignableFrom(t))
            //        .Where(t => t.Namespace.StartsWith("AwesomeProduct.Web"))
            //        .SelectMany(t => t.GetMethods(BindingFlags.Public | BindingFlags.Instance))
            //        .Where(m => typeof(ActionResult).IsAssignableFrom(m.ReturnType))
            //        .Where(m => !m.IsAbstract)
            //        .Where(m => m.GetCustomAttribute<NonActionAttribute>() == null);

        }
        return false;
    }

Assigning the returnType works in the try-block whenever the action name is unique in the controller. But if there are multiple instances of the same action name in the controller, then the AmbiguousMatchException occurs. So in the catch-block, I've assigned the Methods to a collection. Using LINQ, how can I filter values from the methodsInfoCollection variable to the action that is arriving via the ActionExecutingContext?

Some of the articles that I've researched are below, and have used some ideas from those. But I've yet to figure this out.

  • http://www.codeproject.com/Articles/742461/Csharp-Using-Reflection-and-Custom-Attributes-to-M
  • Can I get an Action's return type from an Action Filter?
  • .NET MVC: Counting Action methods in web application

Thanks for your help.

==============

Update to original code. This works, but the looping is not ideal.

private bool isReturnTypeJson(ActionExecutingContext actionExecutingContext)
{
    string actionName = actionExecutingContext.ActionDescriptor.ActionName;
    string controllerName = actionExecutingContext.ActionDescriptor.ControllerDescriptor.ControllerName;
    Type controllerType = actionExecutingContext.Controller.GetType();
    try
    {
        // Only effective when the actionName is not duplicated in the controller.
        Type returnType = controllerType.GetMethod(actionName).ReturnType;
        return (returnType.Name == "JsonResult");
    }
    catch (AmbiguousMatchException)
    {
        // Using LINQ, can I filter this collection to isolate just the methods with a name of actionName.
        MethodInfo[] methodInfoCollection = controllerType.GetMethods(BindingFlags.Public | BindingFlags.Instance);
        foreach (MethodInfo methodInfo in methodInfoCollection)
        {
            if (methodInfo.ReturnType != null) {
                if (methodInfo.ReturnType == typeof(ActionResult))
                {
                    return false;
                }
                if (methodInfo.ReturnType == typeof(JsonResult))
                {
                    return true;
                }
            }
        }
    }
    return false;
}

See the solution from Reza Aghaei below. His solution totally eliminates the need for a separate method.

like image 333
Ken Palmer Avatar asked Oct 12 '15 20:10

Ken Palmer


1 Answers

You can use

((ReflectedActionDescriptor)filterContext.ActionDescriptor).MethodInfo.ReturnType 

And here is what I tried to check if return type is JsonResult:

((ReflectedActionDescriptor)filterContext.ActionDescriptor).MethodInfo.ReturnType == typeof(JsonResult)

And it returns true for my action:

public JsonResult Index()
{
    return Json(new { });
}
like image 193
Reza Aghaei Avatar answered Nov 18 '22 05:11

Reza Aghaei