Search code examples
asp.net-mvcautofaclog4net

Why doesn't Autofac Log4Net middleware allow me to directly resolve `ILog`?


I have ASP.NET MVC app which was working fine but once I upgraded Autofac and did the change for Log4Net middleware example it keeps failling if I try to Resolve ILog.

Here is the code:

    public class Log4NetMiddleware : IResolveMiddleware
    {
        public PipelinePhase Phase => PipelinePhase.ParameterSelection;

        public void Execute(ResolveRequestContext context, Action<ResolveRequestContext> next)
        {
            // Add our parameters.
            context.ChangeParameters(context.Parameters.Union(
                new[]
                {
              new ResolvedParameter(
                  (p, i) => p.ParameterType == typeof(ILog),
                  (p, i) => LogManager.GetLogger(p.Member.DeclaringType)
              ),
                }));

            // Continue the resolve.
            next(context);

            // Has an instance been activated?
            if (context.NewInstanceActivated)
            {
                Type instanceType = context.Instance.GetType();

                // Get all the injectable properties to set.
                // If you wanted to ensure the properties were only UNSET properties,
                // here's where you'd do it.
                IEnumerable<PropertyInfo> properties = instanceType
                    .GetProperties(BindingFlags.Public | BindingFlags.Instance)
                    .Where(p => p.PropertyType == typeof(ILog) && p.CanWrite && p.GetIndexParameters().Length == 0);

                // Set the properties located.
                foreach (PropertyInfo propToSet in properties)
                {
                    propToSet.SetValue(context.Instance, LogManager.GetLogger(instanceType), null);
                }
            }
        }
    }


    public class MiddlewareModule : Autofac.Module
    {
        private readonly IResolveMiddleware middleware;

        public MiddlewareModule(IResolveMiddleware middleware)
        {
            this.middleware = middleware;
        }

        protected override void AttachToComponentRegistration(IComponentRegistryBuilder componentRegistryBuilder, IComponentRegistration registration)
        {
            // Attach to the registration's pipeline build.
            registration.PipelineBuilding += (sender, pipeline) =>
            {
                // Add our middleware to the pipeline.
                pipeline.Use(middleware);
            };
        }
    }

And here is Global.asax.cs:

        protected void Application_Start()
        {
            AreaRegistration.RegisterAllAreas();
            FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
            RouteConfig.RegisterRoutes(RouteTable.Routes);
            BundleConfig.RegisterBundles(BundleTable.Bundles);

            ContainerBuilder builder = new ContainerBuilder();
            var loggerMiddlewareModule = new MiddlewareModule(new Log4NetMiddleware());
            builder.RegisterModule(loggerMiddlewareModule);

            IContainer container = builder.Build();
            ILog log = container.Resolve<ILog>(TypedParameter.From(typeof(MvcApplication)));
        }

It fails when it tries to resolve ILog:

The requested service 'log4net.Core.ILogger' has not been registered. To avoid this exception, either register a component to provide the service, check for service registration using IsRegistered(), or use the ResolveOptional() method to resolve an optional dependency.

See https://autofac.rtfd.io/help/service-not-registered for more info. Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.

Exception Details: Autofac.Core.Registration.ComponentNotRegisteredException: The requested service 'log4net.Core.ILogger' has not been registered. To avoid this exception, either register a component to provide the service, check for service registration using IsRegistered(), or use the ResolveOptional() method to resolve an optional dependency.

See https://autofac.rtfd.io/help/service-not-registered for more info.

#Autofac version 7.1.0

So how can I resolve ILog?


Solution

  • Instead of just telling you the answer, I'm going to walk you through how you could have figured out what's up on your own, and then confirm the answer. So stick with me. :)

    What we know:

    • You added the Log4NetMiddleware to your app.
    • You're trying to directly resolve an ILog.

    On the documentation page, the description for Log4NetMiddleware is:

    Here’s the sample middleware that injects ILog parameters based on the type of the component being activated. This sample middleware handles both constructor and property injection.

    This should be a big clue. It's middleware described as allowing you to inject ILog parameters.

    But let's look at the actual middleware code. It's always good to know what the code does that you put in your application.

    The first thing we see is a block that calls context.ChangeParameters. That block is adding a parameter to the set of parameters going into a resolve operation - it's adding an ILog parameter that is based on the calling type. This would be the same as doing something like this:

    container.Resolve<SomethingThatLogs>(
      TypedParameter.From(
        LogManager.GetLogger(typeof(SomethingThatLogs));
    

    If SomethingThatLogs (in this example) has a constructor parameter of ILog, it'll resolve. That would mean SomethingThatLogs would look like...

    public class SomethingThatLogs
    {
      public SomethingThatLogs(ILog logger)
      {
      }
    }
    

    So, that covers constructors. The next block has some similar work going on, but we see that there's reflection looking at properties. Ah! It's going to try to inject property values of ILog. Cool, so if SomethingThatLogs looks like this instead...

    public class SomethingThatLogs
    {
      public ILog Logger { get; set; }
    }
    

    ...then the module will find that property and inject that for you.

    What else is in that middleware module?

    Trick question: NOTHING.

    There's nothing else in there. It can inject a constructor parameter or a property value.

    Let's back up again and look at what you're trying to do: you're trying to resolve an ILog, you're not trying to resolve something with a constructor or property of ILog.

    So when the exception tells you that ILog is not registered, that's absolutely right.

    You need to register ILog if you want to resolve it directly.

    However, there's not much value in that. If you're getting a logger directly like that, just use the LogManager.GetLogger() call. Why go through DI? It's not adding value. Log4Net does all sorts of caching and management of the logging instances itself, Autofac isn't handling that.