Saltar al contenido

Artículos, tutoriales, trucos, curiosidades, reflexiones y links sobre programación web ASP.NET Core, MVC, Blazor, SignalR, Entity Framework, C#, Azure, Javascript... y lo que venga ;)

17 años online

el blog de José M. Aguilar

Inicio El autor Contactar

Artículos, tutoriales, trucos, curiosidades, reflexiones y links sobre programación web
ASP.NET Core, MVC, Blazor, SignalR, Entity Framework, C#, Azure, Javascript...

¡Microsoft MVP!
lunes, 7 de marzo de 2011
Estos son los enlaces publicados en Variable not found en Facebook y Twitter desde el domingo, 27 de febrero de 2011 hasta el domingo, 06 de marzo de 2011.

Espero que te resulten interesantes. :-)
Y no olvides que puedes seguir esta información en vivo y en directo desde Variable not found en Facebook, o a través de Twitter.

Publicado en: Variable not found
martes, 1 de marzo de 2011
ASP.NET MVCASP.NET MVC utiliza los mismos mecanismos de ASP.NET para la implementación de sitios web localizados, por lo que podemos utilizar los clásicos recursos definidos en la carpeta App_GlobalResources para ir componiendo los interfaces.

De esta forma, los literales de texto de las vistas son sustituidos por expresiones que, ya en tiempo de ejecución, son tomadas del archivo de recursos correspondiente al idioma actual:

image
Sin embargo, recientemente me he encontrado con un escenario en el que, para facilitar el mantenimiento de los contenidos de las vistas, me resultaba mucho más cómodo disponer de versiones distintas de las mismas en sus correspondientes archivos Razor (.cshtml), y seleccionar en tiempo de ejecución cuál de ellas debía ser enviada en función de la cultura del usuario. Eso sí, en caso de no existir una vista específica para la cultura actual, era necesario retornarla en el idioma por defecto.

imageLa idea, como se muestra en la captura de pantalla de la derecha, es modificar ligeramente la convención de nombrado de archivos de vistas del framework para incluir el código del idioma en que está escrito su contenido.

Así, en este caso, asumiremos que las vistas localizadas se llamarán siempre de la forma {vista}_{idioma}. Por ejemplo, la traducción al inglés de la vista “about” estará definida en el archivo “about_en.cshtml” (usando Razor), al francés en “about_fr.cshtml”, y así sucesivamente. Por simplificar, utilizaremos sólo los dos primeros caracteres del idioma, aunque podrían ser referencias culturales completas, como “about_es-es.cshtml”.

Una vez definida la nueva convención, sólo nos falta implementar en el controlador el mecanismo de selección de vistas en función de la cultura actual. En un primer acercamiento, podríamos implementarla directamente sobre las acciones, como en el siguiente ejemplo:
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");
}

SkydriveY finalmente, por si os interesara ver todo esto en funcionamiento, he dejado un proyecto de demostración en Skydrive (requiere VS2010+MVC 3).



Publicado en: Variable not found.
lunes, 28 de febrero de 2011
Estos son los enlaces publicados en Variable not found en Facebook y Twitter desde el domingo, 20 de febrero de 2011 hasta el domingo, 27 de febrero de 2011.

Espero que te resulten interesantes. :-)
Y no olvides que puedes seguir esta información en vivo y en directo desde Variable not found en Facebook, o a través de Twitter.

Publicado en: Variable not found
lunes, 21 de febrero de 2011
Estos son los enlaces publicados en Variable not found en Facebook y Twitter desde el domingo, 13 de febrero de 2011 hasta el domingo, 20 de febrero de 2011.

Espero que te resulten interesantes. :-)
Y no olvides que puedes seguir esta información en vivo y en directo desde Variable not found en Facebook, o a través de Twitter.

Publicado en: Variable not found
jueves, 17 de febrero de 2011
ASP.NET MVCHacía tiempo que no activaba la compilación de vistas en ASP.NET MVC. La verdad es que si no tienes una máquina potente ralentiza bastante la construcción de las soluciones, pero ahora que la tecnología está de nuevo de mi lado, pensé que sería buena idea activarla en un proyecto MVC 3 y resulta que me he topado con el siguiente error de compilación:
No se pudo cargar el tipo 'System.Data.Entity.Design.AspNet.EntityDesignerBuildProvider'
Pero vamos a empezar por el principio, para los que se han incorporado recientemente a ASP.NET MVC. Recordemos que esta característica permite comprobar la corrección de las vistas (Razor o Webforms) en tiempo de compilación, por lo que es bastante útil para la detección temprana de errores que de otra forma sólo podríamos detectar accediendo a la vista en tiempo de ejecución.

