c# - net - ihttpcontextaccessor
Reemplace el activador de middleware en ASP.NET Core (1)
Estoy intentando ordenar a mi aplicación ASP.NET Core MVC que use un contenedor DI de terceros. En lugar de escribir un adaptador, intento simplemente conectar la biblioteca siguiendo el consejo en esta publicación
Esto funciona bastante bien: puedo reemplazar el IControllerActivator
por el mío que usa el contenedor DI. Sin embargo, me encuentro con un obstáculo al tratar de crear instancias de middleware personalizado que también dependa de dependencias inyectadas. ASP.NET no puede resolver estas dependencias porque no está utilizando mi contenedor DI de terceros. ¿Existe un equivalente de IControllerActivator
para middleware, o estoy atrapado usando el DI incorporado o escribiendo un adaptador?
** EDIT **
Aquí hay un poco más de mi código: en realidad estoy tratando de usar Ninject usando el patrón de arriba.
internal sealed class NinjectControllerActivator : IControllerActivator
{
private readonly IKernel _kernel;
public NinjectControllerActivator(IKernel kernel)
{
_kernel = kernel;
}
[DebuggerStepThrough]
public object Create(ActionContext context, Type controllerType)
{
return _kernel.Get(controllerType);
}
}
Descubrí que tengo dos problemas:
- No puedo inyectar componentes ASP.NET estándar en mis controladores porque Ninject no los conoce
- Mi middleware que usa servicios de aplicaciones no se puede instanciar porque ASP.NET no tiene conocimiento de Ninject.
Para un ejemplo del primer problema, aquí hay un controlador que no IUrlHelper
instancia porque estoy usando IUrlHelper
(también tenga en cuenta el ILogger
, que tampoco ILogger
instancia):
public class SystemController : Controller
{
public SystemController(ILogger logger, IUrlHelper urlHelper)
{
/*...*/
}
}
Aquí hay un ejemplo del segundo problema con un middleware personalizado:
public class CustomMiddleware
{
private RequestDelegate _next;
// this is an application specific service registered via my Ninject kernel
private IPersonService _personService;
public CustomMiddleware(RequestDelegate next, IPersonService personService)
{
_next = next;
_personService = personService;
}
public async Task Invoke(HttpContext context)
{
/* ... */
}
}
Me doy cuenta de que, en teoría, los componentes de ASP.NET deberían estar en su propia cartera y mis componentes de aplicación deberían estar en otro, pero en la práctica a menudo necesito usar componentes de forma transversal (como en los ejemplos anteriores).
Los principios SÓLIDOS dictan que:
los resúmenes son propiedad de las capas superior / política ( DIP )
Lo que significa que nuestro código de aplicación no debería depender directamente del código de la infraestructura, incluso si son abstracciones. En su lugar, deberíamos definir interfaces de roles que se adaptan para el uso de nuestra aplicación.
Entonces, en lugar de depender de una abstracción de Microsoft.Framework.Logging.ILogger
, que podría o no ajustarse a las necesidades específicas de nuestra aplicación, los principios SÓLIDOS nos guían hacia las abstracciones (puertos) que son propiedad de la aplicación, y usan implementaciones de adaptadores que se conectan a código de marco. Aquí hay un ejemplo de cómo podría ser su propia abstracción de ILogger .
Cuando el código de la aplicación depende de su propia abstracción, necesita una implementación de adaptador que pueda reenviar la llamada a la implementación proporcionada por el marco:
public sealed class MsLoggerAdapter : MyApp.ILogger
{
private readonly Func<Microsoft.Framework.Logging.ILogger> factory;
public MsLoggerAdapter(Func<Microsoft.Framework.Logging.ILogger> factory) {
this.factory = factory;
}
public void Log(LogEntry entry) {
var logger = this.factory();
LogLevel level = ToLogLevel(entry.Severity);
logger.Log(level, 0, entry.Message, entry.Exception,
(msg, ex) => ex != null ? ex.Message : msg.ToString());
}
private static LogLevel ToLogLevel(LoggingEventType severity) { ... }
}
Este adaptador se puede registrar en su contenedor de aplicaciones de la siguiente manera:
container.RegisterSingleton<MyApp.ILogger>(new MsLoggerAdapter(
app.ApplicationServices.GetRequiredService<Microsoft.Framework.Logging.ILogger>));
GRAN ADVERTENCIA : No hagas copias directas de las abstracciones del marco. Eso casi nunca conducirá a buenos resultados. debe especificar las abstracciones que se definen en términos de su aplicación. Esto podría incluso significar que un adaptador se vuelve más complejo y necesita múltiples componentes de marco para cumplir su contrato, pero esto da como resultado un código de aplicación más limpio y más fácil de mantener.
Pero si la aplicación de SOLID es demasiado molesta para usted, y solo desea depender directamente de los componentes externos, siempre puede realizar una conexión cruzada de las dependencias requeridas en su contenedor de aplicaciones de la siguiente manera:
container.Register<Microsoft.Framework.Logging.ILogger>(
app.ApplicationServices.GetRequiredService<Microsoft.Framework.Logging.ILogger>);
Es tan fácil como esto, pero tenga en cuenta que para mantener su aplicación limpia y mantenible, es mucho mejor definir abstracciones específicas de la aplicación que se adhieran a los principios SÓLIDOS. También tenga en cuenta que, incluso si hace esto, de todos modos solo necesita algunas de esas dependencias interconectadas. Por lo tanto, es mejor mantener el contenedor de la aplicación lo más separado posible del sistema de configuración vNext.
Con el middleware, hay un problema completamente diferente aquí. En su middleware está inyectando datos de tiempo de ejecución (el next
delegado) en un componente (la clase CustomMiddleware
). Esto le está causando doble dolor, porque esto complica el registro y la resolución del componente y evita que el contenedor lo verifique y diagnostique. En su lugar, debe mover al next
delegado del constructor al delegado Invoke
siguiente manera:
public class CustomMiddleware
{
private IPersonService _personService;
public CustomMiddleware(IPersonService personService) {
_personService = personService;
}
public async Task Invoke(HttpContext context, RequestDelegate next) { /* ... */ }
}
Ahora puede conectar su middleware a la canalización de la siguiente manera:
app.Use(async (context, next) =>
{
await container.GetInstance<CustomMiddleware>().Invoke(context, next);
});
Pero no olvide que siempre puede crear su middleware a mano de la siguiente manera:
var frameworkServices = app.ApplicationServices;
app.Use(async (context, next) =>
{
var mw = new CustomMiddleware(
container.GetInstance<IPersonService>(),
container.GetInstance<IApplicationSomething>(),
frameworkServices.GetRequiredService<ILogger>(),
frameworkServices.GetRequiredService<AspNetSomething>());
await mw.Invoke(context, next);
});
Es realmente desafortunado que ASP.NET llame a sus propios servicios ApplicationServices
, porque es ahí donde está su propio contenedor de aplicaciones; no el sistema de configuración incorporado.