Skip to content
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

Autofac and Pipeline Behavior #128

Closed
martosource opened this issue Jan 10, 2017 · 20 comments
Closed

Autofac and Pipeline Behavior #128

martosource opened this issue Jan 10, 2017 · 20 comments

Comments

@martosource
Copy link

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.
@SlowFourierTransform
Copy link

SlowFourierTransform commented Jan 18, 2017

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());

@martosource
Copy link
Author

@JamesFenton all fixed, thanks.

@SlowFourierTransform
Copy link

It seems Autofac does not support registering open generics when doing assembly scanning: autofac/Autofac#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 autofac/Autofac#215 (comment)

@adamhathcock
Copy link

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.

@SlowFourierTransform
Copy link

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?

@jbogard
Copy link
Owner

jbogard commented Jan 23, 2017

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

@jbogard
Copy link
Owner

jbogard commented Jan 27, 2017 via email

@jbogard jbogard closed this as completed Feb 2, 2017
@barryschulz
Copy link

@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 of IRequestPreProcessor<IPing> and Ping implemented IPing, this processor would not be found with the current RequestPreProcessorBehavior<TRequest,TResponse>.

Are you seeing this same limitation?

@worldspawn
Copy link

worldspawn commented Mar 15, 2017

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

@jbogard
Copy link
Owner

jbogard commented Mar 15, 2017

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

@worldspawn
Copy link

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.

@jdaigle
Copy link

jdaigle commented Mar 15, 2017

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.

@SlowFourierTransform
Copy link

SlowFourierTransform commented Mar 15, 2017

@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.

@seroche
Copy link

seroche commented Sep 14, 2017

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

@maldworth
Copy link

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

@billkiddo
Copy link

billkiddo commented Apr 23, 2018

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?

@billkiddo
Copy link

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.

@jesulink2514
Copy link

@maldworth I guess you should change your ContravariantRegistrationSource to this

builder.RegisterSource(new ScopedContravariantRegistrationSource ());

@cyrildurand
Copy link

I had a similar issue with MediatR and INotificationHandler I finally doing my own registration source that extend the original ContravariantRegistrationSource

/// <summary>
/// Returns contravariant registration source without duplicator target. 
/// 
/// <see cref="ContravariantRegistrationSource" /> returns all contravariant implementation of a type. 
/// For example when we resolve IEnumerable&lt;INotificationHandler&lt;SpecificCommand&gt;&gt; it will returns a collection with GenericHandler&lt;SpecificCommand&gt;, GenericHandler&lt;BaseCommand&gt;, SpecificCommandHandler 
/// this registration source will first look up for the native registrations and then group registration based on activator limit type. 
/// </summary>
/// <remarks>See https://stackoverflow.com/questions/46464944/autofac-contravariance-and-resolving-open-generic-types </remarks>
public class ExplicitContravariantRegistrationSource : IRegistrationSource
{
    private readonly IRegistrationSource _source = new ContravariantRegistrationSource();
    private readonly Type _type;

    public ExplicitContravariantRegistrationSource(Type type)
    {
        if (type == null)
        {
            throw new ArgumentNullException(nameof(type));
        }
        if (!type.IsGenericTypeDefinition)
        {
            throw new ArgumentException("Type should be a generic type definition", nameof(type));
        }
        this._type = type;
    }

    public IEnumerable<IComponentRegistration> RegistrationsFor(
        Service service,
        Func<Service, IEnumerable<ServiceRegistration>> registrationAccessor)
    {
        if (service is IServiceWithType st
                && st.ServiceType.IsGenericType
                && this._type == st.ServiceType.GetGenericTypeDefinition())
        {

            // get all non contravariant registration source 
            var originalRegistrations = registrationAccessor(service).ToArray();

            var components = _source
                                // retrieve all contravariant registration source
                                .RegistrationsFor(service, registrationAccessor)
                                // Group will ensure having only a single registration of a activator limit type
                                .GroupBy(this.GetTargetTypeDefinitionOrSelf)
                                // exclude groups if autofac already resolved the same activator limit type
                                .Where(o => !originalRegistrations.Select(oo => this.GetTargetTypeDefinitionOrSelf(oo.Registration)).Contains(o.Key))
                                // taking the last is the default behavior for autofac, it can be improved
                                .Select(o => o.Last());
            return components;
        }
        else
        {
            return Enumerable.Empty<IComponentRegistration>();
        }
    }

    private Type GetTargetTypeDefinitionOrSelf(IComponentRegistration componentRegistration)
    {
        return componentRegistration.Target.Activator.LimitType.IsGenericType ?
                    componentRegistration.Target.Activator.LimitType.GetGenericTypeDefinition()
                    : componentRegistration.Target.Activator.LimitType;
    }

    public bool IsAdapterForIndividualComponents => _source.IsAdapterForIndividualComponents;
}

and I use it like this :

        builder.RegisterSource(new ExplicitContravariantRegistrationSource(typeof(INotificationHandler<>)));

This issue is also discussed here : https://stackoverflow.com/questions/46464944/autofac-contravariance-and-resolving-open-generic-types/71010423#71010423

@lonix1
Copy link

lonix1 commented Feb 21, 2022

Autofac fixed this issue in 6.2.0. But my generic IRequestPostProcessor<TRequest, TResponse> still doesn't work.

Did anyone else solve this problem with the latest version?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests