question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Interface interception fails when used with open generic assembly scanning

See original GitHub issue

This comes from a StackOverflow question

Using assembly scanning in conjunction with interface interception yields an exception:

The component ... cannot use interface interception as it provides services that are not publicly visible interfaces. Check your registration of the component to ensure you're not enabling interception and registering it as an internal/private interface type.

Code to reproduce the issue as a console app:

namespace InterceptorRepro
{
    public interface ICommand<TResult>
    {
    }

    public class StringCommand : ICommand<String>
    {
    }

    public interface ICommandHandler<TCommand, TResult>
        where TCommand : ICommand<TResult>
    {
        TResult Execute(ICommand<TResult> command);
    }

    public class StringCommandHandler : ICommandHandler<StringCommand, String>
    {
        public string Execute(ICommand<string> command)
        {
            return "generic-method";
        }
    }

    public class LoggingInterceptor : IInterceptor
    {
        TextWriter _output;

        public LoggingInterceptor(TextWriter output)
        {
            _output = output;
        }

        public void Intercept(IInvocation invocation)
        {
            _output.Write("Calling method {0} from LoggingInterceptor", invocation.Method.Name);
            invocation.Proceed();
            _output.WriteLine("LoggingInterceptor complete.");
        }
    }

    public class Program
    {
        public static void Main()
        {
            var builder = new ContainerBuilder();
            builder.Register(c => Console.Out).As<TextWriter>();
            builder.RegisterType<LoggingInterceptor>();
            builder
                .RegisterAssemblyTypes(typeof(Program).Assembly)
                .AsClosedTypesOf(typeof(ICommandHandler<,>))
                .EnableInterfaceInterceptors()
                .InterceptedBy(typeof(LoggingInterceptor));

            var container = builder.Build();

            try
            {
                using (var scope = container.BeginLifetimeScope())
                {
                    // Exception thrown on this resolve:
                    var handler = scope.Resolve<ICommandHandler<StringCommand, string>>();
                    Console.WriteLine("Handler type is: {0}", handler.GetType());
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex);
            }
        }
    }
}

If you remove the EnableInterfaceInterceptors() and InterceptedBy() calls from the registration then the resolution proceeds as expected.

Issue Analytics

  • State:open
  • Created 5 years ago
  • Comments:14 (7 by maintainers)

github_iconTop GitHub Comments

1reaction
rbevcommented, Feb 7, 2019

I had a similar issue in and as @blairconrad alluded to the problem is in AsClosedTypesOf I’d put up a PR to fix this issue, but it really just needs a new extension added into Autofac and the documentation updated appropriatelv

My solution was to just implement an interface-only version of AsClosedTypesOf, if there is interest and an appropriate feature request raised against Autofac then i’ll happily put together a PR as i dont’ believe this extension method belongs in this plugin.

Here’s the implementation…

        /// <summary>
        /// Specifies that a type from a scanned assembly is registered if it implements an interface
        /// that closes the provided open generic interface type.
        /// </summary>
        /// <typeparam name="TLimit">Registration limit type.</typeparam>
        /// <typeparam name="TScanningActivatorData">Activator data type.</typeparam>
        /// <typeparam name="TRegistrationStyle">Registration style.</typeparam>
        /// <param name="registration">Registration to set service mapping on.</param>
        /// <param name="openGenericServiceType">The open generic interface or base class type for which implementations will be found.</param>
        /// <returns>Registration builder allowing the registration to be configured.</returns>
        public static IRegistrationBuilder<TLimit, TScanningActivatorData, TRegistrationStyle> AsClosedInterfacesOf<TLimit, TScanningActivatorData, TRegistrationStyle>(this IRegistrationBuilder<TLimit, TScanningActivatorData, TRegistrationStyle> registration, Type openGenericServiceType) where TScanningActivatorData : ScanningActivatorData
        {
            if ((object)openGenericServiceType == null)
                throw new ArgumentNullException(nameof(openGenericServiceType));
            if (!openGenericServiceType.IsInterface)
                throw new ArgumentException("Generic type must be an interface", nameof(openGenericServiceType));

            return registration
                .Where(candidateType => candidateType.IsClosedTypeOf(openGenericServiceType))
                .As(candidateType =>
                    candidateType.GetInterfaces()
                        .Where(i => i.IsClosedTypeOf(openGenericServiceType))
                        .Select(t => (Service)new TypedService(t)));
        }
0reactions
tilligcommented, Mar 2, 2020

I don’t think it’ll be too bad if we have to add some stuff to those events. It’s doubtful anyone has implemented their own IComponentRegistration and I don’t think adding properties to events is breaking… though adding to the constructor is.

I know we have a lot of information coming through in the ResolveRequest now; perhaps that can be helpful?

Also, for a singleton… if I’m not mistaken, you only get OnActivated once. You wouldn’t want it to do something for one particular service especially if it exposes more than one.

builder.RegisterType<Component>()
       .As<IService1>()
       .As<IService2>()
       .SingleInstance();

Ideally, though, this wouldn’t break all the extensions. We just went through a whole major release breaking change thing; if the solution is that we need another breaking change, I don’t know that we’re ready to fire off an Autofac 6.0 and do all that again right now.

Read more comments on GitHub >

github_iconTop Results From Across the Web

c# - Autofac generic type registration with interceptor and ...
But when I try to resolve a command handler, Autofac throws an exception with the following message: OwnedByLifetimeScope cannot use interface ...
Read more >
Advanced Scenarios — Simple Injector 5 documentation
Batch registration of non-generic types based on an open-generic interface; Registering open generic types and working with partially-closed types ...
Read more >
Type Scanning Diagnostics
Confusion of type scanning has been a constant problem with StructureMap usage over the years -- especially if users are trying to dynamically...
Read more >
Adding decorated classes to the ASP.NET Core DI ...
In this post I show how to do service decoration with Scrutor, where services "wrap" another implementation of the service.
Read more >
Implementing the microservice application layer using ...
When using DI in .NET, you might want to be able to scan an assembly and automatically register its types by convention.
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found