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 ;)

18 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!
Mostrando entradas con la etiqueta trucos. Mostrar todas las entradas
Mostrando entradas con la etiqueta trucos. Mostrar todas las entradas
martes, 26 de septiembre de 2017
ASP.NET CoreEn las versiones clásicas de ASP.NET, el archivo Global.asax proporcionaba vías para implementar lógica personalizada cuando la aplicación arrancaba y era detenida, lo que podía resultar bastante útil, por ejemplo, para registrar estos eventos en un log, precargar cachés, inicializar bases de datos, “engancharnos” a servicios externos, etc.

Por ejemplo, en el siguiente código vemos cómo podíamos aprovechar los eventos Application_Start() y Application_End() para guardar un registro básico de estos sucesos:
public class MvcApplication : System.Web.HttpApplication
{
    private static string _logFile;
    protected void Application_Start()
    {
        ...
        _logFile = Server.MapPath("log.txt");
        File.AppendAllText(_logFile, DateTime.Now + ": Starting\n");
    }

    protected void Application_End()
    {
        File.AppendAllText(_logFile, DateTime.Now + ": Stopping\n");
    }
}
Sabemos que en ASP.NET Core no existe Global.asax, por lo esta fórmula ya no está disponible. Sin embargo, el nuevo framework ofrece una alternativa bastante razonable mediante el interfaz IApplicationLifetime, proporcionando, entre otras cosas, vías para suscribirnos a eventos relacionados con el ciclo de vida de una aplicación.
martes, 6 de junio de 2017
Google Chrome logoCuando desarrollamos aplicaciones web que funcionan sobre el protocolo HTTPS en la máquina local, lo más habitual es que utilicemos un certificado digital autofirmado o, en cualquier caso, firmado por una entidad no confiable.

Esto hace que los navegadores, por supuesto siempre con el ánimo de preservar nuestra privacidad,  sospechen del sitio web y muestren alertas como la siguiente, donde se nos informa de que el certificado es inválido y que alguien podría estar intentando robarnos información:

Chrome mostrando el error: La conexión no es privada

martes, 16 de mayo de 2017
ASP.NET CoreA la hora de iniciar un nuevo proyecto ASP.NET Core, una de las primeras decisiones que debemos tomar es si el target de éste será .NET Core o .NET Framework.

Muchas veces esto dependerá de los requisitos y el entorno del proyecto; por ejemplo, si nos interesa la capacidad para ejecutarlo o desarrollarlo sobre entornos Linux o Mac, nos tendremos que decantar por .NET Core, pues el framework completo sólo está disponible para Windows. También podemos encontrarnos con que necesitamos (re)utilizar componentes o bibliotecas que aún no han sido portadas a .NET Core, por lo que en este caso el target será .NET Framework (bueno, esto cambiará bastante con la llegada de Net Standard 2.0, pero de momento es lo que hay).

En cualquier caso, la decisión la tomamos justo en el momento de crear el proyecto en Visual Studio, al seleccionar la plantilla que usaremos como base:

Cuadro de diálogo de creación de proyecto ASP.NET Core en Visual Studio 2017

Sin embargo, conforme el proyecto avanza, puede que esta decisión que tomamos tan al principio no sea del todo válida: quizás en su momento elegimos .NET Core, pero ahora debemos cambiar a .NET Framework. O al contrario, porque ahora necesitamos que nuestra aplicación sea multiplataforma. O tal vez necesitemos las dos cosas al mismo tiempo por si acaso…

Nota: aunque aún pululan por ahí aplicaciones creadas con versiones preliminares del SDK, basadas en el difunto project.json, aquí utilizaremos la versión 1.0 del SDK, que ya utiliza el nuevo .csproj. Si todavía no has migrado, ya estás tardando ;)
miércoles, 3 de mayo de 2017
Los que llevamos tiempo trabajando con paquetes NuGet, sabemos que la desinstalación de paquetes requería tradicionalmente entrar en la consola del gestor de paquetes, o bien usar el interfaz gráfico de este gestor de paquetes en Visual Studio, lo que era bastante lento y farragoso.

Y claro, como los humanos somos animales de costumbre, lo normal es que cuando damos el salto a ASP.NET Core continuemos haciéndolo de la misma forma, sin pararnos un segundo a replantearnos si hay mejores formas… lo que me recuerda a esta famosa imagen que anda por Internet desde hace bastante tiempo:

No gracias, no quiero perder el tiempo probando esas ruedas redondas porque estoy demasiado ocupado tirando de mi carro con ruedas cuadradas
(Fuente: Ni idea)

martes, 21 de marzo de 2017
ASP.NET Core MVCSabemos que mientras se renderiza una vista Razor, por defecto el framework MVC va almacenando el resultado en memoria, y sólo al finalizar es cuando comienza a retornarlo al lado cliente.

Por ejemplo, en la ejecución del siguiente código Razor, el usuario que solicitó la página no vería absolutamente nada durante 10 segundos, y de pronto le llegaría el resultado completo:
@using System.Threading.Tasks
@{
    Layout = null;
}
<html>
<head>
    <title>Hello world!</title>
</head>
<body>
    <h1>Let's go</>
    <ul>
        @for (int i = 0; i < 10; i++)
        {
            await Task.Delay(1000);
            <li>@i</li>
        }
    </ul>
</body>
</html>
Nota: Observad que para hacer el retardo, en lugar del típico Thread.Sleep() he utilizado Task.Delay() sólo para recordaros que en ASP.NET Core las vistas se renderizan/ejecutan en un contexto asíncrono y, por tanto, podemos utilizar en su interior llamadas con await como hacemos en otros puntos del código.

martes, 7 de marzo de 2017
Publish Azure WebJobDesde hace algún tiempo estoy sufriendo un error que aparece justo en el momento de publicar un Webjob a Azure desde Visual Studio 2015.

Supongo que tendrá su motivo y se deberá a que estoy haciendo algo mal, pero bueno, el caso es que el error es un poco molesto porque impide la publicación del proyecto y, aunque no es difícil de solucionar, siempre me obliga a perder unos minutos en buscar una solución y aplicarla a mi proyecto.

El error que podemos ver en la ventana de resultados es el siguiente:
Error : El argumento 'DefaultConnection-Web.config Connection String' no puede ser NULL ni estar vacío.
O su versión en inglés:
Error : The 'DefaultConnection-Web.config Connection String' argument cannot be null or empty.
Así que, a modo de nota mental, y si acaso poder echar una mano a alguno que os encontréis ante el mismo escenario, comento dos soluciones que me han funcionado bien. Simplemente elegid la que más os convenza.

Solución 1

Es la más sencilla, y consiste únicamente en eliminar la carpeta /obj del proyecto en cuestión. Tras ello, volvemos a publicar y funcionará todo bien.

Solución 2

La segunda solución es algo más compleja, aunque tampoco para echarse las manos a la cabeza.

El problema está en el archivo de publicación (.pubxml) que estáis usando para publicar el WebJob, que por algún motivo ha perdido la cadena de conexión por defecto del proyecto. Podéis encontrarlo en la carpeta /Properties y tiene una sección como la siguiente:
<PublishDatabaseSettings>
  <Objects>
    <ObjectGroup Name="DefaultConnection" Order="1" Enabled="False" xmlns="">
      <Destination Path="" />
      <Object Type="DbCodeFirst">
        <Source Path="DBMigration" 
           DbContext="MyProject.MyDataContext, MyProject" 
           MigrationConfiguration="MyProject.Migrations.Configuration, MyProject" 
           Origin="Convention" />
      </Object>
    </ObjectGroup>
  </Objects>
</PublishDatabaseSettings>
Lo único que hay que hacer para poder publicar sin problema es transformar la cuarta línea (el tag <Destination>) de la siguiente forma:
<PublishDatabaseSettings>
  <Objects>
    <ObjectGroup Name="DefaultConnection" Order="1" Enabled="False" xmlns="">
      <Destination Path="{deployment connection string}" /> 
      <Object Type="DbCodeFirst">
        <Source Path="DBMigration" 
           DbContext="MyProject.MyDataContext, MyProject" 
           MigrationConfiguration="MyProject.Migrations.Configuration, MyProject" 
           Origin="Convention" />
      </Object>
    </ObjectGroup>
  </Objects>
</PublishDatabaseSettings>
Es decir, establecemos el atributo Path del tag <Destination> al valor "{deployment connection string}" y lo tendremos solucionado.

¡Y eso es todo! En fin, este post trata sobre esos misterios que suceden de vez en cuando y nos hacen dedicar minutos de nuestro preciado tiempo a labores de fontanería. Espero que lo descrito aquí os sea de utilidad en algún momento... o no, porque significaría que no se os ha dado nunca este caso, y eso no es mala cosa ;)

Publicado en Variable not found.
martes, 21 de febrero de 2017
Aunque IIS Express está diseñado principalmente para ser utilizado por desarrolladores en el interior su equipo de trabajo, seguro que la mayoría sabéis que haciendo un par de cambios en la configuración también es posible utilizarlo desde equipos externos, por ejemplo para poder hacer pruebas de una web o servicio desde otros dispositivos conectados a la misma red.

Es sencillo encontrar cómo hacerlo porque hay mucho escrito al respecto en la red, y sin embargo de vez en cuando me encuentro con desarrolladores que no saben que esto es posible hacerlo, así que este post va por vosotros ;D

Partiremos de un proyecto ASP.NET 4.x creado en Visual Studio 2015 o 2017, da igual si se trata de una aplicación MVC, Web Forms, Web API o lo que sea. Para desarrollar el ejemplo, imaginemos que al ejecutarlo en local estamos usando la URL http://localhost:3803/.

Veamos paso a paso cómo acceder a esta aplicación desde otro equipo conectado a la red local, y cómo solucionar algunos de los problemas que podemos encontrar por el camino.

martes, 14 de febrero de 2017
ASP.NET CoreHace algunos meses comentábamos por aquí cómo en ASP.NET Core es posible modificar los archivos de configuración de una aplicación en caliente, sin necesidad de detenerla, siendo los cambios aplicados de forma inmediata.

En aquél momento ya vimos que era realmente sencillo conseguirlo cuando usábamos settings no tipados, bastaba con añadir el parámetro reloadOnChanges a la hora de añadir el origen de configuración, como en el siguiente ejemplo:
public Startup(IHostingEnvironment env)
{
    var builder = new ConfigurationBuilder()
        .SetBasePath(env.ContentRootPath)
        .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
        .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
        .AddEnvironmentVariables();
    Configuration = builder.Build();
}
Sin embargo, también vimos que conseguir lo mismo cuando queríamos acceder a los settings de forma tipada era algo más engorroso, puesto que había que configurar manualmente el proceso de recarga y bindeado de datos con la instancia de la clase de configuración, para poder acceder luego a ella a través de un objeto IOptions<T> disponible en el contenedor de dependencias.
martes, 31 de enero de 2017
.NET CoreHace unos días hablábamos de que próximamente veremos cambios en los archivos de proyecto de las aplicaciones .NET Core, que dejarán de utilizar el popular project.json para volver al tradicional .csproj, aunque rejuvenecido y potenciado para hacerlo más versátil y utilizable en los nuevos entornos.

Para los que ya hemos comenzado a crear aplicaciones y bibliotecas ASP.NET Core, este cambio implica que en algún momento deberemos actualizarlas al nuevo formato de proyectos. Y como ya adelantamos, este proceso no va a resultar especialmente doloroso aunque obviamente tendremos que saber cómo hacerlo.

En este post vamos a tratar tres posibles escenarios de actualización:
Hey, pero antes de continuar, el tradicional disclaimer: tened en cuenta que tanto Visual Studio 2017 como el tooling de .NET Core está aún en preview, así que hay cosas que podrían variar en el futuro próximo.
martes, 17 de enero de 2017
ASP.NET Core MVCComo sabemos, para invocar a un view component e incluir la vista parcial que retorna en el interior de otra vista, debemos utilizar desde ésta el helper @Component.Invoke() o su alternativa asíncrona @Component.InvokeAsync(), por ejemplo de la forma que vemos a continuación:
<div class="cart">    
   @await Component.InvokeAsync(
      "ShoppingCart", 
      new { showImages = false, showButtons = false }
   )
</div>
Y aunque no es especialmente incómodo ni difícil de implementar, es cierto que presenta algunos inconvenientes. En primer lugar, dado el helper @Component.Invoke() es el mismo para todos los view components, ni el entorno ni el compilador pueden ofrecernos ayuda en nombres o parámetros, ni validar que la llamada sea correcta. Cualquier fallo se detectará exclusivamente en tiempo de ejecución.

Asimismo, está claro que el código de las vistas es más legible y fluido si en lugar de utilizar sintaxis imperativa, como son las llamadas a helpers, se utiliza un enfoque declarativo, esto es, si utilizamos etiquetas como los célebres tag helpers.

martes, 13 de diciembre de 2016
ASP.NET Core MVCHace ya algún tiempo, ASP.NET MVC 5.1 introdujo el helper EnumDropDownListFor() para simplificar la creación de cuadros desplegables cuyos elementos eran valores de una enumeración, un escenario relativamente frecuente al desarrollar aplicaciones.

Su uso era bastante sencillo, como se puede observar en el código mostrado a continuación:
// Model:
public enum Language
{     
    CSharp, VbNet, FSharp, NodeJs
}

public class CodeGenerator
{
    public string ApplicationName { get; set; }
    public Language Language { get; set; }
    ...
}

// View:
@model CodeGenerator
...
@Html.LabelFor(m => m.Language)
@Html.EnumDropDownListFor(m => m.Language)
Pues bien, EnumDropDownListFor() ha dejado de existir en ASP.NET Core MVC, aunque por supuesto se ha incluido una alternativa, que incluso diría que es bastante más acertada, para conseguir el mismo objetivo, pues se ha separado de forma clara la generación del desplegable de la obtención de datos para poblarlo.
martes, 29 de noviembre de 2016
ASP.NET CoreComo vimos hace algún tiempo, ASP.NET Core viene equipado de serie con una potente infraestructura de logging que ofrece una fórmula sencilla para registrar trazas de lo que va ocurriendo en nuestra aplicación en tiempo de ejecución.

A la hora de registrar las trazas es posible indicar uno de los niveles definidos en la enumeración LogLevel, cuyos miembros son Trace, Debug, Information, Warning, Error y Critical. Estos miembros definen jerárquicamente la "importancia" de los acontecimientos que registramos, de forma que luego podemos configurar los proveedores para que sólo muestren o persistan mensajes a partir de un nivel determinado.
martes, 25 de octubre de 2016
ASP.NET Core MVCHace muuuchos muchos años ya hablé por aquí de lo extraño que resultaba que el framework MVC, que por aquellos entonces rondaba su segunda versión, no contase de serie con un mecanismo para generar automáticamente el atributo maxlength en los cuadros de texto, máxime cuando esta información solíamos incluirla en anotaciones de las clases del modelo con atributos como StringLength o MaxLength.

Y ha llovido bastante desde entonces, incluso el framework MVC se ha "reseteado" y ahora es ASP.NET Core MVC, pero seguimos sin disponer de esa posibilidad, que cubre un escenario muy frecuente al desarrollar aplicaciones con este framework.

