net mvc examen developing asp applications asp.net-mvc server.transfer

examen - ¿Cómo simular Server.Transfer en ASP.NET MVC?



70 486 developing asp net web applications (14)

¿El enrutamiento no se ocupa solo de este escenario? es decir, para el escenario descrito anteriormente, podría simplemente crear un controlador de ruta que implemente esta lógica.

En ASP.NET MVC puede devolver un Redirect ActionResult con bastante facilidad:

return RedirectToAction("Index"); or return RedirectToRoute(new { controller = "home", version = Math.Random() * 10 });

Esto realmente dará un redireccionamiento HTTP, que normalmente está bien. Sin embargo, al usar Google Analytics, esto causa grandes problemas porque se pierde el referer original, por lo que Google no sabe de dónde vienes. Esto pierde información útil, como cualquier término de motor de búsqueda.

Como nota al margen, este método tiene la ventaja de eliminar todos los parámetros que pueden provenir de las campañas, pero aún me permite capturarlos desde el lado del servidor. Dejándolos en la cadena de consulta lleva a marcadores de personas o Twitter o blog un enlace que no deberían. Lo he visto varias veces cuando las personas han compartido enlaces a nuestro sitio que contiene identificaciones de campaña.

De todos modos, estoy escribiendo un controlador de ''puerta de enlace'' para todas las visitas entrantes al sitio que puedo redireccionar a diferentes lugares o versiones alternativas.

Por ahora ahora me importa más Google (que los marcadores accidentales), y quiero poder enviar a alguien que visite / a la página que obtendrían si fueran a /home/7 , que es la versión 7 de una página de inicio. .

Como dije antes, si hago esto, pierdo la capacidad de google para analizar el referer:

return RedirectToAction(new { controller = "home", version = 7 });

Lo que realmente quiero es un

return ServerTransferAction(new { controller = "home", version = 7 });

que me dará esa vista sin una redirección del lado del cliente. Sin embargo, no creo que exista tal cosa.

Actualmente, lo mejor que se me ocurre es duplicar toda la lógica del controlador para HomeController.Index(..) en mi acción GatewayController.Index . Esto significa que tuve que mover ''Views/Home'' a ''Shared'' para que fuera accesible. ¿¿Debe haber una mejor manera??..


¿No podría simplemente crear una instancia del controlador al que le gustaría redirigir, invocar el método de acción que desea y luego devolver el resultado? Algo como:

HomeController controller = new HomeController(); return controller.Index();


¿Qué tal una clase TransferResult? (basado en la respuesta de Stans )

/// <summary> /// Transfers execution to the supplied url. /// </summary> public class TransferResult : ActionResult { public string Url { get; private set; } public TransferResult(string url) { this.Url = url; } public override void ExecuteResult(ControllerContext context) { if (context == null) throw new ArgumentNullException("context"); var httpContext = HttpContext.Current; // MVC 3 running on IIS 7+ if (HttpRuntime.UsingIntegratedPipeline) { httpContext.Server.TransferRequest(this.Url, true); } else { // Pre MVC 3 httpContext.RewritePath(this.Url, false); IHttpHandler httpHandler = new MvcHttpHandler(); httpHandler.ProcessRequest(httpContext); } } }

Actualizado: Ahora funciona con MVC3 (usando el código de la publicación de Simon ). Debería (no haber podido probarlo) también funcionar en MVC2 al analizar si se está ejecutando dentro de la interconexión integrada de IIS7 +.

Para una total transparencia; En nuestro entorno de producción, nunca utilizamos TransferResult directamente. Usamos un TransferToRouteResult que a su vez ejecuta el TransferResult. Esto es lo que se está ejecutando en mis servidores de producción.

public class TransferToRouteResult : ActionResult { public string RouteName { get;set; } public RouteValueDictionary RouteValues { get; set; } public TransferToRouteResult(RouteValueDictionary routeValues) : this(null, routeValues) { } public TransferToRouteResult(string routeName, RouteValueDictionary routeValues) { this.RouteName = routeName ?? string.Empty; this.RouteValues = routeValues ?? new RouteValueDictionary(); } public override void ExecuteResult(ControllerContext context) { if (context == null) throw new ArgumentNullException("context"); var urlHelper = new UrlHelper(context.RequestContext); var url = urlHelper.RouteUrl(this.RouteName, this.RouteValues); var actualResult = new TransferResult(url); actualResult.ExecuteResult(context); } }

Y si estás usando T4MVC (si no ... ¡hazlo!), Esta extensión puede ser útil.

public static class ControllerExtensions { public static TransferToRouteResult TransferToAction(this Controller controller, ActionResult result) { return new TransferToRouteResult(result.GetRouteValueDictionary()); } }

Usando esta pequeña joya puedes hacer

// in an action method TransferToAction(MVC.Error.Index());


Descubrí recientemente que ASP.NET MVC no es compatible con Server.Transfer (), así que creé un método stub (inspirado en Default.aspx.cs).

