New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Collection.Register set Lifestyle #688
Comments
Your question is unclear to me:
It would be good if you describe an Minimal, Complete, and Verifiable example that contains:
|
Hi Steve, thank for fast answer!
Yes, this decorator abstraction.
I explicitly get implementations in the command or query constructor. That is, I always need only a specific implementation, I do not work with the decorator list. The main thing for me is to understand how to specify a lifestyle when registering a collection. MCVE: (This code reproduces my problem) This is an analogue of MediatR interfaces. public interface IDecoratorHandler<TIn, TOut> : IHandler<TIn, TOut>
{
IHandler<TIn, TOut> NextHandler { get; }
void InjectDecorator(IHandler<TIn, TOut> handler);
}
public interface IHandler<in TIn, TOut>
{
Task<TOut> Handle(TIn input);
} The implementation of decorators. I decided to manually manage the injection to build the pipeline manually. public abstract class BaseDecorator<TIn, TOut> : IDecoratorHandler<TIn, TOut>
{
protected bool _isDecoratorInjected;
protected IHandler<TIn, TOut> _decorated;
public IHandler<TIn, TOut> NextHandler { get; private set; }
public BaseDecorator() { }
public abstract Task<TOut> Handle(TIn input);
public void InjectDecorator(IHandler<TIn, TOut> handler)
{
// this throw exception
if (_isDecoratorInjected)
throw new InvalidOperationException("Decorator already has injected");
_isDecoratorInjected = true;
_decorated = handler;
}
}
public class IntegrationSenderDecorator<TIn, TOut> : BaseDecorator<TIn, TOut>
{
public IntegrationSenderDecorator() { }
public override async Task<TOut> Handle(TIn input)
{
var res = await _decorated.Handle(input);
// ...
return res;
}
}
public class SaveChangesDecorator<TIn, TOut> : BaseDecorator<TIn, TOut>
{
public SaveChangesDecorator() { }
public override async Task<TOut> Handle(TIn input)
{
var res = await _decorated.Handle(input);
// ...
return res;
}
} I create manually pipeline from decorators. The handler with logic still has to be the last, I pass it. public class OrderCreatedCmdHandler : IHandler<string, int>
{
public OrderCreatedCmdHandler(
IntegrationSenderDecorator<string, int> integrationSenderDecorator,
SaveChangesDecorator<string, int> saveChangesDecorator)
{
//_pipeline = DecoratorPipelineBuilder<string, int>.Create()
// .Add(integrationSenderDecorator)
// .Add(saveChangesDecorator)
// .Build(logicHandler); // skip
// analog Build code:
integrationSenderDecorator.InjectDecorator(saveChangesDecorator);
}
public async Task<int> Handle(string input)
{
return 123;
}
} There are two different integration event handlers that need the same event command. public class IntegrationEventHandler1
{
public IntegrationEventHandler1(IHandler<string, int> cmdHandler) { }
}
public class IntegrationEventHandler2
{
public IntegrationEventHandler2(IHandler<string, int> cmdHandler) { }
//public IntegrationEventHandler2(/*IHandler<string, int> cmdHandler*/) { } // Everything is working
} Reproduction problems: static void Main(string[] args)
{
var container = new Container();
container.Options.DefaultScopedLifestyle = new AsyncScopedLifestyle();
container.Options.DefaultLifestyle = Lifestyle.Scoped;
//container.Options.DefaultLifestyle = Lifestyle.Transient; // Everything is working
container.Register<IHandler<string, int>, OrderCreatedCmdHandler>(Lifestyle.Transient);
container.Register<IntegrationEventHandler1>(Lifestyle.Transient);
container.Register<IntegrationEventHandler2>(Lifestyle.Transient);
var typesToRegister = container.GetTypesToRegister(typeof(IDecoratorHandler<,>),
new Assembly[] { typeof(Program).Assembly },
new TypesToRegisterOptions {
IncludeGenericTypeDefinitions = true,
IncludeComposites = false
});
container.Collection.Register(typeof(IDecoratorHandler<,>), typesToRegister);
container.Verify();
} Thank you in advance for your reply! |
Hi VladSnap,
The short rule is:
There are several overloads available of the
The best current workaround I can think of right now is to override the class TransientDecoratorHandlerBehavior : ILifestyleSelectionBehavior
{
private readonly Container container;
public TransientDecoratorHandlerBehavior(Container container)
=> this.container = container;
public Lifestyle SelectLifestyle(Type implementationType) =>
implementationType.IsClosedTypeOf(typeof(IDecoratorHandler<,>))
? Lifestyle.Transient
: this.container.Options.DefaultLifestyle;
} You can hook this up as follows: var container = new Container();
container.Options.DefaultScopedLifestyle = new AsyncScopedLifestyle();
container.Options.DefaultLifestyle = Lifestyle.Scoped;
container.Options.LifestyleSelectionBehavior = new TransientDecoratorHandlerBehavior(container); I must admit, though, that I find your current design quite confusing:
Perhaps I don't understand your application's constraints that lead to this design, but here are a few suggestions:
|
Hi, Steve! But I would like to consult you about the design, you would be very helpful if it is possible.
It's great that my question helped improve the simple injector.
I agree, I want to fix it.
I don't copy it, in fact I take most of it from here https://github.com/hightechgroup/force/tree/demo-app
Yes, I did it temporarily. There are no restrictions that lead to such a design.
|
I'm sorry for not getting back to you on your questions. I forgot about them. Will try to answer Monday. |
It's okay =) |
There are several ways to do this. Personally, I like separating queries from commands using a different abstraction. MediatR bundles them using a single But even if you keep the two models (queries and commands) combined in a single container.RegsiterDecorator(
typeof(IHandler<,>),
typeof(CachingHandlerDecorator<,>),
c => { some condition }); This condition can be anything:
Another option is to declare generic type constraints on your decorators. This allows them to be applied conditionally. e.g.: public CachingHandlerDecorator<TIn, TOut> : IHandler<TIn, TOut>
where TIn : ICachableQuery // TIn must be a cachable query
{
public CachingHandlerDecorator(IHandler<TIn, TOut> decoratee) { ... }
} In this case, you can simply register this decorator as follows: container.RegsiterDecorator(typeof(IHandler<,>), typeof(CachingHandlerDecorator<,>)); Simple Injector will automatically detect the type constraints and apply the decorator conditionally based on it generic type constraints.
I would, again, urge you to read the two linked articles. They describe how to define and use decorators.
You could register decorators using Auto-Registration (using reflection), but that typically doesn't work well for decorators, as a specific order is required, while with Auto-Registration, you lose the ordering guarantee. Thing to note is, though, that decorators should typically be generic in nature, and not defined for a specific use case. For instance, don't do this: // Bad idea
container.RegisterDecorator<IHandler<CreateOrder, int>, ValidateCreateOrderDecorator>();
container.RegisterDecorator<IHandler<CancelOrder, int>, ValidateCancelOrderDecorator>();
container.RegisterDecorator<IHandler<ShipOrder, int>, ValidateShipOrderDecorator>(); This is bad, because it causes a lot of maintainance on your Composition Root, and now you are starting to implement actual business logic into a specific decorator. This makes the business logic more complex than strictly required. Instead, I propose a model where the decorator is generic, and delegates to underlying (and specific) objects. Using this validation example, for instance, think about doing something as follows: // NOTE: Generic decorator
container.RegisterDecorator(typeof(IHandler<,>), typeof(ValidateHandlerDecorator<,>));
// Register another decorator. Decorators are always wrapped in order of registration. This means
// that this decorator will wrap the ValidateHandlerDecorator. The order in which decorators are
// executed is often of outmost importance.
container.RegisterDecorator(typeof(IHandler<,>), typeof(SecurityHandlerDecorator<,>)); The generic decorator is implemented as follows: public class ValidateHandlerDecorator<TIn, TOut> : IHandler<TIn, TOut>
{
private readonly IEnumerable<IValidator<TIn>> validators,;
private readonly IHandler<TIn, TOut> decoratee;
public ValidateHandlerDecorator(
IEnumerable<IValidator<TIn>> validators,
IHandler<TIn, TOut> decoratee
{
this.validators = validators;
this.decoratee = decoratee;
}
public async Task<TOut> Handle(TIn input)
{
var errors = (
from validator this.validators
from result in validator.Validate(input)
select result)
.ToArray();
if (errors.Any()) throw new ValidationException(errors);
return await this.decoratee.Handle(input);
}
} In this case, the public interface IValidator<T>
{
IEnumerable<string>Validate(T input);
} This allows the actual validation to be moved to these public CreateOrderValidator : IValidator<CreateOrder>
{
public CreateOrderValidator(IMyUnitOfWork uow) { ... }
public IEnumerable<string> Validate(CreateOrder input)
{
if (input.Id == Guid.Empty) yield return "Id is required.";
}
} Because your validator implementations are now declared using the generic container.Collection.Register(typeof(IValidator<>), typeof(CreateOrderValidator).Assembly); There are a lot of variations you can apply here. For instance:
Central point here, though, is that you should:
Not sure I understand, but I think that the referenced articles should make things clear. |
Hi, Steve! Thank you very much for your answer! I will study your articles, thanks for the links. |
I want to register a collection of open-generic types with a specific
Transient
. I do not understand how I can achieve this? By default, I set upLifestyle.Scoped
.I have decorators (only an injection is not through the designer), the implementations of decorators are open generics. I can register them only through
Collection.Register
; if I register throughRegister
, I get an error:My register code:
The text was updated successfully, but these errors were encountered: