我能从一个Action过滤器中获取Action的返回类型吗?

11

我正在开发一个 ASP.NET MVC 2 应用程序,其中我正在创建一个自定义操作过滤器。该过滤器位于应用程序的控制器上,并从数据库中验证该函数当前是否可用。

Public Overrides Sub OnActionExecuting(ByVal filterContext As System.Web.Mvc.ActionExecutingContext)
  Try
    ' Check controller name against database.
    Dim controllerName = filterContext.Controller.GetType().Name
    controllerName = controllerName.Remove(controllerName.Length - 10)
    ' Look up availability.
    Dim available As Boolean = _coreService.GetControllerAvailability(controllerName)
    If Not available Then
      ' Redirect to unavailable notice.
      filterContext.Result = New RedirectResult("/Home/Unavailable/")
    End If
  Catch ex As Exception
    _eventLogger.LogWarning(ex, EventLogEntryType.Error)
    Throw
  End Try
End Sub

我的问题是,根据请求的操作,我需要将用户重定向到返回视图、部分视图或JSON的操作。

给定ActionExecutingContext,我能否找出最初请求的操作的返回类型是什么?

编辑:

好的,我接近了答案但又遇到了另一个问题。

Public Overrides Sub OnActionExecuting(ByVal filterContext As System.Web.Mvc.ActionExecutingContext)
  Try
    ' Check controller name against database.
    Dim controllerName = filterContext.Controller.GetType().Name
    Dim shortName = controllerName.Remove(controllerName.Length - 10)
    ' Look up availability.
    Dim available As Boolean = _coreService.GetControllerAvailability(shortName)
    If Not available Then
      ' find out what type is expected to be returned
      Dim actionName As String = filterContext.ActionDescriptor.ActionName
      Dim controllerType = Type.GetType("Attenda.Stargate.Web." & controllerName)
      Dim actionMethodInfo = controllerType.GetMethod(actionName)
      Dim actionReturnType = actionMethodInfo.ReturnType.Name

      Select Case actionReturnType
        Case "PartialViewResult"
          filterContext.Result = New RedirectResult("/Home/UnavailablePartial/")
        Case "JsonResult"
          filterContext.Result = New RedirectResult("/Home/UnavailableJson/")
        Case Else
          filterContext.Result = New RedirectResult("/Home/Unavailable/")
      End Select

    End If
  Catch ex As Exception
    _eventLogger.LogWarning(ex, EventLogEntryType.Error)
    Throw
  End Try
End Sub

我可以使用反射来查找操作方法的返回类型。我的问题是,如果我在控制器上有以下方法:

Public Function Create() As ViewResult
  Return View()
End Function

<AcceptVerbs(HttpVerbs.Post)>
Public Function Create(values as FormCollection) As ViewResult
  ' Do stuff here
End Function

我遇到了一个AmbiguousMatchException异常。

根据在OnActionExecuting方法中拥有的信息,有没有什么办法可以更精确地确定正在被调用的重载?

4个回答

9

我基于这个创建了一个AuthenticationFilterAttribute,根据类型返回不同的结果:

    /// <summary>
    /// Access to the action will be blocked if the user is not logged in. 
    ///  Apply this to the controller level or individual actions as an attribute.
    /// </summary>
    public class AuthenticationFilterAttribute : ActionFilterAttribute
    {
        protected const string InvalidAccess = "Invalid access";

        public override void OnActionExecuting(ActionExecutingContext filterContext)
        {
            // Find out if the user is logged in: 
            Controller controller = (Controller)filterContext.Controller;
            if (!controller.User.Identity.IsAuthenticated)
            {
                switch (GetExpectedReturnType(filterContext).Name)
                {
                    case "JsonResult":
                        var jsonResult = new JsonResult();
                        jsonResult.Data = new { Error = true, ErrorMessage = InvalidAccess };
                        jsonResult.JsonRequestBehavior = JsonRequestBehavior.AllowGet;
                        filterContext.Result = jsonResult;
                        break;

                    // Assume same behaviour as ActionResult
                    default: 
                        var actionResult = new ContentResult();
                        actionResult.Content = InvalidAccess;
                        filterContext.Result = actionResult;
                        break;
                }
            }
        }

        private Type GetExpectedReturnType(ActionExecutingContext filterContext)
        {
            // Find out what type is expected to be returned
            string actionName = filterContext.ActionDescriptor.ActionName;
            Type controllerType = filterContext.Controller.GetType();
            MethodInfo actionMethodInfo = default(MethodInfo);
            try
            {
                actionMethodInfo = controllerType.GetMethod(actionName);
            }
            catch (AmbiguousMatchException ex)
            {
                // Try to find a match using the parameters passed through
                var actionParams = filterContext.ActionParameters;
                List<Type> paramTypes = new List<Type>();
                foreach (var p in actionParams)
                {
                    paramTypes.Add(p.Value.GetType());
                }

                actionMethodInfo = controllerType.GetMethod(actionName, paramTypes.ToArray());
            }

            return actionMethodInfo.ReturnType;
        }
    }

1
有趣的解决方案,谢谢。请注意,如果filterContext.ActionDescriptor是System.Web.Mvc.ReflectedActionDescriptor类型,则它已经具有MethodInfo属性,因此您无需费力确定它。 - Tobias J

2

好的,这是我想出来的解决方案。

Public Overrides Sub OnActionExecuting(ByVal filterContext As System.Web.Mvc.ActionExecutingContext)
  Try
    ' Check controller name against database.
    Dim controllerName = filterContext.Controller.GetType().Name
    Dim shortName = controllerName.Remove(controllerName.Length - 10)
    ' Look up availability.
    Dim available As Boolean = _coreService.GetControllerAvailability(shortName)
    If Not available Then
      ' find out what type is expected to be returned
      Dim actionName As String = filterContext.ActionDescriptor.ActionName
      Dim controllerType = Type.GetType("Attenda.Stargate.Web." & controllerName)
      Dim actionMethodInfo As MethodInfo
      Try
        actionMethodInfo = controllerType.GetMethod(actionName)
      Catch ex As AmbiguousMatchException
        ' Try to find a match using the parameters passed through
        Dim actionParams = filterContext.ActionParameters
        Dim paramTypes As New List(Of Type)
        For Each p In actionParams
          paramTypes.Add(p.Value.GetType())
        Next
        actionMethodInfo = controllerType.GetMethod(actionName, paramTypes.ToArray)
      End Try
      Dim actionReturnType = actionMethodInfo.ReturnType.Name

      Select Case actionReturnType
        Case "PartialViewResult"
          filterContext.Result = New RedirectResult("/Home/UnavailablePartial/")
        Case "JsonResult"
          filterContext.Result = New RedirectResult("/Home/UnavailableJson/")
        Case Else
          filterContext.Result = New RedirectResult("/Home/Unavailable/")
      End Select

    End If
  Catch ex As Exception
    _eventLogger.LogWarning(ex, EventLogEntryType.Error)
    Throw
  End Try
End Sub

如果Type.GetMethod(string)调用无法识别所请求的方法,则我会从ActionExecutingContext.ActionParameters集合中获取参数集合,并构建一个包含请求中传递的参数类型的数组。然后,我可以使用Type.GetMethod(string,type())重载来更具体地请求。


1

上面有一些不错的答案,但在MVC Core中,我注意到您可以通过将其转换为ControllerActionDescriptor来直接获取方法信息,这将使上述答案更加简洁明了。(我正在使用Preview 6中的Blazor Webassembly Web Api后端)

((Microsoft.AspNetCore.Mvc.Controllers.ControllerActionDescriptor)filterContext.ActionDescriptor).MethodInfo

获取返回类型可以变为:

所以获取返回类型可以变成:

public static class FilterHelper
{
    public static Type GetReturnType(this ActionExecutingContext filterContext)
    {
        return ((ControllerActionDescriptor)filterContext.ActionDescriptor).MethodInfo.ReturnType;

    }
}

"并且可以像这样使用:"
Type t = actionContext.GetReturnType();

我会更新一个适用于6预览版的完整解决方案。


0

在调用OnActionExecuting时,动作方法尚未执行,因此您无法知道该动作方法将返回哪个ActionResult子类。因此,除非您可以使用CIL分析实现(我认为这可能会很快变得丑陋),否则我认为您想要做的事情是不可能的。

话虽如此,您将用户重定向到视图而控制器不可用的事实不足以说明问题吗?我的意思是,我不明白为什么您要将用户重定向到JSON结果或部分视图。


该网站是我们客户的门户。我有一些页面,例如主页,其中包含来自其他控制器的部分视图。我想返回一个带有消息的部分视图回到父视图。主控制器将始终可用,但报告控制器可能不可用。报告小部件应只显示礼貌的消息。 - Nick
@Nick:那为什么不像这样做,filterContext.Result = New PartialViewResult(...),而不管实际的操作结果是什么? - Buu
如果他们期望部分视图,那就没问题。但是如果他们访问/Reports/Index,他们不会喜欢裸的部分视图返回。我已经使用反射更新了我的问题。 - Nick
我也考虑过反射方法,但这只适用于严格声明操作方法返回特定的ActionResult子类。这可能并不总是可能的,因为您可能有一些条件代码,例如if(cond1)return Json(...); else return JavaScript(...);此外,仅使用ActionResult而不考虑返回的特定子类几乎是一种惯例。话虽如此,如果您可以接受这些已知的限制,那么反射就是一种选择。 - Buu
2
@Nick: 关于更新的帖子:为了正确地挑选出一个类中多个方法的重载,你需要遵循MVC依据匹配规则找到正确方法的解决步骤。具体来说,你应该查看内部类ActionMethodSelector中的FindActionMethod方法(在MVC 2源代码中)。如果你很幸运,可能能够在没有太多改变和依赖的情况下重用该方法 - 不过我自己还没有尝试过。 - Buu
谢谢,我已经下载了源代码并仔细研究了一下。它似乎使用了与我提出的答案非常相似的方法 :) - Nick

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接