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!
martes, 10 de febrero de 2015
ASP.NET CoreHace poco hablábamos del destierro del Global.asax en ASP.NET Core, y de cómo la clase Startup, siguiendo la convención establecida por este framework, era el lugar elegido para introducir código de inicialización.

Pero además del código personalizado que podamos introducir, es donde, por defecto, se configuran aspectos tan importantes como los middlewares que guiarán el proceso de las peticiones, o la inyección de dependencias incluida de serie en el framework.

En este post vamos a estudiar un poco más a fondo la clase de inicialización de ASP.NET Core, y comenzaremos viendo qué vamos a encontrar en ella, y las convenciones a las que hay que ceñirse para que el entorno pueda inicializarse correctamente.

1. Convenciones de la clase Startup

Cuando arranca una aplicación web, los componentes de infraestructura de ASP.NET Core buscan esta clase en su ensamblado principal, produciéndose un error si no se encuentra. Para que todo funcione bien esta clase de inicialización debe cumplir una serie de requisitos que se describen a continuación:
  • Debe llamarse Startup, y ser pública e instanciable. Puede encontrarse en cualquier espacio de nombres, aunque el sistema la localizará más rápidamente si está en el espacio de nombres raíz del proyecto (por ejemplo, MyApp.StartUp).
     
  • Debe contener, como mínimo, un método llamado Configure() de tipo void, que es el que se ejecutará justo al iniciar la aplicación. Podríamos decir que este método el equivalente al clásico Application_Start() del Global.asax. y es donde configuraremos todos los aspectos básicos de la misma, como los middlewares que incluiremos en el pipeline. 
     
  • Opcionalmente, puede contener un método llamado ConfigureServices(), que es donde se introducirá el código de inicialización específico para el sistema de inyección de dependencias. Lo explicaremos más adelante.
Por tanto, teniendo en cuenta las convenciones, el siguiente código es una clase de inicialización válida, aunque nuestra aplicación no haría nada porque no estamos introduciendo en el pipeline ningún middleware para procesar las peticiones:


2. Configuraciones específicas por entorno

Existe una variante en el nombrado de la clase y sus métodos que permite especificar distintas configuraciones de arranque en función del nombre del entorno en el que estemos ejecutando la aplicación. Los nombres de entorno son configurables, por ejemplo podemos tener un entorno llamado “development” para el desarrollo, otro llamado “release” para producción, “staging” para pruebas, etc.

La forma de establecer el entorno activo en el servidor está todavía muy verde al encontrarse en desarrollo; la fórmula más sencilla a día de hoy es establecer una variable de entorno a nivel de sistema llamada ASPNET_ENV (o KRE_ENV si estamos usando una versión beta anterior a la actual).

SET ASPNET_ENV=Release

En teoría también se podría modificar mediante el archivo de configuración del proceso host, o incluso pasándolo como parámetro al servidor utilizado, pero la verdad es que el comportamiento que he encontrado hasta el momento y la falta de ejemplos ha hecho que desista de intentarlo.

En cualquier caso, si queremos hacer uso de esta característica y crear configuraciones distintas para cada entorno de ejecución, podemos optar por una o varias de las siguientes convenciones:
  • Si existe una clase llamada Startup[EnvironmentName], por ejemplo StartupDevelopment, se usará ésta en lugar de la clase por defecto Startup.
     
  • Si en la clase de inicialización seleccionada según la convención usada existe un método llamado Configure[EnvironmentName], por ejemplo ConfigureRelease(), se usará éste en lugar de Configure() a secas.
     
  • De la misma forma, si existe un método llamado Configure[EnvironmentName]Services, como por ejemplo ConfigureDevelopmentServices(), se usará éste en lugar de ConfigureServices().
Por ejemplo, el siguiente código muestra una clase de inicialización con dos métodos Configure(), uno para cuando se ejecuta en un entorno llamado “Development” y otro para cuando estamos en el entorno “Release”:

3. Un primer vistazo a Configure()

El método Configure() (o su variante Configure[Environment]) es donde introduciremos normalmente código de inicialización y configuraremos el pipeline de ejecución, introduciendo en él los middlewares que usaremos en nuestra aplicación.

Aunque en los ejemplos anteriores lo hemos presentado sin parámetros, nunca será así. Lo habitual será encontrarlo aceptando diversos parámetros, que serán suministrados por el runtime utilizando el sistema integrado de inyección de dependencias, y lo mínimo que llevaremos a la práctica será algo como lo siguiente:

image

Ese objeto app que implementa el interfaz IApplicationBuilder es una representación del pipeline que la infraestructura de ASP.NET suministrará a nuestro método, cuyos métodos y extensores son las herramientas que utilizaremos para configurar los middlewares. Aunque volveremos a hablar de ello en próximos posts, de momento podéis echar un vistazo a algunas entradas sobre OWIN (por ejemplo middlewares personalizados, Map() y Run()) porque se hace exactamente igual.

Por ver un ejemplo rápido, el siguiente código corresponde al método Configure() creado por la plantilla de proyectos ASP.NET Core MVC:



(Nota: he simplificado algo el código original para que sea más sencillo de leer)

