2012-03-04 26 views
7

Estoy trabajando en una solución ASP.NET MVC que tiene varios menús diferentes. El menú para mostrar depende del rol del usuario que está conectado actualmente.¿Cómo redirigir la acción MVC sin devolver 301? (usando MVC 4 beta)

En MVC 3 tenía un código personalizado para admitir este escenario, al tener un único método de controlador que devolvería el menú correcto. Haría esto posponiendo la solicitud al controlador apropiado y a la acción dependiendo del usuario actual.

Este código parece estar roto en MVC 4 y estoy buscando ayuda para solucionarlo.

En primer lugar, añade una clase TransferResult ayudante para realizar la redirección:

public class TransferResult : RedirectResult 
{ 
    #region Transfer to URL 
    public TransferResult(string url) : base(url) 
    { 
    } 
    #endregion 

    #region Transfer using RouteValues 
    public TransferResult(object routeValues) : base(GetRouteUrl(routeValues)) 
    { 
    } 

    private static string GetRouteUrl(object routeValues) 
    { 
     var url = new UrlHelper(new RequestContext(new HttpContextWrapper(HttpContext.Current), new RouteData()), RouteTable.Routes); 
     return url.RouteUrl(routeValues); 
    } 
    #endregion 

    #region Transfer using ActionResult (T4MVC only) 
    public TransferResult(ActionResult result) : base(GetRouteUrl(result.GetT4MVCResult())) 
    { 
    } 

    private static string GetRouteUrl(IT4MVCActionResult result) 
    { 
     var url = new UrlHelper(new RequestContext(new HttpContextWrapper(HttpContext.Current), new RouteData()), RouteTable.Routes); 
     return url.RouteUrl(result.RouteValueDictionary); 
    } 
    #endregion 

    public override void ExecuteResult(ControllerContext context) 
    { 
     HttpContext httpContext = HttpContext.Current; 
     httpContext.RewritePath(Url, false); 
     IHttpHandler httpHandler = new MvcHttpHandler(); 
     httpHandler.ProcessRequest(HttpContext.Current); 
    } 
} 

En segundo lugar, he modificado T4MVC para emitir algunos métodos controlador de ayuda, que resulta en cada controlador que tiene este método:

protected TransferResult Transfer(ActionResult result) 
{ 
    return new TransferResult(result); 
} 

Esto me permitió tener una acción de controlador compartido para devolver un menú, sin tener que saturar las vistas con ninguna lógica condicional:

public virtual ActionResult Menu() 
{ 
    if(Principal.IsInRole(Roles.Administrator)) 
     return Transfer(MVC.Admin.Actions.Menu()); 
    return View(MVC.Home.Views.Partials.Menu); 
} 

Sin embargo, no parece funcionar con la versión preliminar actual de MVC 4. Me da el siguiente error (apuntando a la línea "httpHandler.ProcessRequest") el código en ExecuteResult en la clase TransferResult:

'HttpContext.SetSessionStateBehavior' can only be invoked before 
'HttpApplication.AcquireRequestState' event is raised. 

¿Alguna idea de cómo solucionar esto?

PD: Me doy cuenta de que podría lograr lo mismo con una simple extensión HtmlHelper, que es lo que estoy usando actualmente como solución alternativa. Sin embargo, tengo muchos otros escenarios donde este método me ha permitido mezclar y reutilizar acciones, y odiaría renunciar a esta flexibilidad cuando me muevo a MVC 4.

+0

Usted debe poner ese método en una base de clase' controlador', no en todos los controladores de – SLaks

+0

@SLaks Eso parece ser un micro-optimización teniendo en cuenta que el código es generado por T4MVC y es solo un trazador de líneas. Tampoco hace nada para ayudar a resolver el problema;) –

+0

Debe ser un día lento; ni siquiera puede obtener un voto positivo: o) –

Respuesta

5

A veces creo que "MVC" debería llamarse "RCMV" para "Vista de modelo de controlador de enrutador", ya que ese es realmente el orden en que suceden las cosas. Además, dado que es simplemente "MVC", las personas siempre tienden a olvidarse del enrutamiento. Lo mejor de MVC es que el enrutamiento es configurable y extensible. Creo que lo que estás tratando de hacer podría resolverse con un manejador de ruta personalizado.

No he probado esto, pero usted debería ser capaz de hacer algo como esto:

routes.Add(
    new Route(
     "{controller}/{action}/{id}", 
     new RouteValueDictionary(new { controller = "Home", action = "Menu" }), 
     new MyRouteHandler(Roles.Administrator, new { controller = "Admin" }))); 

A continuación, el controlador de ruta se vería así:

public class MyRouteHandler : IRouteHandler 
{ 
    public string Role { get; set; } 

    public object RouteValues { get; set; } 

    public MyRouteHandler(string role, object routeValues) 
    { 
     Role = role; 
     RouteValues = routeValues; 
    } 

    public IHttpHandler GetHttpHandler(RequestContext requestContext) 
    { 
     return new MyHttpHandler(Role, RouteValues); 
    } 
} 

Y finalmente manejar la re -routing en su HttpHandler:

public class MyHttpHandler : IHttpHandler 
{ 
    public string Role { get; set; } 

    public object RouteValues { get; set; } 

    public MyHttpHandler(string role, object routeValues) 
    { 
     Role = role; 
     RouteValues = routeValues; 
    } 

    public void ProcessRequest(HttpContext httpContext) 
    { 
     if (httpContext.User.IsInRole(Role)) 
     { 
      RouteValueDictionary routeValues = new RouteValueDictionary(RouteValues); 

      // put logic here to create path similar to what you were doing 
      // before but you will need to replace any keys in your route 
      // with the values from the dictionary created above. 

      httpContext.RewritePath(path); 
     } 

     IHttpHandler handler = new MvcHttpHandler(); 
     handler.ProcessRequest(httpContext); 
    } 
} 

Eso no puede ser 100% correcto, pero debe estar en el derecho d irection de una manera que no debe toparse con nada obsoleto en MVC4.

+0

+1 por encontrar una solución Sin embargo, el código anterior proporcionaba una única solución compartida que podía usar en todos mis métodos de control según fuera necesario, mientras que esto requiere un código personalizado para cada uso. Preferiría una solución más genérica, si existe. –

+0

Respuesta actualizada ser más genérico. Otra ventaja de esta solución es que se puede poner en un biblioteca utilizable (suponiendo que los nombres de su función también se pueden poner en esa biblioteca) y luego puede usarla en múltiples aplicaciones de MVC. Su método estático era interno para su aplicación. –

+0

Con la solución anterior, tenía una biblioteca de clases con los bits compartidos y una plantilla T4MVC.tt modificada para generar los métodos de controlador, pero siempre lo incluiría de todos modos. Hasta donde puedo decir, esto solo funciona para las rutas que configuro manualmente para habilitar esta anulación al especificar el controlador personalizado. ¿O me estoy perdiendo algo? –

1

Creo que se debe incluir un TransferResult en el framework sin que cada desarrollador tenga que volver a implementarlo para las diferentes versiones cuando se rompa. (como en este hilo y, por ejemplo, el siguiente hilo también: Implementing TransferResult in MVC 3 RC - does not work).

Si está de acuerdo conmigo, me gustaría animarlo a votar por "Servidor".Transferir" Para llegar a ser incluido en el marco MVC sí:. http://aspnetwebstack.codeplex.com/workitem/798