En este post vamos a ver, paso a paso, cómo utilizar los maravillosos tag helpers para incluir este atributo de forma automática en los tags <input>  vinculados a propiedades del modelo cuyos metadatos indiquen un tamaño máximo para el campo.
martes, 11 de octubre de 2016
ASP.NET CoreComo ocurría en ASP.NET MVC 5 y anteriores, en ASP.NET Core MVC también podemos utilizar la información almacenada en la tabla de rutas para generar URLs hacia nuestras acciones, evitando las frágiles dependencias que introducen en las aplicaciones las direcciones hard-coded.

El framework nos ofrece distintas fórmulas para generar direcciones, aunque la más básica es utilizar extensores de IUrlHelper como Action(), Link() o RouteUrl(). Tenemos instancias de dicho interfaz disponibles en la propiedad Url de controladores y vistas, pero también podemos reclamarlas directamente al sistema de inyección de dependencias en otros contextos. Asimismo, en las vistas podemos utilizar helpers específicos para generar hipervínculos, como el clásico Html.Action(), o el más reciente tag helper <a>.

martes, 4 de octubre de 2016
ASP.NET CoreEn ASP.NET Core, sabemos que la clase Startup es donde introducimos el código de inicialización de nuestras aplicaciones. En sus métodos Configure() y ConfigureServices() encontraremos aspectos de todo tipo, como los siguientes, por citar sólo algunos:
  • La configuración del sistema de settings
  • Definición de destinos de logs y niveles de trazas
  • Configuración de componentes de tratamiento de errores y sistemas de depuración y profiling adicionales
  • Configuración de servicios como caching o estado de sesión
  • Inserción y configuración de servicios y middlewares del framework, como los destinados al proceso de contenidos estáticos, internacionalización, CORS, u otros
  • Middlewares personalizados
  • Registro de servicios en el sistema de inyección de dependencias
  • Inicializaciones específicas de la aplicación, como seeds de bases de datos o configuración de mapeos entre objetos
  • Configuración de rutas de la aplicación
Y todo esto, además, salpicado por la lógica para distinguir entre los distintos tipos de entorno de ejecución (development, staging…), o incluso condicionales basados en el contenido de archivos de configuración.
martes, 12 de julio de 2016
ASP.NET CoreCuando ASP.NET “clásico” modificábamos algún setting de la aplicación almacenado en el archivo web.config, esto traía como consecuencia directa el reciclado del pool, o en otras palabras, la aplicación se reiniciaba irremediablemente para recargar los nuevos valores de configuración.

Pero como sabemos, en ASP.NET Core el sistema de configuración ha cambiado para mejor, y ahora los settings podemos almacenarlos en archivos JSON independientes del resto de configuraciones del proyecto, así como en otros formatos y ubicaciones. Y ya que se ponían con el tema, el equipo de ASP.NET ha aprovechado para hacer que podamos modificar los settings sobre los archivos de configuración y que éstos sean aplicados sobre la marcha, sin necesidad  de volver a arrancar la aplicación.
miércoles, 22 de junio de 2016
ASP.NET Core Conforme el desarrollo del nuevo framework ha ido avanzando, se han producido bastantes cambios en el sistema de hosting, que es, al fin y al cabo, quien determina la forma en que podemos cambiarle el nombre a "wwwroot". En el pasado hemos publicado varios posts al respecto (aquí y aquí), y ahora vamos a actualizar los conocimientos tras los cambios aparecidos en la RC2 (sí, esa en la que en teoría no iba a cambiar demasiado respecto a la RC1 ;D).

Estructura de un proyecto ASP.NET Core en Visual Studio, donde aparece la carpeta wwwroot con un icono especialComo sabemos, en proyectos ASP.NET Core, la carpeta "wwwroot", también conocida como "Web root", ejerce como raíz para los contenidos estáticos de la web, es decir, es donde encontraremos todos los archivos que en algún momento enviaremos al lado cliente: javascript, CSS, favicons, imágenes, fonts, etc.

