30

In a Console application, I'm using Log4Net and in the Main method I'm getting the logger object. Now, I'd like to make this log object available in all my classes by letting all the classes inherit from a BaseClass which has a ILog property and is supposed to be set by Property Injection rather than Constructor Injection.

I'm using AutoFac IoC container, how to inject my log Object to the Log property of my every class?

What's the best/easiest way to achieve this?

Is there any way to automatically resolve types?

Below is my test application:

namespace ConsoleApplication1
{
    class Program
    {
        static ILog Log;
        static IContainer Container;

        static void Main(string[] args)
        {                
           InitializeLogger();

           InitializeAutoFac();

            // the below works but could it be done automatically (without specifying the name of each class)?
           Product.Log = Container.Resolve<ILog>();

           // tried below but didn't inject ILog object into the Product
           Container.Resolve<Product>();

           RunTest();

            Console.ReadLine();
        }

        private static void RunTest()
        {
            var product = new Product();
            product.Do();
        }

        private static void InitializeAutoFac()
        {
            var builder = new ContainerBuilder();

            builder.Register(c => Log).As<ILog>();

            builder.RegisterType<Product>().PropertiesAutowired();

            Container = builder.Build();            
        }

        private static void InitializeLogger()
        {
            log4net.Config.XmlConfigurator.Configure();

            Log = LogManager.GetLogger("LoggerName");
        }
    }

    public class Product
    {
        public static ILog Log { get; set; }

        public void Do()
        {
            // this throws exception because Log is not set   
            Log.Debug("some Debug");  
        }
    }
}
The Light
  • 26,341
  • 62
  • 176
  • 258
  • 1
    You can also create a static instance of logger in each of your classes. That way each logger will automatically have the name of the class, where it was defined. – alex Mar 24 '13 at 15:56
  • 2
    Remove `static` out of Ilog in `Product` – cuongle Mar 24 '13 at 16:36
  • I tried with being instance as well but didn't help. – The Light Mar 24 '13 at 16:41
  • You code is working try: `var product = Container.Resolve()` you can see instance of Ilog inside, I have just debugged – cuongle Mar 24 '13 at 16:47
  • If you use IoContainer: you can not call `new` to create instance, you have to resolve via Container, so you should change yout method `DoTest` – cuongle Mar 24 '13 at 16:51
  • I see, thanks! what if I need some classes to be created at runtime later rather than initially? For example, I might be having a ProductFactory class with CreateProduct() method, so my Factory class will be responsible to inject the dependencies then? I see, so I create a factory class and pass all the dependencies of its products then the factory injects them for me later at runtime. – The Light Mar 24 '13 at 16:55
  • Should we make our Container object accessible throughout the application then perhaps using a ServiceLocator? – The Light Mar 24 '13 at 17:03
  • 1
    Logging (almost) never needs to managed by a DI container. Just use log4net directly. – default.kramer Mar 25 '13 at 03:04
  • 1
    @default.kramer I see this statement all the time and I disagree. From my experience, logging is a perfect example of something that should be abstracted. Using an implementation like Log4Net directly couples you to that implementation. – Josh Noe Dec 19 '17 at 17:18

6 Answers6

27

Use Property Injection:

builder.Register(c => LogManager.GetLogger("LoggerName"))
       .As<ILog>();

builder.RegisterType<CustomClass>()
       .PropertiesAutowired();
Thorarin
  • 47,289
  • 11
  • 75
  • 111
cuongle
  • 74,024
  • 28
  • 151
  • 206
  • thanks but what/where is the Log class here? I just have the ILog interface I think. The ILog object is retrieved from the LogManager.GetLogger("LoggerName"); – The Light Mar 24 '13 at 15:47
  • good thanks, I can now see the log object from the container by container.Resolve(). but how could I ask AutoFac to resolve ILog automatically wherever it sees it? It doesn't resolve it automatically and throws exception currently when I access the Log property in my CustomClass. – The Light Mar 24 '13 at 16:08
  • 1
    @TheLight: Could you try to use `PropertiesAutowired`? – cuongle Mar 24 '13 at 16:10
  • I did. It registers it correctly but doesn't resolve it automatically. – The Light Mar 24 '13 at 16:11
  • I have edited again, could you try on `CustomClass` instead of `ILog` – cuongle Mar 24 '13 at 16:12
  • Registration is different from Resolving. For example, in MVC I'd write DependencyResolver.SetResolver(new AutofacDependencyResolver(container)); to resolve my classes. What should I write for a Console app so that the types are resolved? – The Light Mar 24 '13 at 16:18
  • @TheLight: afraid I does not understand you clearly, for console: you just resolve at the top layer by calling: `Container.Resolve` – cuongle Mar 24 '13 at 16:21
  • Yes, if I write CustomClass.Log = Container.Resolve(); it will work but is there any way to resolve all types of the assembly automatically rather than manually setting the Log property of every single class? In MVC for example there were resolved automatically by the code I wrote before. – The Light Mar 24 '13 at 16:22
  • You can write `container.Resolve();` it will work and automatically set ILog for you – cuongle Mar 24 '13 at 16:24
  • @TheLight: did you check it again, I actually make it work on my hand for this answer – cuongle Mar 24 '13 at 16:29
  • 4
    The scary thing with `PropertiesAutowired` however is that it does implicit property injection, which means that any unresolvable dependencies will be skipped. This makes it easy to miss configuration errors and can result in application that fails at runtime. – Steven Mar 24 '13 at 20:52
27

In my opinion the solution Ninject created is much nicer than the propertyinjection in Autofac. Therefore I created a a custom attribute which is a postsharp aspect which automatically injects my classes:

[AutofacResolve]
public IStorageManager StorageManager { get; set; }

My aspect:

[Serializable]
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public class AutofacResolveAttribute : LocationInterceptionAspect
{
    public override void OnGetValue(LocationInterceptionArgs args)
    {
        args.ProceedGetValue();

        if (!args.Location.LocationType.IsInterface) return;

        if ( args.Value != null )
        {
           args.Value = DependencyResolver.Current.GetService(args.Location.LocationType);
           args.ProceedSetValue();
        }
    }
}

I know the answer on the question is already given but I thought this was a really neat way of solving automatic property injection in Autofac. Maybe it'll be useful to somebody in the future.

Hans Leautaud
  • 1,742
  • 1
  • 19
  • 34
  • Do you have a solution without postsharp? – Morten Holmgaard Feb 18 '16 at 10:15
  • No, but I think you should be able to create a custom attribute yourself by inheriting from System.Attribute and build this functionality yourself. – Hans Leautaud Feb 18 '16 at 11:56
  • In mvvm WPF there is no dependencyResolver available How could I achieve the same with Postsharp /Autofac/ MVVM please ? – Xavave Feb 25 '18 at 12:18
  • The check if the location type is an interface should be implemented in `CompileTimeValidate` method of the aspect, so the check leads to a build-time error instead of a run-time error. See http://doc.postsharp.net/aspect-validation. – Antonín Procházka Mar 02 '18 at 09:52
  • 1
    Upvoted for creativity. Yes, you're writing less code but tbh this isn't a very loosely coupled solution. The solution is dependent on the DependencyResolver class which is an MVC thing. If the property belongs to a class in the Data layer, this wouldnt work. – Raihan Iqbal Jun 19 '18 at 23:11
15

Property injection works for Properties and not for Fields. In your class, Log is a field and not a property and hence it will never get resolved by the Autofac.

Naresh Mittal
  • 231
  • 2
  • 4
4

Use Property Injection (In addition to @cuongle answer).

Option 1:

builder.Register(c => LogManager.GetLogger("LoggerName")).As<ILog>();

builder.RegisterType<Product>()
        .WithProperty("Log", LogManager.GetLogger("LoggerName"));

Option 2:

Or you can add a SetLog method to the Product class:

public class Product
{
    public static ILog Log { get; set; }
    public SetLog(Log log)
    {
        Log = log;
    }
}

This way you won't have to call LogManager.GetLogger("LoggerName") twice but to use the context of the builder in order to resolve the Log.

builder.Register(c => LogManager.GetLogger("LoggerName")).As<ILog>();

builder.Register(c => 
    var product = new Product();
    product.SetLog(c.Resolve<Log>());
    return product;
);

Option 3:

Use the OnActvated:

The OnActivated event is raised once a component is fully constructed. Here you can perform application-level tasks that depend on the component being fully constructed - these should be rare.

builder.RegisterType<Product>()
    .OnActivated((IActivatedEventArgs<Log> e) =>
    {
        var product = e.Context.Resolve<Parent>();
        e.Instance.SetParent(product);
    });

These options gives more control, and you will not have to worry about @steven comment:

The scary thing with PropertiesAutowired however is that it does implicit property injection, which means that any unresolvable dependencies will be skipped. This makes it easy to miss configuration errors and can result in application that fails at runtime

Adrian
  • 8,271
  • 2
  • 26
  • 43
Shahar Shokrani
  • 7,598
  • 9
  • 48
  • 91
3

I didn't want to use postsharp so I made a quick solution, but it doesn't auto inject. I am new to Autofac, and it should be possible to build on to this solution.

[Serializable]
[AttributeUsage(AttributeTargets.Property)]
public class AutofacResolveAttribute : Attribute
{
}

public class AutofactResolver
{
    /// <summary>
    /// Injecting objects into properties marked with "AutofacResolve"
    /// </summary>
    /// <param name="obj">Source object</param>
    public static void InjectProperties(object obj)
    {
        var propertiesToInject = obj.GetType().GetProperties()
             .Where(x => x.CustomAttributes.Any(y => y.AttributeType.Name == nameof(AutofacResolveAttribute))).ToList();

        foreach (var property in propertiesToInject)
        {
            var objectToInject = Autofact.SharedContainer.Resolve(property.PropertyType);
            property.SetValue(obj, objectToInject, null);
        }
    }
}

Use it with this call:

AutofactResolver.InjectProperties(sourceObject);
Morten Holmgaard
  • 7,484
  • 8
  • 63
  • 85
0

There is an interface IPropertySelector that you can implement and pass the implementaiton via .PropertiesAutowired(new MyPropertySelector()). This will allow you to implement any logic you want.

t3chb0t
  • 16,340
  • 13
  • 78
  • 118