1

Using Simple Injector I have come across a common theme with my logger where I set the logger name in the constructor of the service object to which the logger is injected. When the service writes to the log, it can be easily identified by this logname.

Since LogNames will be set for each service, the logger should be unique per object graph request.

I would like to do this automatically when the graph is being built, I have poked around in ExpressionBuilt() but I am struggling and yet to get what i want to work - is this even possible (or an OK thing to want to do)?

My constructor code is below (this LogName property setting code is common across most of my services).

Thanks,

Chris

public interface ILogger
{
    void LogMessage(string message, LogLevel level,
        ILoggableCompany company = null);

    string LogName {get; set; }
}

public BusinessUnitService
{
    private readonly IUnitOfWork unitOfWork;
    private readonly ILogger logger;

    public BusinessUnitService(IUnitOfWork unitOfWork, 
        ILogger logger)
    {
        this.unitOfWork = unitOfWork;
        this.logger = logger;

        // it would be great if we could take away this 
        // line and set it automatically
        this.logger.LogName = this.GetType().ToString();
    }
}
Steven
  • 166,672
  • 24
  • 332
  • 435
morleyc
  • 2,169
  • 10
  • 48
  • 108

1 Answers1

1

This design looks a bit like the Logger<T> design of log4net, where T will be the class the logger is created for. Although I can't look into your design, but I'm wondering: aren't you logging too much?

If you really need to do this, at least remove the LogName property from the ILogger interface, since it has no business there. This means you have to remove the code that sets this property from your constructors, which is absolutely fine, because this is code duplication all over the place.

What you are trying to do is context based injection, which is not something that is supported out of the box, but the Simple Injector wiki contains a Context Bases Injection section that explains how add this support. This documentation page even uses a Logger<T> as example :-)

Using the extension method the wiki refers to, you can do the following:

public interface ILogger
{
    void LogMessage(string message, LogLevel level,
        ILoggableCompany company = null);

    // No LogName property here. Keep it clean.
}

public class LoggerImpl : ILogger
{
    public void LogMessage(string message, 
        LogLevel level, ILoggableCompany company)
    {
       // implementation
    }

    // Property only part of the implementation.
    public string LogName {get; set; }
}

// The parent contains information about the type in 
// which ILogger is injected.
container.RegisterWithContext<ILogger>(parent =>
{
    // Retrieve a new LoggerImpl instance from the container 
    // to allow this type to be auto-wired.
    var logger = container.GetInstance<LoggerImpl>();

    // ImplementationType is null when ILogger is
    // requested directly (using GetInstance<ILogger>())
    // since it will have no parent in that case.
    if (parent.ImplementationType != null)
    {
        // Set the LogName with the name of the class 
        // it is injected into.
        logger.LogName = parent.ImplementationType.Name;
    }

    return logger;
});

// Register the LoggerImpl as transient. This line is
// in fact redundant in Simple Injector, but it is important
// to not accidentally register this type with another
// lifestyle, since the previous registration depends on it
// to be transient.
container.Register<LoggerImpl>();

Because this works by hooking into the BuiltExpression event, and by rewiring the expressions, resolving instances this way this almost as fast as registrations using a Func<T> factory method.

Community
  • 1
  • 1
Steven
  • 166,672
  • 24
  • 332
  • 435
  • But this will only work for constructor injection, not for property injection, will it? Because property setters are hidden behind a function invocation, they don't show up in the instantiation expression. Which means that the expression rewriter can't spot a property being injected. – Fyodor Soikin Aug 03 '15 at 04:06
  • @FyodorSoikin: It will actually work just fine with property injection, because property dependencies do show up in the instantiation expression. Just try this out yourself. – Steven Aug 03 '15 at 09:42
  • 1
    Indeed it does, I was mistaken. Even though the property itself doesn't show up in the expression, the call to `rootFactory` does, and that's what you can catch. However, I found another problem: when the service is open generic, `parent.ImplementationType` ends up being the _service type_, not implementation type, which means that you can't really do context-based injection with generic services. – Fyodor Soikin Aug 03 '15 at 15:25
  • @FyodorSoikin: True, the setting of the property doesn't end up in the expression tree, but the creation of the property's dependency does, and the expression tree is built deliberately to allow this. Simple Injector even contains unit tests that ensure this behavior will not change. – Steven Aug 03 '15 at 15:56
  • @FyodorSoikin: That's an interesting finding. I can't reproduce this when using `RegisterOpenGeneric`. How did you register your generic type? Would you mind posting this [as issue on Github](https://github.com/simpleinjector/SimpleInjector/issues/new)? – Steven Aug 03 '15 at 15:57
  • 1
    Done. Turns out you don't even need open generic service, but you do need property injection. https://github.com/simpleinjector/SimpleInjector/issues/86 – Fyodor Soikin Aug 03 '15 at 16:11
  • @FyodorSoikin: Thanks for taking the time to reporting this. – Steven Aug 03 '15 at 17:52