Aunque sea la primera vez que le echáis el vistazo, si tenéis claro el concepto de middleware seguro que podréis entender el código sin problema. En líneas generales:
  • En primer lugar, si el entorno es “Development”, se añaden al pipeline los middlewares que aportan la funcionalidad browser link (para depuración de HTML, CSS) y páginas de error detalladas.
  • En caso contrario, se añade un manejador para páginas de error “civilizada”, apto para entornos de producción.
  • A continuación, se añade el manejador para archivos estáticos (HTML, JS, etc.)
  • Seguidamente, se inserta el middleware proporcionado por Identity Framework, que se encargará de proporcionar el entorno de seguridad a módulos posteriores del pipeline.
  • Finalmente, se añade el framework MVC, en el que se configuran las rutas por defecto.
Esos métodos UseXXX() que vemos son los que introducen los middlewares en el pipeline, y como vemos, en algunos casos incluyen parámetros o configuraciones específicas. En todos los casos se trata de métodos de extensión definidos sobre IApplicationBuilder por cada uno de los middlewares para facilitar el trabajo con ellos.

Gráficamente, estas serían las configuraciones del pipeline para el entorno de desarrollo y otros entornos que hemos configurado anteriormente:

Entorno “Development”
Entornos distintos a “Development”
ASP.NET Pipeline en el entorno "Development" ASP.NET Pipeline en el entorno distinto a "Development"

Observad también que en el código anterior se ha añadido al método Configure() un parámetro de entrada de tipo IHostingEnvironment que posibilita el acceso a distintas propiedades del entorno de ejecución, y que se utiliza más tarde para averiguar el nombre del entorno.

Esta será la forma habitual de actuar al implementar nuestro propio Configure(): cuando necesitemos los servicios de algún componente del framework, simplemente lo añadiremos como parámetro de entrada, y el sistema de inyección de dependencias se encargará de proporcionarnos una instancia.

4. Un vistazo rápido a ConfigureServices()

El otro método que encontraremos normalmente en la clase Startup, aunque a diferencia del anterior no es obligatorio, es ConfigureServices(). Éste se ejecuta antes que Configure() y su fin principal es registrar componentes en el sistema de inyección de dependencias, es decir, aquí es donde registraremos las equivalencias entre interfaces y clases que el sistema necesita conocer para poder proporcionarnos las dependencias de nuestros componentes correctamente resueltas.

La pinta que tiene este método en la plantilla de proyectos ASP.NET Core es la siguiente:



El objeto IServiceCollection que recibimos como parámetro representa al contenedor donde se registran los servicios que usará la aplicación, y las llamadas que podemos observar en el código a los métodos AddXXX() son extensores de este interfaz implementados por los middlewares o frameworks que requieren una configuración de servicios.

Por ejemplo, la llamada services.AddMvc() hace que el framework MVC que registre sus servicios en el contenedor. Esto tendremos que hacerlo para cada middleware o framework que añadamos al pipeline y que requiera cierta configuración, como MVC, Identity framework o Entity framework.

De hecho, si no añadimos esta configuración de servicios inicial, habrá frameworks que no dejen ni siquiera iniciar la aplicación, como se puede observar en la captura de la derecha. Otros, como Identity, no se quejarán en el arranque, simplemente provocarán errores cuando se vaya a utilizar algunas de sus características que requieran de instancias de clases proporcionadas por este marco de trabajo.

Por otra parte, además de usar ConfigureServices() para configurar los servicios de los frameworks, middlewares u otros componentes utilizados a nivel de infraestructura, este también será un buen lugar para introducir el registro de servicios de nuestras aplicaciones, para lo que el interfaz IServiceCollection nos aporta algunos extensores más, como los que vemos a continuación:

services.AddScoped<IMyRepository, MyRepository>();
services.AddSingleton<ISomething, Something>();

El tema de inyección de dependencias en ASP.NET Core es bastante amplio, y seguro que le dedicaremos algún post más adelante. Por adelantar algo, podríamos decir que en la mayoría de escenarios no será necesario utilizar contenedor de inversión de control, como los tradicionales Unity, Ninject o Autofac para registrar nuestras dependencias y obtener instancias, puesto que ASP.NET Core trae ya de serie mecanismos que cubrirán muchos de los casos comunes. Pero, por supuesto, podremos seguir usándolos si es lo que nos interesa… lo importante, como siempre, es tener opciones :)

Bueno, y creo que con esto es suficiente para tener una idea de lo que encontraremos en el código de inicialización de ASP.NET Core; un primer paso para empezar a conocer una clase que nos acompañará durante los próximos años cuando desarrollemos para la web, y a la que tenemos que ir empezando a tomarle cariño ;)

Publicado en Variable not found.

2 Comentarios:

Unai dijo...

Me has jodido un post!

José María Aguilar dijo...

jajaja! Lo siento tío, vamos a tener que hacer un calendario común o algo para no pisarnos ;)

Pero conociéndote, seguro que puedes aportar mucho más sobre la clase Startup de lo que yo he sido capaz de plasmar aquí, así que adelante con tu artículo de todas formas :)

Nos vemos pronto, un abrazo!