Mediatr: Autofac and Pipeline Behavior

Created on 10 Jan 2017  Â·  18Comments  Â·  Source: jbogard/MediatR

I am trying to get new pipeline working with autofac but it is running the same pipeline many times with different types.

Adding the following class the MediatR example

public class BehaviorOne<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse> 
    {

        private readonly TextWriter _writer;

        public BehaviorOne(TextWriter writer)
        {
            _writer = writer;
        }

        public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next)
        {
            //System.Diagnostics.Trace.WriteLine($"BehaviorOne - Handle {typeof(TRequest).Name}");

            _writer.WriteLine("BehaviorOne - Handle " + typeof(TRequest).Name);

            var response = await next();

            return response;
        }
    }

Added this to the BuildMediator for the autofac example program.cs

builder.RegisterGeneric(typeof(BehaviorOne<,>)).As(typeof(IPipelineBehavior<,>));

Produced the following output:

Sample mediator implementation using send, publish and post-request handlers in sync and async version.
---------------
Sending Ping...
BehaviorOne - Handle Ping
BehaviorOne - Handle Object
BehaviorOne - Handle IRequest`1
Received: Ping Pong
Sending Ping async...
BehaviorOne - Handle PingAsync
BehaviorOne - Handle Object
BehaviorOne - Handle IRequest`1
Received async: Ping Pong
Publishing Pinged...
Got pinged also.
Got pinged.
Got notified.
Got notified also async.
Publishing Pinged async...
Got notified.
Got pinged also async.
Got pinged async.
Got notified also async.

For the structure map example you get:

Sample mediator implementation using send, publish and post-request handlers in sync and async version.
---------------
Sending Ping...
BehaviorOne - Handle Ping
Received: Ping Pong
Sending Ping async...
BehaviorOne - Handle PingAsync
Received async: Ping Pong
Publishing Pinged...
Got notified.
Got pinged.
Got pinged also.
Got notified also async.
Publishing Pinged async...
Got notified.
Got notified also async.
Got pinged async.
Got pinged also async.

Most helpful comment

Hey guys,

I had the same problem this morning and I solved by implementing my own ScopedContravariantRegistrationSource (as advised by jdaigle). It's a simplistic (probably incomplete) implementation but it works quite well with my easy use case:

Here is the code:

    public class ScopedContravariantRegistrationSource : IRegistrationSource
    {
        private readonly IRegistrationSource _source = new ContravariantRegistrationSource();
        private readonly List<Type> _types = new List<Type>();

        public ScopedContravariantRegistrationSource(params Type[] types)
        {
            if (types == null)
                throw new ArgumentNullException(nameof(types));
            if (!types.All(x => x.IsGenericTypeDefinition))
                throw new ArgumentException("Supplied types should be generic type definitions");
            _types.AddRange(types);
        }

        public IEnumerable<IComponentRegistration> RegistrationsFor(
            Service service,
            Func<Service, IEnumerable<IComponentRegistration>> registrationAccessor)
        {
            var components = _source.RegistrationsFor(service, registrationAccessor);
            foreach (var c in components)
            {
                var defs = c.Target.Services
                    .OfType<TypedService>()
                    .Select(x => x.ServiceType.GetGenericTypeDefinition());

                if (defs.Any(_types.Contains))
                    yield return c;
            }
        }

        public bool IsAdapterForIndividualComponents => _source.IsAdapterForIndividualComponents;
    }

Then simply call in Startup:
then builder.RegisterSource(typeof(MyBehavior<,>));

Regards,
Seb

All 18 comments

I am having the same issue. The pipeline's Handle method gets called 3 times after sending Ping as @martosource describes.

If I create an OuterBehavior and an InnerBevavior and register them as follows:

builder.RegisterGeneric(typeof(OuterBehavior<,>)).As(typeof(IPipelineBehavior<,>));
builder.RegisterGeneric(typeof(InnerBehavior<,>)).As(typeof(IPipelineBehavior<,>));

I get the following output:

Sample mediator implementation using send, publish and post-request handlers in sync and async version.
---------------
Sending Ping...
OuterBehavior - Handle Ping
InnerBehavior - Handle Ping
InnerBehavior - Handle Object
OuterBehavior - Handle Object
InnerBehavior - Handle IRequest`1
OuterBehavior - Handle IRequest`1
Received: Ping Pong
Sending Ping async...
OuterBehavior - Handle PingAsync
InnerBehavior - Handle PingAsync
InnerBehavior - Handle Object
OuterBehavior - Handle Object
InnerBehavior - Handle IRequest`1
OuterBehavior - Handle IRequest`1
Received async: Ping Pong
Publishing Pinged...
Got pinged.
Got pinged also.
Got notified.
Got notified also async.
Publishing Pinged async...
Got notified.
Got pinged async.
Got pinged also async.
Got notified also async.

Removing the following line seems to fix the problem in both cases:

builder.RegisterSource(new ContravariantRegistrationSource());

@JamesFenton all fixed, thanks.

It seems Autofac does not support registering open generics when doing assembly scanning: https://github.com/autofac/Autofac/issues/215

What this means is that the built in pipeline behaviors RequestPreProcessorBehavior and RequestPostProcessorBehavior do not get registered from the following line from the Autofac sample:

builder.RegisterAssemblyTypes(typeof (IMediator).GetTypeInfo().Assembly).AsImplementedInterfaces();

This also means that any open generic IRequestPreProcessor or IRequestPostProcessor implementations will not get registered, for example:

public class GenericPreProcessor<TRequest> : IRequestPreProcessor<TRequest>
    {
        public Task Process(TRequest request)
        {
            return Task.Delay(0);
        }
    }

The built-in behaviours can be registered using

builder.RegisterGeneric(typeof(RequestPreProcessorBehavior<,>)).AsImplementedInterfaces();
builder.RegisterGeneric(typeof(RequestPostProcessorBehavior<,>)).AsImplementedInterfaces();

But registering of all open generic IRequestPreProcessor or IRequestPostProcessor implementations using assembly scanning requires some more specialised code, possibly similar to https://github.com/autofac/Autofac/issues/215#issuecomment-248538820

I switched to using Autofac instead of using the ASP.NET Core default container because Autofac supports better generic constraints.

I wanted to use interfaces as markers for certain pipeline behaviors like this and Autofac does it:

public class DistributedCachingHandler<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
        where TRequest : class, IUseDistributedCache
        where TResponse : class

Anyway, I believe I would see this issue but I don't because I'm still using the IServiceCollection way of registering types instead of the Autofac builder. I believe it's confirmed above from @JamesFenton

Maybe using IServiceCollection registration and assembly scanning for Autofac is an option.

What I would like to do is write some tests for each of the different containers. Ideally I'd like to keep the registration code in each of the samples so that people can easily find it. I'd also like to avoid "infecting" the current test project with all the different containers, so perhaps another test project that references all the same projects? What do you think @jbogard?

Well we do have the example projects, I wouldn't mind extending those perhaps to highlight what capabilities each container has through tests.

Yeah that sounds good to me!

On Thu, Jan 19, 2017 at 1:29 PM James notifications@github.com wrote:

What I would like to do is write some tests for each of the different
containers. Ideally I'd like to keep the registration code in each of the
samples so that people can easily find it. I'd also like to avoid
"infecting" the current test project with all the different containers, so
perhaps another test project that references all the same projects? What do
you think @jbogard https://github.com/jbogard?

—
You are receiving this because you were mentioned.

Reply to this email directly, view it on GitHub
https://github.com/jbogard/MediatR/issues/128#issuecomment-273873792,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AAGYMrHl61zG8LHvS_VpI-pehPJlogEMks5rT7mFgaJpZM4LfBd5
.

@JamesFenton If you do not register ContravariantRegistrationSource, it seems that you will lose out on the polymorphic dispatch. For example, if you had an implementation ofIRequestPreProcessor<IPing> and Ping implemented IPing, this processor would not be found with the current RequestPreProcessorBehavior<TRequest,TResponse>.

Are you seeing this same limitation?

So was there a solution without caveats? I'm feeling pressure to use the same di container as Jimmy :)