El nombre que se ha dado a la carpeta, "wwwroot", es por convención, y simplemente se trata del valor por defecto establecido por el framework, pero si no nos convence podemos cambiarlo por otro que nos parezca más conveniente. No es que vaya va a ser muy frecuente hacerlo en aplicaciones del mundo real, pero saber que se puede cambiar y cómo hacerlo es interesante porque, además de darnos la libertad de elegir, nos obliga a aprender un poco sobre las interioridades del framework.

Así, si deseamos cambiar el nombre de "wwwoot" por cualquier otro, los pasos serían básicamente tres:
  • Renombrar la carpeta a nivel físico desde el IDE o desde el explorador de archivos.
     
  • Cambiar la configuración de preprocesadores como Gulp, Grunt o WebPack para que utilicen vuestra nueva carpeta como directorio de salida, lo que podemos hacer fácilmente editando sus correspondientes archivos de configuración gulpfile.js, grunt.config, webpack.config.js o el que sea.
     
  • Informar al framework de que esa nueva carpeta es la base para los recursos estáticos, lo que permitirá a otros componentes (por ejemplo, los middlewares encargados de retornar este tipo de contenidos) saber dónde se encuentran los archivos.
En este último punto es donde nos centraremos en el resto del post, viendo algunas fórmulas para conseguirlo.
miércoles, 8 de junio de 2016
ASP.NET CoreUno de los cambios que ha introducido ASP.NET Core RC2 respecto a la anterior Release Candidate es que dejaremos de utilizar HttpPlatformHandler para redirigir las peticiones desde IIS hacia Kestrel que, como sabemos, es finalmente el que ejecuta nuestra aplicación.

El problema es que este componente era un redirector genérico, no ligado a ninguna tecnología concreta, lo que impedía el uso de algunas características interesantes o necesarias para la correcta ejecución de ASP.NET Core en todos los entornos, como el soporte para directorios virtuales de IIS o el reenvío de certificados de cliente.
martes, 15 de marzo de 2016
ASP.NET Core
Seguro que estamos todos de acuerdo en que el nuevo sistema de configuración proporcionado por ASP.NET Core mejora bastante lo que teníamos en las versiones más tradicionales de ASP.NET.

Y la posibilidad de almacenar settings en formatos como .INI o .XML resulta interesante, el formato JSON me parece de lo más cómodo y apropiado para muchos escenarios en los que antes no lo teníamos tan sencillo, como definir configuraciones con un cierto nivel de jerarquía como la siguiente:
martes, 16 de febrero de 2016
Noooooo!Hace unos días, el amigo Eloy Ortiz, ex-alumno de mi curso de ASP.NET MVC 5 en CampusMVP, me comentaba vía email un problema que había encontrado con una aplicación MVC que debía ser utilizada desde clientes con Internet Explorer 11 en un entorno corporativo.

El problema consistía básicamente en que Internet Explorer ignoraba las cookies, tanto las responsables de mantener el seguimiento de estado de la sesión (ASP.NET_SessionId), las de autenticación (.AspNet.ApplicationCookie) u otras cookies creadas a propósito por la aplicación, lo que hacía imposible su uso.

La verdad es que no pude ayudarlo demasiado porque es un tema que no me había ocurrido nunca y en un escenario que no era sencillo de reproducir. Intenté darle algunas pistas sobre qué podía ir mirando para identificar el origen del problema, pero finalmente no llevaron a ninguna parte y probablemente no sirvieron nada más que para aumentar la incertidumbre.

Algunos días después, me contactaba de nuevo diciéndome que había encontrado el problema, y me ha parecido tan curioso que pensé que sería interesante comentarlo por aquí, por si a alguien más le ocurre lo mismo.