public ActionResult About()
{
string currentLang = Thread.CurrentThread.CurrentCulture.TwoLetterISOLanguageName;
var viewResult = ViewEngines.Engines.FindView
(ControllerContext, "About_" + currentLang, null);
if (viewResult.View == null)
return View("About_es");
return View("About_" + currentLang);
}
Aunque funcionalmente es correcto, es fácil ver que la introducción de esta lógica contaminaría bastante el código del controlador, además de atentar contra el principio DRY en cuanto tuviéramos varias acciones en las que introducirlo. Obviamente, debemos encontrar otra solución más apropiada y elegante a esta cuestión.
Y como siempre, en ASP.NET MVC existen multitud de fórmulas para resolver problemas de este tipo. En este post vamos a ver dos de ellas: extendiendo el conjunto de ActionResults, y mediante filtros personalizados.
Usando ActionResults
Empezando por el final, el resultado que vamos a lograr es el que podemos ver en el siguiente código de controlador:
public ActionResult About()
{
return LocalizedView();
}LocalizedView() no es sino un método rápido para instanciar un
LocalizedViewResult, cuya implementación veremos a continuación. La lógica que incluye utilizará los motores de vistas para buscar una vista compuesta por el nombre indicado como parámetro (por defecto el nombre de la acción actual) y la cultura establecida en el hilo de ejecución; de no encontrarse, se mostrará la vista correspondiente a la cultura por defecto.
El código de este ActionResult es el siguiente:
public class LocalizedViewResult : ViewResult
{
private string _defaultCulture;
public LocalizedViewResult(string defaultCulture)
{
_defaultCulture = defaultCulture;
}
public override void ExecuteResult(ControllerContext context)
{
string currentCulture = Thread.CurrentThread.CurrentCulture.TwoLetterISOLanguageName;
if (string.IsNullOrWhiteSpace(ViewName))
{
ViewName = context.RouteData.GetRequiredString("action");
}
var viewResult = ViewEngines.Engines.FindView(context, this.ViewName + "_" + currentCulture, null);
if (viewResult.View == null)
ViewName += "_" + _defaultCulture;
else
ViewName += "_" + currentCulture;
base.ExecuteResult(context);
}
}Como se puede observar, el método
ExecuteResult() obtiene el idioma actual y comprueba si existe una vista que siga la convención de nombrado que hemos definido previamente (vista_idioma.cshtml). Si se encuentra, se anexa al nombre original de la vista el sufijo del idioma; en caso contrario se actúa de la misma forma, pero utilizando el nombre por defecto.
Por último, para hacer más rápido su uso desde las acciones, necesitamos implementar el método
LocalizedView() en una clase base de la que deberíamos heredar nuestros controladores:
public class ControladorBase: Controller
{
public LocalizedViewResult LocalizedView(string viewName = null, object model = null,
string master = null, string defaultLang = "es")
{
if (model != null)
{
ViewData.Model = model;
}
return new LocalizedViewResult(defaultLang)
{
ViewName = viewName,
MasterName = master,
ViewData = ViewData,
TempData = TempData,
};
}
}Obviamente, el método
LocalizedView() sólo podría ser utilizado desde controladores descendientes de
ControladorBase. Si preferís no heredar de un controlador base, siempre podríais crearlo como método extensor de
Controller, aunque entonces deberíais invocarlo con el
this por delante, y queda algo menos elegante.
Las siguientes acciones muestran posibles usos de esta implementación. Como se puede observar, son bastante explícitas y dejan clara su intencionalidad:
public ActionResult Prueba()
{
return LocalizedView("About");
}
public ActionResult About()
{
return LocalizedView();
}
Usando filtros
Los filtros presentan una solución bastante menos intrusiva al problema y, para mi gusto bastante mejor, puesto que no obligaría a modificar la implementación tradicional de las acciones. Para hacernos una idea, lo que pretendemos conseguir es lo siguiente:
[LocalizedView]
public ActionResult About()
{
return View();
}
El simple hecho de decorar una acción con el atributo
LocalizedView provocará que el retorno de la acción sea analizado, y se ejecute la lógica de selección de vistas ya descrita anteriormente. Además, el hecho de ser un filtro hace posible su aplicación a controladores completos, o incluso su aplicación global registrándolo en la inicialización de la aplicación.
El código del filtro personalizado es el siguiente:
public class LocalizedViewAttribute : ActionFilterAttribute
{
private string _defaultLang;
public LocalizedViewAttribute(string defaultLang = "es")
{
_defaultLang = defaultLang;
}
public override void OnResultExecuting(ResultExecutingContext filterContext)
{
var viewResult = filterContext.Result as ViewResultBase;
if (viewResult != null)
{
if (string.IsNullOrWhiteSpace(viewResult.ViewName))
{
viewResult.ViewName = filterContext.RouteData.GetRequiredString("action");
}
string currentLang = Thread.CurrentThread.CurrentCulture.TwoLetterISOLanguageName;
var v = ViewEngines.Engines.FindView(
filterContext.Controller.ControllerContext,
viewResult.ViewName + "_" + currentLang, null
);
if (v.View == null)
viewResult.ViewName += "_" + _defaultLang;
else
viewResult.ViewName += "_" + currentLang;
}
base.OnResultExecuting(filterContext);
}
}
En el código se puede observar que en primer lugar se comprueba que el resultado de la acción sea de tipo
ViewResultBase; en caso contrario, es decir, cuando la acción no retorne una vista, simplemente se ejecutará la lógica por defecto.
A continuación, ya asumiendo que la acción retorna una vista, puede observarse la aplicación de la convención de nombrado cuando el nombre de la misma sea nulo, tomándolo de la acción actual. Finalmente, tras comprobar la existencia o no de la vista localizada, se modifica el valor de la propiedad
ViewName del resultado, que identifica la vista que finalmente será enviada al cliente.
Desde el punto de vista de la implementación de las acciones queda prácticamente igual de claro que usando la técnica del
ActionResult, pero en cambio, ésta requiere menos andamiaje. A continuación, dos posibles ejemplos de uso de este atributo:
[LocalizedView]
public ActionResult About()
{
return View();
}
[LocalizedView("en")]
public ActionResult Prueba()
{
return View("About");
}

Y finalmente, por si os interesara ver todo esto en funcionamiento, he dejado
un proyecto de demostración en Skydrive (requiere VS2010+MVC 3).
Publicación original:
http://www.variablenotfound.com/2011/03/retornar-vistas-dependiendo-de-la.html