2010-05-19 10 views
11

Tengo una aplicación ASP.NET MVC 2 en la que estoy creando un filtro de acción personalizado. Este filtro se encuentra en los controladores de la aplicación y verifica desde la base de datos si esa función está actualmente disponible.¿Puedo obtener el tipo de devolución de una acción desde un filtro de acción?

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 

Mi problema es que dependiendo de la acción que se ha solicitado que necesito para redirigir al usuario a una acción que devuelve un punto de vista, vistas parciales o JSON.

Dado el ActionExecutingContext ¿puedo averiguar cuál es el tipo de devolución de la acción solicitada originalmente?

EDIT:

Ok, estoy cada vez más cerca, pero tienen otro problema.

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 

Puedo usar la reflexión para encontrar el tipo de devolución del método de acción. Mi problema es si tengo los siguientes métodos en un controlador:

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 

Me sale una AmbiguousMatchException lanzada.

Con la información que tengo en el método OnActionExecuting, ¿hay alguna forma de ser más preciso para determinar la sobrecarga que se está llamando?

Respuesta

2

Ok, esta es la solución que he ideado.

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 

Si la (cadena) llamada Type.GetMethod no logra identificar el método solicitado, voy a buscar la colección de parámetros de la colección ActionExecutingContext.ActionParameters y construir una matriz de los tipos de los parámetros pasados ​​en la solicitud. Luego puedo usar la sobrecarga de Type.GetMethod (string, type()) para ser más específico sobre mi solicitud.

0

Cuando se invoca OnActionExecuting, el método de acción no se ha ejecutado aún, por lo que no hay forma de saber si ese método de acción va a devolver qué subclase de ActionResult. Entonces, a menos que pueda ir con la implementación del análisis CIL (que creo que puede ponerse feo muy rápidamente), no creo que lo que quiera hacer sea posible.

Dicho esto, ¿no es el hecho de que redirige a los usuarios a una vista cuando el controlador no está disponible lo suficiente? Quiero decir, no entiendo por qué quieres redirigir a los usuarios a un resultado JSON o una vista parcial.

+0

El sitio es un portal para nuestros clientes. Tengo algunas páginas, como la página de inicio con vistas parciales de otros controladores. Quiero devolver una vista parcial con un mensaje de regreso a la vista principal. El controlador doméstico siempre estará disponible, pero el controlador de informes puede no estar disponible. El widget de informes solo debe mostrar un mensaje educado. – Nick

+0

@Nick: ¿por qué no simplemente hacer algo como filterContext.Result = New PartialViewResult (...), independientemente del resultado de la acción real que se devuelva por el método de acción? –

+0

Eso está bien si están esperando la vista parcial. Si presionan/Informes/Indexan, no les agradará que vuelva una vista parcial desnuda. He actualizado mi pregunta con el progreso que he hecho usando la reflexión. – Nick

9

He creado una AuthenticationFilterAttribute en base a este que devuelve resultados diferentes en función del tipo:

/// <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

Solución interesante, gracias. Tenga en cuenta que si filterContext.ActionDescriptor es del tipo System.Web.Mvc.ReflectedActionDescriptor, ya tendrá la propiedad MethodInfo, por lo que no tendrá que tomarse la molestia de determinarlo. –

Cuestiones relacionadas