tiempo que para pantalla net eliminar ejecucion crear control baja agregar c# dependency-injection inversion-of-control autofac object-lifetime

c# - que - ¿Cómo lidiar con los parámetros de tiempo de ejecución cuando se usa el alcance de por vida?



para que se baja la pantalla en el iphone 6 (5)

Autofac ahora es compatible con esta versión de fábrica con una extensión para los ámbitos de duración. El método BeginLifetimeScope() tiene una sobrecarga que realiza una Action<ContainerBuilder> que permite agregar nuevos registros específicos para solo el alcance de la vida útil. Entonces, para el ejemplo dado, se vería algo así como:

var builder = new ContainerBuilder(); builder.RegisterType<MyService>().As<IMyService>().InstancePerLifetimeScope(); var container = builder.Build(); using(var scope = container.BeginLifetimeScope( builder => { builder.RegisterInstance(new Data(....)); })) { // References to ''IMyService'' will always be resolved to the same instance within this lifetime scop // References to ''Data'' will be resolved to the instance registered just for this lifetime scope. var svc = scope.Resolve<IMyService>(); }

Advertencia, larga publicación por delante.

He estado pensando mucho sobre esto últimamente y estoy luchando para encontrar una solución satisfactoria aquí. Usaré C # y autofac para los ejemplos.

El problema

IoC es ideal para construir grandes árboles de servicios apátridas. Resuelvo los servicios y paso los datos solo a las llamadas a métodos. Estupendo.

A veces, quiero pasar un parámetro de datos al constructor de un servicio. Para eso están las fábricas. En lugar de resolver el servicio, resuelvo su fábrica y llamo a crear el método con el parámetro para obtener mi servicio. Poco más trabajo pero está bien.

De vez en cuando, quiero que mis servicios se resuelvan en la misma instancia dentro de un cierto alcance. Autofac proporciona InstancePerLifeTimeScope() que es muy útil. Me permite resolver siempre la misma instancia dentro de un subárbol de ejecución. Bueno.

Y hay momentos en los que quiero combinar ambos enfoques. Quiero el parámetro de datos en el constructor y tengo las instancias con un alcance. No he encontrado una manera satisfactoria de lograr esto.

Soluciones

1. Método de inicialización

En lugar de pasar datos al constructor, simplemente páselo al método Initialize .

Interfaz:

interface IMyService { void Initialize(Data data); void DoStuff(); }

Clase:

class MyService : IMyService { private Data mData; public void Initialize(Data data) { mData = data; } public void DoStuff() { //... } }

Registro:

builder.RegisterType<MyService>().As<IMyService>().InstancePerLifetimeScope();

Uso:

var myService = context.Resolve<IMyService>(); myService.Init(data); // somewhere else var myService = context.Resolve<IMyService>();

Después de resolver el servicio por primera vez y llamar a Initialize, puedo resolverlo felizmente en el mismo contexto y obtener la misma instancia inicializada. No me gusta el hecho de que antes de llamar a Initialize tengo un objeto inutilizable. Existe el peligro de que la instancia se resuelva y use en otro lugar antes de llamar a Initialize ().

2. Patrón de soporte

Este es un patrón que contiene una referencia al objeto de datos y, en lugar de inyectar el propio objeto de datos, inyecto el objeto titular.

Interfaz:

interface IMyService { void DoStuff(); }

Clase:

class MyService : IMyService { private Data mData; public MyService(IDataHolder dataHolder) { mData = dataHolder.Data; } public void DoStuff() { //... } }

Registro:

builder.RegisterType<MyService>().As<IMyService>(); builder.RegisterType<DataHolder>().As<IDataHolder>().InstancePerLifetimeScope();

Uso:

var holder = context.Resolve<IDataHolder>(); holder.Data = data; // somewhere else var myService = context.Resolve<IMyService>();

Esto es un poco mejor ya que moví la responsabilidad de llevar una instancia a una clase diferente. Ahora puedo usar el titular en otros servicios también. Otra ventaja es que puedo intercambiar datos en el titular si es necesario. No me gusta el hecho de que ofusca el código y agrega otra interfaz que debo simular durante la prueba.

3. Deje que el contenedor contenga la instancia

Interfaz:

interface IMyService { void DoStuff(); }

Clase:

class MyService : IMyService { private Data mData; public MyService(Data data) { mData = dataHolder.Data; } public void DoStuff() { //... } }

Registro:

builder.RegisterType<MyService>().As<IMyService>().InstancePerLifetimeScope();

Uso:

var myServiceFactory = context.Resolve<Func<Data, IMyService>>(); myServiceFactory(data); // somewhere else var myService = context.Resolve<IMyService>();

Está bien. No almaceno el resultado de una llamada de fábrica en ningún lugar, porque autofac lo guarda para mí. Esto es bastante sorprendente para cualquiera que lea el código. No estoy seguro si autofac fue diseñado para ser usado así. Lo bueno de esto es que no necesito ni un método de inicialización adicional ni una clase adicional para la instancia de celebración.

Pregunta

¿Cuál es su opinión sobre esto? ¿Cómo maneja una situación con los parámetros de datos en tiempo de ejecución y el alcance de por vida? ¿Me estoy perdiendo un mejor enfoque?


La mayoría de las veces, los datos de tiempo de ejecución son la información no estática que se necesita para pasar en cualquier proceso, como x en una función matemática, por lo que la forma más sencilla de lidiar con ella es usar un parámetro en la función:

class MyService : IMyService { public MyService(){} public void DoStuff(Data mData) { //... } } var myService = context.Resolve<IMyService>(); myService.DoStuff(data);

Pero suponer que su ejemplo es solo un ejemplo y lo está preguntando porque su clase necesita mantener los datos en tiempo de ejecución para ejecutar más procesos y no quiere pasar el mismo argumento en cada función:

1.- Si no pierde el alcance de los datos de tiempo de ejecución en cada Resolución, puede resolver con TypedParameter :

Ej:

//initilization var builder = new ContainerBuilder(); builder.RegisterType<MyService>().As<IMyService>().InstancePerLifetimeScope(); var container = builder.Build(); //any point of your app Data mData = new Data("runtimeData"); // must to be accesible in every place you Resolve using(var scope = container.BeginLifetimeScope()) { var service = scope.Resolve<IMyService>(new TypedParameter(typeof(Data), mData)); service.DoStuff(); } using(var scope = container.BeginLifetimeScope()) { var service2 = scope.Resolve<IMyService>(new TypedParameter(typeof(Data), mData)); service2.DoStuff(); }

2.- Si no tiene una referencia a los datos de tiempo de ejecución en cada lugar que está resolviendo, puede RegisterInstance cuando y donde crea datos de tiempo de ejecución. Autofac debería inyectar la instancia de mData gracias a Direct Depency Policy

//initilization var builder = new ContainerBuilder(); builder.RegisterType<MyService>().As<IMyService>().InstancePerLifetimeScope(); var container = builder.Build(); //where you create or modify runtime data. When runtime data changes you have to update the container again. var mData = new Data("runtimeData"); updatedBuilder= new ContainerBuilder(); updatedBuilder.RegisterInstance(mData).As<Data> updatedBuilder.Update(builder); //in any point of your app using(var scope = updatedBuilder.BeginLifetimeScope()) { var service = scope.Resolve<IMyService>(); service.DoStuff(); } //in any other point of your app using(var scope = updatedBuilder.BeginLifetimeScope()) { var service2 = scope.Resolve<IMyService>(); service2.DoStuff(); }


Mi opinión sobre esto es que has hecho lo mejor que puedes hacer. La única pega que tengo es que Autofac realmente no hace un gran trabajo al ayudarlo a administrar esos ámbitos de por vida, por lo que está atrapado llamando a BeginLifetimeScope alguna parte. Y se pueden anidar .

Ninject, por otro lado, hace algunas cosas realmente geniales que no requieren que tu cerebro se vuelva del revés. Su extensión de ámbito nombrado hace posible crear un ámbito nombrado ( gasp ) y vincular el tiempo de vida de los objetos dentro de ese ámbito. Si está utilizando fábricas (claramente lo es, a juzgar por la pregunta) también querrá utilizar la extensión de preservación de contexto, para que las cosas activadas fuera de las fábricas obtengan la administración de por vida del alcance especificado en el que se activó la fábrica. Los enlaces terminan pareciéndose a algo así:

var scopeName = "Your Name Here"; Bind<TopLevelObject>().ToSelf().DefinesNamedScope(ScopeName); Bind<ISomeScopedService>().To<SomeScopedService>().InNamedScope(ScopeName); // A minor bit of gymnastics here for factory-activated types to use // the context-preservation extension. Bind<FactoryActivatedType>().ToSelf().InNamedScope(ScopeName); Bind<IFactoryActivatedType>().ToMethod(x => x.ContextPreservingGet<FactoryActivatedType>());

La mejor parte de esto es que el alcance de esas vinculaciones está específicamente vinculado al alcance designado en lugar de estar vinculado al alcance de la cadena más cercano de la cadena. En mi humilde opinión, hace que las vidas de esos objetos sean mucho más predecibles.


Muchos frameworks de IoC soportan el registro de una función de fábrica (o expresión lambda), que toma como uno de sus argumentos una instancia del contenedor / alcance / contexto de resolución en sí.

Esto permite el uso de niveles adicionales de indirección, así como el uso de información que identifica de forma única el contexto o el alcance. Además, muchos proporcionan enlaces, como los controladores de eventos o la opción de derivar de una clase de ámbito de ciclo de vida, para interactuar con un ámbito que se inicia o finaliza.

Principio

Para AutoFac y su ejemplo específico, el siguiente principio funcionaría, usando niveles adicionales de direccionamiento indirecto en el registro.

// Inject `Data` instance resolved from current scope. builder.Register<IMyService>(ctx => new MyService(ctx.Resolve<Data>())); // Extra level of indirection, get a "factory" for a ''Data'' instance. builder.Register<Data>(ctx => ctx.Resolve<Func<Data>>()()).InstancePerLifetimeScope(); // The indirection resolves to a map of scopes to "factory" functions. builder.Register<Func<Data>>(ScopedDataExtensions.GetFactory);

Podemos usar cualquier propiedad única disponible en un contexto / alcance para construir esta asignación.

// Maps scopes to data "factories". public static class ScopedDataExtensions { private static readonly ConcurrentDictionary<object, Func<Data>> _factories = new ConcurrentDictionary<object, Fund<Data>>(); public static Func<Data> GetFactory(this IComponentContext ctx) { var factory = default(Func<Data>); return _factories.TryGetValue(ctx.ComponentRegistry, out factory) ? factory : () => null; } public static void SetFactory(this ILifetimeScope scope, Func<Data> factory) { _factories[scope.ComponentRegistry] = factory; } }

Podemos usarlo así para suministrar instancias de datos "locales" para ser inyectadas en nuestras instancias de servicio con ámbito.

var myData = new Data("nested"); nestedScope.SetFactory(() => myData); // ... var myService = nestedScope.Resolve<IMyService>();

Un ejemplo más completo y genérico para AutoFac sigue a continuación.

Clase de extensión genérica para este patrón

public static class AutofacScopeExtensions { // Map from context => factories per type public static readonly ConcurrentDictionary<object, ConcurrentDictionary<Type, object>> _factories = new ConcurrentDictionary<object, ConcurrentDictionary<Type, object>>(); private static class ScopedFactoryFor<T> { public static Func<T> DefaultFactory = () => default(T); public static Func<T> GetFactory(ConcurrentDictionary<Type, object> fromContext) { object factory; return (fromContext.TryGetValue(typeof(T), out factory)) ? (Func<T>)factory : DefaultFactory; } } public static IRegistrationBuilder<T, SimpleActivatorData, SingleRegistrationStyle> WithContextFactoryFor<T>(this ContainerBuilder builder, Func<T> defaultFactory = null) { if (defaultFactory != null) ScopedFactoryFor<T>.DefaultFactory = defaultFactory; builder.Register<Func<T>>(AutofacScopeExtensions.GetFactory<T>); return builder.Register<T>(ctx => ctx.Resolve<Func<T>>()()); } public static IContainer BuildContainer(this ContainerBuilder builder) { var container = builder.Build(); container.ChildLifetimeScopeBeginning += OnScopeStarting; return container; } public static ILifetimeScope SetScopeFactory<T>(this ILifetimeScope scope, Func<T> factory) { ScopeMapFor(scope)[typeof(T)] = factory; return scope; } public static ILifetimeScope SetScopeValue<T>(this ILifetimeScope scope, T instance) { return SetScopeFactory(scope, () => instance); } public static Func<T> GetFactory<T>(IComponentContext ctx) { return ScopedFactoryFor<T>.GetFactory(ScopeMapFor(ctx)); } private static ConcurrentDictionary<Type, object> ScopeMapFor(IComponentContext ctx) { return _factories.GetOrAdd(ctx.ComponentRegistry, x => new ConcurrentDictionary<Type, object>()); } private static void OnScopeStarting(object sender, LifetimeScopeBeginningEventArgs evt) { evt.LifetimeScope.ChildLifetimeScopeBeginning += OnScopeStarting; evt.LifetimeScope.CurrentScopeEnding += OnScopeEnding; // so we can do clean up. } private static void OnScopeEnding(object sender, LifetimeScopeEndingEventArgs evt) { var map = default(ConcurrentDictionary<Type, object>); if (_factories.TryRemove(evt.LifetimeScope.ComponentRegistry, out map)) map.Clear(); } }

Permitiendo la siguiente sintaxis para el registro:

builder.WithContextFactoryFor<Data>(() => new Data("Default")).InstancePerLifetimeScope(); builder.Register<IMyService>(ctx => new MyService(ctx.Resolve<Data>()));

Y resolver como:

// ... var myData = new Data("Some scope"); // ... context.SetScopeFactory(() => myData); // ... // Will inject ''myData'' instance. var myService = context.Resolve<IMyService>();

Alternativa mas simple

Si inicia explícitamente ámbitos anidados y, en el momento en que lo hace, sabe cómo se creará la instancia de Data del ámbito, puede omitir la clase de extensión y registrar el delegado "fábrica" ​​con el ámbito anidado al crearlo:

var nestedScope = container.BeginLifetimeScope( "L2", x => x.RegisterInstance<Func<Data>>(() => new Data("nested")));


Si te entiendo correctamente, quieres usar fábricas delegando la creación de objetos en el contenedor mientras pasas algunos parámetros a su constructor.

Esto se implementa en Castle Windsor con instalaciones de fábrica tipadas .

Ejemplo de clases que queremos resolver:

public interface IMyService { void Do(); } public class MyService : IMyService { private readonly Data _data; private readonly IDependency _dependency; public MyService(Data data, IDependency dependency) { _data = data; _dependency = dependency; } public void Do() { throw new System.NotImplementedException(); } } public class Data { } public interface IDependency { } public class Dependency : IDependency { }

Creamos una interfaz de fábrica:

public interface IMyServiceFactory { IMyService Create(Data data); void Release(IMyService service); }

No implementaremos esta interfaz porque Castle Windsor generará una implementación con Dynamic Proxy. Aquí hay un detalle importante: el nombre del parámetro (datos) en el método de fábrica y el del constructor debe coincidir.

Luego hacemos el registro y tratamos de resolver los valores.

[Test] public void ResolveByFactory() { WindsorContainer container = new WindsorContainer(); container.AddFacility<TypedFactoryFacility>(); container.Register(Component.For<IMyServiceFactory>().AsFactory()); container.Register(Component.For<IMyService>().ImplementedBy<MyService>().LifestyleScoped()); container.Register(Component.For<IDependency>().ImplementedBy<Dependency>().LifestyleScoped()); IMyServiceFactory factory = container.Resolve<IMyServiceFactory>(); IMyService myService1; IMyService myService2; using (container.BeginScope()) { myService1 = factory.Create(new Data()); myService2 = factory.Create(new Data()); myService1.Should().BeSameAs(myService2); } using (container.BeginScope()) { IMyService myService3 = factory.Create(new Data()); myService3.Should().NotBeSameAs(myService1); myService3.Should().NotBeSameAs(myService2); } }

Verá que el objeto creado en el mismo ámbito son las mismas referencias. Déjame saber si este es el comportamiento que quieres.