En ocasiones nos podemos encontrar con en una acción de redirección necesitamos pasar valores recibidos en el QueryString a la acción destino. Si los posibles parámetros son pocos y claramente definidos podemos hacerlo a través del parámetro
routeValues. ¿Pero qué pasa si los posibles parámetros son muchos o, peor aún, indefinidos?
He leído algún comentario por parte del equipo de desarrollo de MVC indicando que es una problemática a la que tienen pensado dar solución en futuras versiones. Pero, ¿qué se puede hacer mientras tanto?
Para mostrar tanto el problema como la solución voy a crear un nuevo proyecto web
RedirectQueryString utilizando la plantilla vacía de ASP.NET y marcando el check MVC.
En la carpeta
Controllers creo un controlador
HomeController con dos acciones:
- La acción Index que será el destino de la redirección. Esta acción simplemente mostrará el nombre de la acción y el contenido de la colección RouteData y el QueryString. Para simplificar el ejemplo devuelve los datos en un string directamente al navegador.
- La acción Redirect que devuelve un resultado RedirectToRouteResult redirigiendo la petición a la acción Index.
El código del archivo
HomeController.cs/HomeController.vb:
namespace RedirectQueryString.Controllers
{
public class HomeController : Controller
{
public string Index()
{
string result = "Action: Index<br/>";
result += "<b>RouteData</b><br/>";
foreach (string key in RouteData.Values.Keys)
result += string.Format("{0}: {1}<br/>"
, key, RouteData.Values[key]);
result += "<b>QueryString</b><br/>";
foreach (string key in Request.QueryString.AllKeys)
result += string.Format("{0}: {1}<br/>"
, key, Request.QueryString[key]);
return result;
}
public RedirectToRouteResult Redirect()
{
return RedirectToAction("Index");
}
}
}
Namespace Controllers
Public Class HomeController
Inherits Controller
Function Index() As String
Dim result As String = "Action: Index<br/>"
result += "<b>RouteData</b><br/>"
For Each key As String In RouteData.Values.Keys
result += String.Format("{0}: {1}<br/>", key, RouteData.Values(key))
Next
result += "<b>QueryString</b><br/>"
For Each key As String In Request.QueryString.AllKeys
result += String.Format("{0}: {1}<br/>", key, Request.QueryString(key))
Next
Return result
End Function
Function Redirect() As RedirectToRouteResult
Return RedirectToAction("Index")
End Function
End Class
End Namespace
Si arrancamos la aplicación y accedemos a las urls
http://<rutapublicacion> o
http://<rutapublicacion>/Home/Redirect obtenemos el mismo resultado:
Pero ¿qué sucede si intentamos acceder a una url del tipo
http://<rutapublicacion>/Home/Redirect?dato=4 ? Obtenemos exactamente el mismo resultado. Entonces, ¿cómo conseguimos pasar el valor de
dato a la acción
Index?
La opción más sencilla es utilizar el parámetro
routeValues del método
RedirectToAction. Para ello reescribimos el código de la acción
Redirect:
public RedirectToRouteResult Redirect()
{
return RedirectToAction("Index"
, new { dato = Request.QueryString["dato"] });
}
Function Redirect() As RedirectToRouteResult
Return RedirectToAction("Index", New With {.dato = Request.QueryString("dato")})
End Function
Ahora al acceder a la url
http://<rutapublicacion>/Home/Redirect?dato=14:
¿Y si los posibles valores fueran muchos o no los conociéramos de antemano? Entonces utilizaríamos la sobrecarga del método
RedirectToAction que acepta una colección del tipo
RouteValueDictionary:
public RedirectToRouteResult Redirect()
{
RouteValueDictionary routeData = new RouteValueDictionary();
foreach (string key in Request.QueryString.AllKeys)
routeData.Add(key, Request.QueryString[key]);
return RedirectToAction("Index", routeData);
}
Function Redirect() As RedirectToRouteResult
Dim routeData As RouteValueDictionary = New RouteValueDictionary()
For Each key As String In Request.QueryString.AllKeys
routeData.Add(key, Request.QueryString(key))
Next
Return RedirectToAction("Index", routeData)
End Function
Para utilizar la clase
RouteValueDictionary deberemos añadir una referencia al namespace
System.Web.Routing:
using System.Web.Routing;
Imports System.Web.Routing
Para probarlo accedemos a la url:
http://<rutapublicacion>/Home/Redirect?dato=4&otrodato=8:
Otro escenario que quería contemplar es cuando utilizamos estos valores del QueryString a lo largo de toda la aplicación y tenemos muchas acciones de redirección. Podríamos repetir el código anterior en todas las acciones o utilizar un atributo de filtro. He de decir que la solución anterior también me ha dado algún problema cuando la acción destino requiere autenticación y debe pasar por una pantalla de login, en este caso también es una alternativa a tener en cuenta.
Para mostrar esta solución vamos a crear una nueva carpeta
Code en el proyecto y en esta carpeta una nuevo archivo de clase llamado
PropagarQueryStringAttribute.cs/PropagarQueryStringAttribute.vb. La clase
PropagarQueryStringAttribute heredará de la clase
ActionFilterAttribute, esta clase implementa las interfaces
IActionFilter e
IResultFilter las cuales definen métodos que se ejecutan antes y después de ejecutar la acción y el resultado de ésta respectivamente. En este caso sobreescribiremos el método que se ejecuta después de ejecutar la acción para añadir a la colección
RouteValues del resultado los valores del QueryString:
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Web;
using System.Web.Mvc;
namespace RedirectQueryString.Code
{
public class PropagarQueryStringAttribute: ActionFilterAttribute
{
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
var redirectResult = filterContext.Result as RedirectToRouteResult;
if (redirectResult != null)
{
NameValueCollection query = filterContext.HttpContext.Request.QueryString;
foreach (string key in query.Keys)
{
if (!redirectResult.RouteValues.ContainsKey(key))
redirectResult.RouteValues.Add(key, query[key]);
}
}
base.OnActionExecuted(filterContext);
}
}
}
Imports System.Web.Mvc
Public Class PropagarQueryStringAttribute
Inherits ActionFilterAttribute
Public Overrides Sub OnActionExecuted(filterContext As ActionExecutedContext)
Dim redirectResult As RedirectToRouteResult = filterContext.Result
If (redirectResult IsNot Nothing) Then
Dim query As NameValueCollection = filterContext.HttpContext.Request.QueryString
For Each key As String In query.Keys
If (Not redirectResult.RouteValues.ContainsKey(key)) Then
redirectResult.RouteValues.Add(key, query(key))
End If
Next
End If
MyBase.OnActionExecuted(filterContext)
End Sub
End Class
Para probar el nuevo atributo crearemos una nueva acción en el controlador
HomeController y le aplicamos el atributo recién creado:
[PropagarQueryString]
public RedirectToRouteResult RedirectQueryString()
{
return RedirectToAction("Index");
}
<PropagarQueryString>
Function RedirectQueryString() As RedirectToRouteResult
Return RedirectToAction("Index")
End Function
Para poder utilizar el atributo
PropagarQueryString en C# deberemos añadir una referencia al namespace
RedirectQueryString.Code:
using RedirectQueryString.Code;
Para probar el nuevo atributo accedemos a la url
http://<rutapublicacion>/Home/RedirectQueryString?dato=4&otrodato=8:
El código completo del ejemplo, tanto en C# como en Visual Basic, está disponible en:
Códigos de muestra - Ejemplos MSDN
No hay comentarios:
Publicar un comentario