
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
- …
Aunque en aplicaciones pequeñas esto no supondrá ningún problema, cuando nos enfrentamos a sistemas de cierto volumen pronto comenzaremos a ver que la clase de inicialización crece bastante, dificultando su legibilidad y, sobre todo, introduciendo bastante "ruido" sobre el contenido que inicialmente debería presentar. Un ejemplo de ello lo tenemos en el siguiente código, que de hecho está simplificado para que no se alargue demasiado ;)
public void ConfigureServices(IServiceCollection services)En lugar de llegar a este extremo, es bastante más razonable seguir un criterio como el que ya usábamos con MVC 5 y anteriores, donde el código de inicialización lo encontrábamos en una carpeta elegida por convención (normalmente
{
services.AddDirectoryBrowser();
services.AddSession(opt => {
...// Configurar servicios de estado de sesión
});
services.AddRouting(opt => {
...// Configurar servicios de routing
});
services.AddDistributedMemoryCache();
services.AddLocalization(opt => {
... // Configuración de localización
});
services.AddAuthorization(configure => {
... // Configuración de servicios de autorización
});
services.AddScoped<ICustomerServices, CustomerServices>();
services.AddScoped<IInvoiceServices, InvoiceServices>();
services.AddScoped<IOrderServices, OrderServices>();
services.AddScoped<IDeliveryServices, DeliveryServices>();
services.AddScoped<INotificationServices, NotificationServices>();
services.AddScoped<IPaymentServices, PaymentServices>();
services.AddScoped<ICatalogServices, CatalogServices>();
services.AddScoped<IProductServices, ProductServices>();
... // Registro de otros muchos servicios de la aplicación en el DI
services.AddSingleton<IMapper>(Mapper.Instance);
services.AddMvc();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env,
ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseBrowserLink();
}
else
{
app.UseExceptionHandler();
}
app.UseStatusCodePagesWithReExecute("/error/{0}");
app.UseStaticFiles();
app.UseDirectoryBrowser(new DirectoryBrowserOptions() { ... });
app.UseRequestLocalization(new RequestLocalizationOptions() { ... });
app.UseCookieAuthentication(new CookieAuthenticationOptions() { ... });
app.UseSession();
app.UseCors(builder => { ... });
app.UseMiddleware<ProfilingMiddleware>();
app.UseMiddleware<GeolocationMiddleware>();
... // Otros middlewares o servicios adicionales de la aplicación
app.UseMvc(routes =>
{
... // Otras convenciones de rutas
routes.MapRoute(name: "areas",
template: "{area:exists}/{controller=Home}/{action=Index}/{id?}");
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
/App_Start
) y estructurado en distintas clases en función del tipo de componente a inicializar.Para ello, sin duda una buena opción sería construir extensores sobre
IServiceCollection
e IApplicationBuilder
y agrupar en ellos el código de inicialización específico para cada clase. Dado que la convención de ubicación App_Start
ya no existe en ASP.NET Core, podríamos elegir cualquier otra que nos interesara.Por ejemplo, en el caso del registro de servicios en el inyector de dependencias, podríamos crear un extensor personalizado de
IServiceCollection
e introducir en él todos los registros específicos de nuestra aplicación:namespace Microsoft.Extensions.DependencyInjectionEl uso del espacio de nombres
{
public static class ServiceCollectionExtensions
{
public static IServiceCollection
AddMyApplicationServices(this IServiceCollection services)
{
services.AddScoped<ICustomerServices, CustomerServices>();
services.AddScoped<IInvoiceServices, InvoiceServices>();
services.AddScoped<IOrderServices, OrderServices>();
services.AddScoped<IDeliveryServices, DeliveryServices>();
services.AddScoped<INotificationServices, NotificationServices>();
services.AddScoped<IPaymentServices, PaymentServices>();
services.AddScoped<ICatalogServices, CatalogServices>();
services.AddScoped<IProductServices, ProductServices>();
... // Registro de otros muchos servicios de la aplicación en el DI
return services;
}
}
}
Microsoft.Extensions.DependencyInjection
es opcional, aunque suele ser una práctica habitual para que intellisense y otras herramientas de ayuda al desarrollo puedan localizar con facilidad esta clase, sin necesidad de tener que añadir usings al archivo.En cualquier caso, de esta forma simplificaríamos bastante el código del método
ConfigureServices()
en la clase de inicialización:public void ConfigureServices(IServiceCollection services)Asimismo, podríamos crear métodos extensores para configurar distintos grupos de servicios, sobre todo extrayendo aquellos que necesitan más fontanería de inicialización, con lo que conseguiríamos tener un código bastante más limpio:
{
services.AddDirectoryBrowser();
services.AddSession(opt => {
// Servicios de estado de sesión
});
services.AddRouting(opt => {
// Servicios de routing
});
services.AddDistributedMemoryCache();
services.AddLocalization(opt => {
// Configuración de localización
});
services.AddAuthorization(configure => {
// Configuración de servicios de autorización
});
services.AddMyApplicationServices(); // Mucho mejor ahora :)
services.AddSingleton<IMapper>(Mapper.Instance);
services.AddMvc();
}
public void ConfigureServices(IServiceCollection services)Lo mismo que hemos hecho con
{
services.AddStaticFilesServices();
services.AddCachingAndSessionServices();
services.AddCustomLocalizationServices();
services.AddCustomAuthorizationServices();
services.AddMyApplicationServices();
services.AddMvcServices();
}
ConfigureServices()
podríamos hacerlo también con el método Configure()
, simplificando así el código de configuración de la aplicación. En este caso, podríamos crear extensores de IApplicationBuilder
para agrupar la introducción de middlewares específicos de la aplicación, como en el siguiente código:namespace Microsoft.Extensions.DependencyInjectionPodríamos hacer lo mismo, por ejemplo, para extraer y agrupar bloques de configuración de componentes del framework dirigidos a objetivos similares, o con cierta relación entre ellos:
{
public static class ApplicationBuilderExtensions
{
public static IApplicationBuilder ConfigureMyCustomMiddlewares(
this IApplicationBuilder app)
{
app.UseMiddleware<ProfilingMiddleware>();
app.UseMiddleware<GeolocationMiddleware>();
... // Otros middlewares o servicios adicionales de la aplicación
return app;
}
}
}
public static IApplicationBuilder ConfigureErrorHandling(Y de esta forma, podríamos también dejar el código del método
this IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/error/exception");
}
app.UseStatusCodePagesWithReExecute("/error/{0}");
return app;
}
Configure()
como los chorros del oro:public void Configure(IApplicationBuilder app, IHostingEnvironment env)Publicado en Variable not found.
{
app.ConfigureDebugging();
app.ConfigureErrorHandling(env);
app.ConfigureStaticFiles();
app.ConfigureAppAuthentication();
app.ConfigureLocalization();
app.ConfigureMyCustomMiddlewares();
app.ConfigureMvc();
}