Para activarla, basta con abrir el archivo del proyecto MVC (el .csproj o .vbproj) con un editor y cambiar un detalle en la configuración. Esto podemos hacerlo desde cualquier editor tipo bloc de notas, o desde el mismo Visual Studio abriendo el menú contextual sobre el proyecto y seleccionando la opción “Descargar el proyecto”, como vemos en la captura de pantalla:
Menú contextual > Descargar el proyecto
Justo a continuación, de nuevo sobre el proyecto, volvemos a abrir el menú contextual, seleccionando en este caso la opción “Editar {nombreProyecto}”.
Menú contextual > Editar proyecto
Ya con el archivo abierto, veremos que se trata de un XML, en el que simplemente hay que localizar el tag <MvcBuildViews>false</MvcBuildViews> e introducir el valor true en su interior.
Archivo de configuración
Salvando el archivo, acudimos de nuevo al menú contextual sobre el proyecto, y seleccionamos “Volver a cargar el proyecto”, con lo que ya tendremos activa esta característica. A partir de ese momento, el proceso de construcción del proyecto incluirá la compilación de los archivos de vistas, por lo que podremos ver y corregir los errores detectados en ellas.

El único problema, al que hacía referencia al principio del post, es el extraño error “No se pudo cargar el tipo 'System.Data.Entity.Design.AspNet.EntityDesignerBuildProvider'“ que aparecerá al finalizar la compilación cuando estéis utilizando Entity Framework en el proyecto. Por alguna extraña razón, el hecho de tener activa la compilación de vistas hace que se produzca este error (!).

Bueno, pues si os aparece simplemente debéis añadir la siguiente línea al web.config:

<assemblies>
  ...
  <add assembly="System.Data.Entity.Design, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
</assemblies>

De todas formas, ya os adelanto que aunque tengáis un último modelo de máquina, cuando el proyecto tiene cierto volumen váis a notar el tiempo extra a la hora de compilar y, si sois algo impacientes, lo tendréis la mayor parte del tiempo desactivado.

Publicado en: Variable not found.
miércoles, 16 de febrero de 2011
ASP.NET MVCHace unos días hablaba de ObjectInfo y ServerInfo, dos helpers presentes en System.Web.Helpers, un componente compartido por ASP.NET MVC 3 y WebPages. Hoy vamos a revisar la clase estática Json, también definida en dicho espacio de nombres, que podemos utilizar para codificar objetos en formato JSON (JavaScript Object Notation) y viceversa, crear instancias de objetos partiendo de un texto JSON.

El primero de los sentidos, CLR—>JSON, no es algo que no pudiéramos hacer antes utilizando los serializadores Javascript, pero este componente nos lo pone algo más fácil. Por ejemplo, dada la siguiente clase del Modelo:

Clase del Modelo
… y un controlador en el que suministramos a la vista una instancia de dicha clase:

Código del Controlador

En la Vista podríamos introducir un código como el siguiente para volcar sobre la salida la representación JSON del objeto del modelo. Observad que hasta ahora para conseguir lo mismo teníamos que escribir bastante más código:

Código de la Vista

Fijaos también que el resultado de Json.Encode (bueno, y la alternativa anterior también), debemos pasarlo por una llamada a Html.Raw() para evitar que sea codificado a HTML antes de enviarlo al cliente. En cualquier caso, el resultado puede ser asignado directamente a una variable, puesto que el marcado generado es la representación JSON del objeto suministrado listo para su uso, como puede observarse en  el resultado de procesar la vista anterior:

Código generado

Por otra parte, también podemos utilizar la clase Json para hacer la conversión inversa, es decir, construir un objeto del CLR desde un string que representa un objeto codificado en JSON. Como en el caso anterior, también podíamos utilizar JavascriptSerializer para conseguirlo, pero ahora podemos hacerlo más rápidamente:

Deserialización JSON a objeto

Publicado en: Variable not found.