private void Transfer(string url) { // Create URI builder var uriBuilder = new UriBuilder(Request.Url.Scheme, Request.Url.Host, Request.Url.Port, Request.ApplicationPath); // Add destination URI uriBuilder.Path += url; // Because UriBuilder escapes URI decode before passing as an argument string path = Server.UrlDecode(uriBuilder.Uri.PathAndQuery); // Rewrite path HttpContext.Current.RewritePath(path, false); IHttpHandler httpHandler = new MvcHttpHandler(); // Process request httpHandler.ProcessRequest(HttpContext.Current); }


En lugar de simular una transferencia de servidor, MVC aún puede hacer un Server.TransferRequest :

public ActionResult Whatever() { string url = //... Request.RequestContext.HttpContext.Server.TransferRequest(url); return Content("success");//Doesn''t actually get returned }


No es una respuesta per se, pero es evidente que el requisito no sería solo que la navegación real "hiciera" la funcionalidad equivalente de Webforms Server.Transfer (), sino también que todo esto fuera totalmente compatible con las pruebas unitarias.

Por lo tanto, ServerTransferResult debe "verse" como un RedirectToRouteResult, y debe ser lo más similar posible en términos de la jerarquía de clases.

Estoy pensando en hacer esto mirando Reflector, y haciendo cualquier clase de RedirectToRouteResult y también los diversos métodos de la clase base del Controlador, y luego "agregando" el último al Controlador a través de métodos de extensión. Tal vez estos podrían ser métodos estáticos dentro de la misma clase, para facilitar / la pereza de la descarga?

Si me hago cargo de esto, lo publicaré; de lo contrario, tal vez alguien más me venza.


Para cualquiera que utilice el enrutamiento basado en expresiones, usando solo la clase TransferResult anterior, aquí hay un método de extensión de controlador que hace el truco y preserva TempData. No hay necesidad de TransferToRouteResult.

public static ActionResult TransferRequest<T>(this Controller controller, Expression<Action<T>> action) where T : Controller { controller.TempData.Keep(); controller.TempData.Save(controller.ControllerContext, controller.TempDataProvider); var url = LinkBuilder.BuildUrlFromExpression(controller.Request.RequestContext, RouteTable.Routes, action); return new TransferResult(url); }


Puede actualizar el otro controlador e invocar el método de acción para devolver el resultado. Sin embargo, esto requerirá que coloque su vista en la carpeta compartida.

No estoy seguro de si esto es lo que quisiste decir por duplicado, pero:

return new HomeController().Index();

Editar

Otra opción podría ser crear tu propia ControllerFactory, de esta manera puedes determinar qué controlador crear.


Puede usar Server.TransferRequest en IIS7 + en su lugar.


Quería redirigir la solicitud actual a otro controlador / acción, manteniendo la ruta de ejecución exactamente igual que si se solicitara ese segundo controlador / acción. En mi caso, Server.Request no funcionaría porque quería agregar más datos. Esto es en realidad equivalente al controlador actual que ejecuta otro HTTP GET / POST, y luego transmite los resultados al cliente. Estoy seguro de que habrá mejores formas de lograr esto, pero esto es lo que funciona para mí:

RouteData routeData = new RouteData(); routeData.Values.Add("controller", "Public"); routeData.Values.Add("action", "ErrorInternal"); routeData.Values.Add("Exception", filterContext.Exception); var context = new HttpContextWrapper(System.Web.HttpContext.Current); var request = new RequestContext(context, routeData); IController controller = ControllerBuilder.Current.GetControllerFactory().CreateController(filterContext.RequestContext, "Public"); controller.Execute(request);

Su suposición es correcta: puse este código en

public class RedirectOnErrorAttribute : ActionFilterAttribute, IExceptionFilter

y lo estoy usando para mostrar errores a los desarrolladores, mientras usaré un redireccionamiento regular en producción. Tenga en cuenta que no quería utilizar la sesión de ASP.NET, la base de datos ni otras formas de pasar datos de excepción entre solicitudes.


Solo instancia el otro controlador y ejecuta su método de acción.


Html.RenderAction esto al aprovechar el helper Html.RenderAction en una vista:

@{ string action = ViewBag.ActionName; string controller = ViewBag.ControllerName; object routeValues = ViewBag.RouteValues; Html.RenderAction(action, controller, routeValues); }

Y en mi controlador:

public ActionResult MyAction(....) { var routeValues = HttpContext.Request.RequestContext.RouteData.Values; ViewBag.ActionName = "myaction"; ViewBag.ControllerName = "mycontroller"; ViewBag.RouteValues = routeValues; return PartialView("_AjaxRedirect"); }


Server.TransferRequest es completamente innecesario en MVC . Esta es una característica anticuada que solo era necesaria en ASP.NET porque la solicitud llegaba directamente a una página y tenía que haber una forma de transferir una solicitud a otra página. Las versiones modernas de ASP.NET (incluido MVC) tienen una infraestructura de enrutamiento que se puede personalizar para enrutar directamente al recurso que se desea. No tiene sentido que la solicitud llegue a un controlador solo para transferirla a otro controlador cuando simplemente puede hacer que la solicitud vaya directamente al controlador y la acción que desea.

Además, dado que está respondiendo a la solicitud original , no hay necesidad de meter nada en TempData u otro tipo de almacenamiento solo para enrutar la solicitud al lugar correcto. En cambio, llega a la acción del controlador con la solicitud original intacta. También puede estar seguro de que Google aprobará este enfoque, ya que sucede completamente en el lado del servidor.

Si bien puede hacer bastante con IRouteConstraint e IRouteHandler , el punto de extensión más poderoso para el enrutamiento es la subclase de RouteBase . Esta clase se puede ampliar para proporcionar rutas entrantes y generación de URL saliente, lo que lo convierte en una ventanilla única para todo lo que tiene que ver con la URL y la acción que ejecuta la URL.

Entonces, para seguir su segundo ejemplo, para obtener desde / a /home/7 , simplemente necesita una ruta que agregue los valores de ruta apropiados.

public class RouteConfig { public static void RegisterRoutes(RouteCollection routes) { routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); // Routes directy to `/home/7` routes.MapRoute( name: "Home7", url: "", defaults: new { controller = "Home", action = "Index", version = 7 } ); routes.MapRoute( name: "Default", url: "{controller}/{action}/{id}", defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional } ); } }

Pero volviendo al ejemplo original en el que tiene una página aleatoria, es más complejo porque los parámetros de ruta no pueden cambiar en el tiempo de ejecución. Por lo tanto, podría hacerse con una subclase de RouteBase siguiente manera.

public class RandomHomePageRoute : RouteBase { private Random random = new Random(); public override RouteData GetRouteData(HttpContextBase httpContext) { RouteData result = null; // Only handle the home page route if (httpContext.Request.Path == "/") { result = new RouteData(this, new MvcRouteHandler()); result.Values["controller"] = "Home"; result.Values["action"] = "Index"; result.Values["version"] = random.Next(10) + 1; // Picks a random number from 1 to 10 } // If this isn''t the home page route, this should return null // which instructs routing to try the next route in the route table. return result; } public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values) { var controller = Convert.ToString(values["controller"]); var action = Convert.ToString(values["action"]); if (controller.Equals("Home", StringComparison.OrdinalIgnoreCase) && action.Equals("Index", StringComparison.OrdinalIgnoreCase)) { // Route to the Home page URL return new VirtualPathData(this, ""); } return null; } }

Que se puede registrar en el enrutamiento como:

public class RouteConfig { public static void RegisterRoutes(RouteCollection routes) { routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); // Routes to /home/{version} where version is randomly from 1-10 routes.Add(new RandomHomePageRoute()); routes.MapRoute( name: "Default", url: "{controller}/{action}/{id}", defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional } ); } }

Tenga en cuenta que, en el ejemplo anterior, podría tener sentido almacenar también una cookie que registre la versión de la página de inicio en la que ingresó el usuario, de modo que cuando regresen, reciban la misma versión de la página de inicio.

Tenga en cuenta también que al utilizar este enfoque, puede personalizar el enrutamiento para tener en cuenta los parámetros de cadena de consulta (los ignora por completo de forma predeterminada) y enrutar a una acción de controlador apropiada en consecuencia.

Ejemplos adicionales

  • https://.com/a/31958586
  • https://.com/a/36774498
  • https://.com/a/36168395

Editar: actualizado para ser compatible con ASP.NET MVC 3

Siempre que esté utilizando IIS7, la siguiente modificación parece funcionar para ASP.NET MVC 3. Gracias a @nitin y @andy por señalar que el código original no funcionó.

Editar 11/04/2011: TempData se rompe con Server.TransferRequest a partir de MVC 3 RTM

Modificó el código a continuación para lanzar una excepción, pero no hay otra solución en este momento.

Aquí está mi modificación basada en la versión modificada de Markus de la publicación original de Stan. Agregué un constructor adicional para tomar un diccionario de valor de ruta y lo renombré como MVCTransferResult para evitar confusiones de que podría ser solo una redirección.

Ahora puedo hacer lo siguiente para una redirección:

return new MVCTransferResult(new {controller = "home", action = "something" });

Mi clase modificada:

public class MVCTransferResult : RedirectResult { public MVCTransferResult(string url) : base(url) { } public MVCTransferResult(object routeValues):base(GetRouteURL(routeValues)) { } private static string GetRouteURL(object routeValues) { UrlHelper url = new UrlHelper(new RequestContext(new HttpContextWrapper(HttpContext.Current), new RouteData()), RouteTable.Routes); return url.RouteUrl(routeValues); } public override void ExecuteResult(ControllerContext context) { var httpContext = HttpContext.Current; // ASP.NET MVC 3.0 if (context.Controller.TempData != null && context.Controller.TempData.Count() > 0) { throw new ApplicationException("TempData won''t work with Server.TransferRequest!"); } httpContext.Server.TransferRequest(Url, true); // change to false to pass query string parameters if you have already processed them // ASP.NET MVC 2.0 //httpContext.RewritePath(Url, false); //IHttpHandler httpHandler = new MvcHttpHandler(); //httpHandler.ProcessRequest(HttpContext.Current); } }