You can look at the way StructureMap does this and borrow its implementation?

I should have checked the autofac issue, just reposting this here for posterity, a suggested workaround for autofac is:

builder.RegisterAssemblyTypes(assemblies)
    .Where(t => t.GetTypeInfo()
        .ImplementedInterfaces.Any(
            i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof (IRepository<>)))
    .AsImplementedInterfaces()
    .InstancePerLifetimeScope();

Swapping IRepository for IPipelineBehavior i guess. Havent tried it yet.

Part of the problem with the ContravariantRegistrationSource is that it applies to any service resolved. Instead, we want it limited in scope.

For a project I ended up implementing my own ScopedContravariantRegistrationSource (largely copy/pasted the original source but added code to restrict which services it applied to via constructor parameter). Then register this source for only the services you want to have contravariant resolution.

It's certainly not as flexible as StructureMap's configuration, but it works decently well.

@barryschulz you are correct, if you remove the ContravariantRegistrationSource, you cannot use contravariance, e.g. IRequestPreProcessor<IPing>, but if you keep it, you cannot use generic pipeline behaviours. We haven't needed to use contravariance, but we have used generic pipeline behaviours to handle cross-cutting concerns for every request. I suppose that achieves the same thing as contravariance, provided your container supports generic constraints like @adamhathcock suggested.

The setup of Autofac does seem to require a lot more ceremony than StructureMap, unfortunately.

Hey guys,

I had the same problem this morning and I solved by implementing my own ScopedContravariantRegistrationSource (as advised by jdaigle). It's a simplistic (probably incomplete) implementation but it works quite well with my easy use case:

Here is the code:

    public class ScopedContravariantRegistrationSource : IRegistrationSource
    {
        private readonly IRegistrationSource _source = new ContravariantRegistrationSource();
        private readonly List<Type> _types = new List<Type>();

        public ScopedContravariantRegistrationSource(params Type[] types)
        {
            if (types == null)
                throw new ArgumentNullException(nameof(types));
            if (!types.All(x => x.IsGenericTypeDefinition))
                throw new ArgumentException("Supplied types should be generic type definitions");
            _types.AddRange(types);
        }

        public IEnumerable<IComponentRegistration> RegistrationsFor(
            Service service,
            Func<Service, IEnumerable<IComponentRegistration>> registrationAccessor)
        {
            var components = _source.RegistrationsFor(service, registrationAccessor);
            foreach (var c in components)
            {
                var defs = c.Target.Services
                    .OfType<TypedService>()
                    .Select(x => x.ServiceType.GetGenericTypeDefinition());

                if (defs.Any(_types.Contains))
                    yield return c;
            }
        }

        public bool IsAdapterForIndividualComponents => _source.IsAdapterForIndividualComponents;
    }

Then simply call in Startup:
then builder.RegisterSource(typeof(MyBehavior<,>));

Regards,
Seb

@seroche Can you give an example of usage as well?

I have the same behavior on behaviors as @martosource or @JamesFenton described on dotnet core default DI. Any hints on how to fix it without my own error prone ScopedContravariantRegistrationSource?

I'm using the recommended ASP.NET Core DI registration, see below
services.AddScoped<ServiceFactory>(p => p.GetService); services.AddScoped(typeof(IPipelineBehavior<,>), typeof(RequestPreProcessorBehavior<,>)); services.Scan(scan => scan .FromAssembliesOf(typeof(IMediator), typeof(FeasibilityApplicationService)) .AddClasses() .AsImplementedInterfaces());
Removing the Scrutor part, seems to do the trick for now.

@maldworth I guess you should change your ContravariantRegistrationSource to this

builder.RegisterSource(new ScopedContravariantRegistrationSource ());

Was this page helpful?
0 / 5 - 0 ratings

Related issues

orantwolinkme picture orantwolinkme  Â·  4Comments

etechnologiesng picture etechnologiesng  Â·  3Comments

nevaenuf picture nevaenuf  Â·  4Comments

qazq picture qazq  Â·  3Comments

ArturKarbone picture ArturKarbone  Â·  3Comments