【问题标题】:Can I get an Action's return type from an Action Filter?我可以从动作过滤器中获取动作的返回类型吗?
【发布时间】:2011-02-21 08:16:59
【问题描述】:

我有一个 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 方法中的信息,是否可以更准确地确定正在调用的重载?

【问题讨论】:

    标签: asp.net-mvc vb.net reflection


    【解决方案1】:

    我基于此创建了一个 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;
            }
        }
    

    【讨论】:

    • 有趣的解决方案,谢谢。请注意,如果 filterContext.ActionDescriptor 是 System.Web.Mvc.ReflectedActionDescriptor 类型,则它已经具有 MethodInfo 属性,因此您无需费心去确定它。
    【解决方案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()) 重载来更具体地说明我的请求。

    【讨论】:

      【解决方案3】:

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

      也就是说,当控制器不够可用时,您是否将用户重定向到视图?我的意思是,我不明白您为什么要将用户重定向到 JSON 结果或部分视图。

      【讨论】:

      • 该网站是我们客户的门户。我有一些页面,例如带有来自其他控制器的部分视图的主页。我想将带有消息的部分视图返回到父视图。家庭控制器将始终可用,但报告控制器可能不可用。报告小部件应该只显示一条礼貌的消息。
      • @Nick:那为什么不直接做类似filterContext.Result = New PartialViewResult(...)的事情,不管action方法返回的实际action结果如何?
      • 如果他们期待局部视图,那没关系。如果他们点击/Reports/Index,尽管他们不喜欢光秃秃的局部视图回来。我已经使用反射所取得的进展更新了我的问题。
      • 我也考虑过反射方法,但只有当您严格声明您的操作方法以返回 ActionResult 的特定子类时才有效。这可能并不总是可能的,因为您可能有一些条件代码,例如 if (cond1) return Json(...);否则返回 JavaScript(...);另外,无论返回的特定子类如何,都只使用 ActionResult 几乎是一种约定。也就是说,如果你能忍受这些已知的限制,那么反射是一种选择。
      • @Nick:关于更新后的帖子:要正确选择方法,在许多不同的重载中,您需要按照 MVC 执行的解析步骤来找到匹配项。具体来说,您应该查看内部类 ActionMethodSelector 的方法 FindActionMethod(在 MVC 2 源代码中)。如果你很幸运,那么你也许可以在没有太多变化和很多依赖的情况下重用该方法——不过我自己还没有尝试过。
      【解决方案4】:

      上面有一些不错的答案,但是在 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 Preview 的完整解决方案

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多