23

On an ASP.NET MVC 5 application I have the following StructureMap configuration:

cfg.For(typeof(IRequestHandler<,>)).DecorateAllWith(typeof(MediatorPipeline<,>));

Does anyone know how to do this configuration with ASP.NET Core IOC?

Steven
  • 159,023
  • 23
  • 313
  • 420
Miguel Moura
  • 32,822
  • 74
  • 219
  • 400

5 Answers5

15

The out of the box IoC container doesn't support decorate pattern or auto discovery, which is "by design" as far as I know.

The idea is to provide a basic IoC structure that works out of the box or where other IoC containers can be plugged in to extend the default functionality.

So if you need any advanced features (support for a specific constructor, auto-registering of all types which implement an interface or inject decorators and interceptors) you have to either write it yourself or use an IoC container which offers this functionality.

Tseng
  • 57,187
  • 13
  • 181
  • 194
11

Use Scrutor. Just install the nuget package and then do the following.

services.AddSingleton<IGreeter, Greeter>();
services.Decorate<IGreeter, GreeterLogger>();
services.Decorate<IGreeter, GreeterExceptionHandler>();

The order is important. In the above, GreeterLogger decorates Greeter. And GreeterExceptionHandler decorates GreeterLogger.

If you need more info, take a look at this and this.

And of course, you can use the popular Autofac as well.

If you want to know how to configure Autofac, take a look at Ardalis Clean Arch template

VivekDev
  • 13,304
  • 19
  • 90
  • 150
5

This workaround doesn't apply the decorator to all instances of a type but uses extension methods to abstract the decorator logic into another file.

Defining the decorator structure like:

public static class QueryHandlerRegistration
{
    public static IServiceCollection RegisterQueryHandler<TQueryHandler, TQuery, TResult>(
        this IServiceCollection services) 
        where TQuery : IQuery<TResult>
        where TQueryHandler : class, IQueryHandler<TQuery, TResult>
    {
        services.AddTransient<TQueryHandler>();
        services.AddTransient<IQueryHandler<TQuery, TResult>>(x =>
            new LoggingDecorator<TQuery, TResult>(x.GetService<ILogger<TQuery>>(), x.GetService<TQueryHandler>()));
        return services;
    }
}

And calling it like:

services.AddMvc();
// Add application services.
services.AddTransient<IEmailSender, AuthMessageSender>();
services.AddTransient<ISmsSender, AuthMessageSender>();

services.RegisterQueryHandler<FindThingByIdQueryHandler, FindThingByIdQuery, Thing>();

There's also the Scrutor package being worked on.

Lukas Körfer
  • 11,934
  • 7
  • 40
  • 54
Willie
  • 106
  • 2
  • 3
  • 2
    The Scrutor mention was very useful. Nice library there. It's impressive that they managed to create the entire mechanism using standard registration for descriptor classes. – julealgon Mar 20 '18 at 13:58
  • 2
    Note that Scrutor doesn't curently support calling Dispose on decorated objects when a DI scope ends. I filed an issue on GitHub for this, hope they will fix it eventually: https://github.com/khellang/Scrutor/issues/91 – sich May 30 '19 at 06:17
5

In my blogpost I described how a relatively simple extension method can solve this problem easily. Here is an example from that post which shows how decorator configuration may look like:

services.AddDecorator<IEmailMessageSender, EmailMessageSenderWithRetryDecorator>(decorateeServices =>
    {
        decorateeServices.AddScoped<IEmailMessageSender, SmtpEmailMessageSender>();
    });
sich
  • 455
  • 5
  • 10
1

one of another example

services.AddTransient<Greeter>();
services.AddTransient<IGreeter>(g=>
   ActivatorUtilities.CreateInstance<GreeterLogger>(g,g.GetRequiredServices<Greeter>())
);

or generic

private static void AddTransientDecorated<TInterface,TService,TDecorator>(this IServiceCollection services)
{
    services.AddTransient(typeof(TService));
    services.AddTransient(typeof(TInterface), p => ActivatorUtilities.CreateInstance<TDecorator>(p, p.GetRequiredService<TService>()));
}

additional information .NET Core DI, ways of passing parameters to constructor

xSx
  • 146
  • 4