Crono usa la inyección de dependencias (DI) de Microsoft para lograr la inversión de control (IoC) entre las clases y sus dependencias. Dado que la DI de Microsoft carece de algunas características como adaptadores, decoradores, fuentes de registro, etc. Además, se utiliza Autofac. Sin embargo, internamente, Autofac también funciona con Microsoft DI.
"La idea detrás de la inversión de control es que, en lugar de unir las clases en su aplicación y dejar que las clases "renueven" sus dependencias, lo cambia para que las dependencias se pasen durante la construcción de la clase". — Autofac
Un ejemplo sencillo de escritor de salida en la documentación de ilustra muy bien la idea básica detrás de IoC y la arquitectura desacoplada.
Registro de servicios
Para que se resuelvan las dependencias, se debe registrar el servicio correspondiente. El registro se realiza a través de una clase de inicio heredada de IniciadorBase. Debe declararse como interno. En el núcleo de Crono, las clases de inicio se encuentran en una carpeta de iniciador de la sección de código relacionada (por ejemplo, Finanzas). Invalide el método para agregar servicios mediante Autofac o invalide el método para agregar servicios mediante el archivo . En cualquier caso, los servicios resueltos terminan en el mismo contenedor de inyección de dependencias. ConfigurarContenedorConfigurarServiciosIServiceCollection
Cada componente dependiente o llamada obtiene una instancia nueva y única (valor predeterminado) Resolve
InstancePerLifetimeScope
AddScoped
Cada componente dependiente o llamada dentro de una sola instancia, generalmente la solicitud HTTP, obtiene la misma intancia compartida. Los componentes dependientes en diferentes ámbitos de duración obtendrán diferentes instancias. ResolveILifetimeScope
SingleInstance
AddSingleton
Cada componente dependiente o llamada obtiene la misma instancia compartida. Resolve
Registros especiales
Utilice Autofac para registos de servicios especiales. Por ejemplo, un TareaActivador usa metadatos con nombre para busqueda de tipos como este: ContainerBuilder
protected override void Load(ContainerBuilder builder)
{
builder.RegisterType<TareaProgramador>()
.As<ITareaProgramador>()
.As<IHostedService>()
.SingleInstance();
builder.RegisterType<TareaEjecutor>().As<ITareaEjecutor>().InstancePerLifetimeScope();
builder.RegisterType<TareaActivador>().As<ITareaActivador>().InstancePerLifetimeScope();
// Registrar todos los impls de ITarea
var tareaTipos = _appContexto.EscanerTipo.BuscarTipos<ITarea>();
foreach (var tareaTipo in tareaTipos)
{
var tipoNombre = tareaTipo.ObtenerAtributo<TareaNombreAttribute>(true)?.Nombre ?? tareaTipo.Name;
var registracion = builder.RegisterType(tareaTipo)
.Named<ITarea>(tipoNombre)
.Keyed<ITarea>(tareaTipo)
.InstancePerAttributedLifetime()
.WithMetadata<TareaMetadatos>(m =>
{
m.For(em => em.Nombre, tipoNombre);
m.For(em => em.Tipo, tareaTipo);
});
}
}
public class TareaActivador : ITareaActivador
{
public virtual Type ObtenerTipoClrTarea(string nombreTipoNormalizado, bool lanzarEnError = false)
{
Guarda.NoVacio(nombreTipoNormalizado);
var tareaDiferida = _componentContext.ResolveOptionalNamed<Lazy<ITarea, TareaMetadatos>>(nombreTipoNormalizado);
if (lanzarEnError && tareaDiferida == null)
{
throw new TareaActivacionExcepcion($"Ninguna tarea registrada para '{nombreTipoNormalizado}'.");
}
return tareaDiferida?.Metadata?.Tipo;
}
}
Los componentes registrados por clave (un parámetro de tipo) pueden ser resueltos por un delegado de función. object
constructor.RegisterType<ReglaServicio>().As<IReglaServicio>().InstancePerLifetimeScope();
// Representación.
constructor.RegisterType<ReglaPlantillaSelector>().As<IReglaPlantillaSelector>().InstancePerLifetimeScope();
// Registre el delegado de resolución del proveedor.
constructor.Register<Func<ReglaAmbito, IReglaProveedor>>(c =>
{
// TODO: registrar proveedores explícitamente
var cc = c.Resolve<IComponentContext>();
return clave => cc.ResolveKeyed<IReglaProveedor>(clave);
});
Siempre que se necesite un servicio registrado como parte de una configuración de configuración fuertemente tipada, debe usar o al implementar estas interfaces, puede configurar un objeto de opciones mediante cualquier servicio necesario del contenedor de inyección de dependencias. IConfigureOptions<T>IConfigureNamedOptions<T>T
servicios.TryAddEnumerable(
ServiceDescriptor.Singleton<IConfigureOptions<IdentityOptions>, IdentidadOpcionesConfigurador>());
internal sealed class IdentidadOpcionesConfigurador : IConfigureOptions<IdentityOptions>
{
private readonly IAplicacionContexto _appContexto;
public IdentidadOpcionesConfigurador(IAplicacionContexto appContexto)
{
_appContexto = appContexto;
}
public void Configure(IdentityOptions options)
{
var usuarioConfiguraciones = _appContexto.Servicios.Resolve<UsuarioConfiguraciones>();
var usr = options.User;
usr.RequireUniqueEmail = true;
// Agregue espacio a la lista predeterminada de caracteres permitidos y caracteres que se permitieron explícitamente para los nombres de los usuarios.
// INFO: No usamos += para agregar caracteres especiales a la lista predeterminada,
// porque la lista predeterminada puede haber sido restablecida por el caso else donde establecimos los caracteres permitidos en nulo.
if (usuarioConfiguraciones.CaracteresPermitidosNombreUsuario.TieneValor())
{
usr.AllowedUserNameCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._@+ " + usuarioConfiguraciones.CaracteresPermitidosNombreUsuario;
}
else
{
// Si no se permitieron caracteres explícitamente para los nombres de los usuarios, permitimos todos los caracteres.
usr.AllowedUserNameCharacters = null;
}
var clave = options.Password;
clave.RequiredLength = usuarioConfiguraciones.ClaveLongitudMinima;
clave.RequireDigit = usuarioConfiguraciones.ClaveRequiereDigitos;
clave.RequireUppercase = usuarioConfiguraciones.ClaveRequiereMayusculas;
clave.RequiredUniqueChars = usuarioConfiguraciones.ClaveRequiereCaracteresUnicos;
clave.RequireLowercase = usuarioConfiguraciones.ClaveRequiereMinusculas;
clave.RequireNonAlphanumeric = usuarioConfiguraciones.ClaveRequiereNoAlfanumericos;
var signIn = options.SignIn;
signIn.RequireConfirmedAccount = false;
signIn.RequireConfirmedPhoneNumber = false;
signIn.RequireConfirmedEmail = false;
var bloqueo = options.Lockout;
bloqueo.AllowedForNewUsers = true;
bloqueo.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(2);
bloqueo.MaxFailedAccessAttempts = 3;
}
}
La configuración anterior debe volver a activarse si se realiza algún cambio en ella. Esto se puede hacer de la siguiente manera:
private readonly Lazy<IConfigureOptions<IdentityOptions>> _identidadOpcionesConfigurador;
private readonly IOptions<IdentityOptions> _identidadOpciones;
[HttpPost]
public async Task<IActionResult> Configurar(ConfiguracionModel model)
{
var identidadActualizado = DebeActualizarOpcionesIdentidad(modelo.UsuarioConfiguraciones, usuarioConfiguraciones);
await MapeadorFabrica.MapearAsync(modelo.UsuarioConfiguraciones, usuarioConfiguraciones);
if (identidadActualizado)
{
// Guarde usuarioConfiguraciones ahora para que se puedan aplicar nuevos valores en IdentityOptionsConfigurer.
await Servicios.ConfiguracionFabrica.GuardarConfiguracionesAsync(usuarioConfiguraciones, organizacionAmbito);
_identidadOpcionesConfigurador.Value.Configure(_identidadOpciones.Value);
}
}
Resolución de servicios
Una vez que se ha registrado un servicio, se puede resolver desde el contenedor de IoC o desde ámbitos de duración secundarios.
"Si bien es posible resolver componentes directamente desde el contenedor raíz, hacerlo a través de la aplicación en algunos casos puede resultar en una pérdida de memoria. Se recomienda resolver siempre los componentes de un ámbito de duración siempre que sea posible para asegurarse de que las instancias de servicio se eliminen correctamente y se recojan los elementos no utilizados". — Autofac
Puede usar Autofac o Microsoft para resolver dependencias. En los lugares en los que a menudo es necesaria la resolución de dependencias, normalmente se proporciona una instancia de estos como parámetro. También puede obtener una instancia de este tipo a través de la inyección de constructores. ILifetimeScopeIServiceProvider
Nunca resuelva las dependencias con ámbito de IAplicacionContexto.Servicios, ya que es el contenedor de servicios de la aplicación raíz. Solo las dependencias singleton deben resolverse a partir de él.
Evite resolver dependencias a través de MotorContexto.Actual.Ambito siempre que sea posible. Puede hacer que la escritura de pruebas unitarias sea difícil o incluso imposible.
Si el código siempre se ejecuta en el contexto de una solicitud HTTP, las dependencias también se pueden resolver de la "manera ASP.NET" mediante . Concede acceso al contenedor de servicio de la solicitud. HttpContext.RequestServices
/// <summary>
/// No permite el acceso de robots a la ruta. También se usa para llenar dinámicamente el archivo robots.txt.
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public sealed class NoPermitirRobotAttribute : Attribute, IAuthorizationFilter, IOrderedFilter
{
// Codigo adicional omitida para más claridad
public void OnAuthorization(AuthorizationFilterContext context)
{
var usuarioAgente = context.HttpContext.RequestServices.GetRequiredService<IAgenteUsuario>();
if (usuarioAgente.EsRobot())
{
context.Result = new StatusCodeResult(StatusCodes.Status403Forbidden);
}
}
}
Inyección en constructores
La inyección en constructores es la forma preferida de resolver dependencias. Su componente debe estar registrado en DI para poder utilizarlo.
public partial class EnviarMensajesEnColaTarea : ITarea
{
private readonly CronoDbContexto _db;
private readonly ICorreoElectronicoColaServicio _correoElectronicoColaServicio;
public EnviarMensajesEnColaTarea(
CronoDbContexto db,
ICorreoElectronicoColaServicio correoElectronicoColaServicio)
{
_db = db;
_correoElectronicoColaServicio = correoElectronicoColaServicio;
}
// Proceso utilizando las dependencias anteriores.
}
Solo resuelve lo que realmente necesitas. Evite las dependencias de componentes que a su vez tienen muchas dependencias, como está pensado principalmente para controladores puede dificultar el trabajo de las pruebas unitarias. IComunServicios
Inyección de propiedades
Aunque la inyección de constructores es el método preferido para pasar dependencias a un componente que se está construyendo, también puede usar el método para que las propiedades se inserten automáticamente. PropertiesAutowired
Se recomienda evitar la inyección de propiedades si es posible y usarla solo para casos especiales (como clases abstractas) o servicios muy simples (como o ). Las propiedades autoinsertadas deben ser públicas, aunque en la mayoría de los casos una dependencia de componente no debe serlo. LoggerLocalizador
Dependencia de "Trabajo<T>"
A veces, es necesario resolver una dependencia cuando se accede a ella por primera vez, en lugar de cuando se llama al constructor del componente (por ejemplo, cuando se llama en una etapa muy temprana, antes de que el contenedor de inyección de dependencias pueda resolver los servicios). Una solución para esto es la clase Trabajo. La dependencia se resuelve desde el momento en que se accede a su propiedad.
public class MiComponente
{
private readonly Trabajo<IIdiomaServicio> _idiomaServicio;
public MiComponente(Trabajo<IIdiomaServicio> idiomaServicio)
{
// IIdiomaServicio aún no resuelto.
_idiomaServicio = Guarda.NoNulo(idiomaServicio);
}
public void Proceso()
{
// IIdiomaServicio resuelto a través de la propiedad "Valor".
var idiomas = _idiomaServicio.Valor.ObtenerTodosIdiomas();
}
}