Search code examples
c#.netclassstructuremapobjectfactory

How to call constructor of class which are instantiated through Structure Map in C#


I have an interface called ILogger which basically contains some methods for logging.

Ilogger.cs

public interface ILogger
{   
    void LogError(string message, Exception exception = null);

    void LogMessage(string message);

    void LogValidationError(UploadResult uploadResult);

    void LogValidationError(ValidationResult validationResult);

    void LogProcessingError(string processingError);
}

I have a LogHelper class which implements this interface. The LogHelper class is instantiated through StructureMap like

ObjectFactory.Initialize(
    request =>
    {
        request.For<ILogger>().Singleton().Use<LogHelper>();

    });

I have many classes in whose constructor I just instantiate this class and call methods to log the information. For eg: I have a class say Dummy1 in whose constructor I instantiate the LogHelper as:

public Dummy1()
{
    this.logger = ObjectFactory.GetInstance<ILogger>();     
}

In LogHelper I have method which basically creates log file and writes the message passed as parameter to it.

public void LogMessage(string message)
{
    using (var writer = this.GetTextWriter(this.messageFilename))
    {
        writer.WriteLine(message);
    }
}

Currently the filename is hardcoded into a constant property of LogHelper class as private string messageFilename = "logs\\UserCreationResult.log";

But I want the Filename to be dynamically sent whenever the LogHelper is instantiated. I thought of having a class property and define that property in the constructor whenever the class is instantiated. But since the LogHelper class is instantiated as ObjectFactory.GetInstance<ILogger>(). I am not able call the constructor in which I can pass the filename.


Solution

  • Unfortunately the way you are going about this is a little bit self-defeating. Your classes only know about ILogger, not any particular implementation of ILogger. That's good - it means that the implementation could write to a file, a SQL table, or anything.

    But if your class only knows about ILogger, not the implementation, then how does your class know that the logger needs a file path? If you change your method signatures in ILogger to contain a file path, then two things happen.

    1. It becomes impossible to have any implementation of ILogger that doesn't write to a file (unless it ignores the file path, which would be really weird.)
    2. Now that class that calls the logger has to know a file path. Where will that class get a file path from? Will it be stored in the class? In that case you end up with a class that doesn't work unless it's part of an assembly executing on a computer where it can write to that exact file path.

    Instead, the details of where and how to log should live somewhere in your ILogger implementation. That's closer to the Single Responsibility Principle. The class that calls ILogger isn't responsible for decisions about how ILogger works. It doesn't know and it doesn't want to know. It says "Here, take this and log it." The logger implementation is responsible for the rest.

    I'd recommend scrapping the static ObjectFactory entirely and using the container to resolve and create all of your classes, including the logger and the classes that depend on it, but that's so broad that it's not really helpful. (It has been deprecated because it's a bad pattern. It's not even in the latest version of StructureMap.)

    Everything above this is a recommendation. After this I'm offering an option that's not really recommendable, but requires less change and keeps your classes from knowing about file paths, because please don't do that ever.

    One option - a halfway compromise - might be to register different named implementations of ILogger. You could modify your logger class to look like this:

    public class FileLogger : ILogger
    {
        private readonly string _filePath;
    
        public FileLogger(string filePath)
        {
            _filePath = filePath;
        }
    }
    

    Now you can create multiple instances of that logger class, passing a different file path to each one. That way it's not a static property, which limits you to only having one file path.

    Then you could register your implementations like this.

    ObjectFactory.Initialize(
        request =>
        {
            request.For<ILogger>().Singleton()
                .Use<FileLogger>(() => new FileLogger("some path")).Name = "LoggerOne";
    
            request.For<ILogger>().Singleton()
                 .Use<FileLogger>(() => new FileLogger("some other path")).Name = "LoggerTwo";
    
        });
    

    Now your class can say which logger it wants, like this:

    var logger = ObjectFactory.GetNamedInstance<ILogger>("LoggerOne");
    

    But please don't really do that either. It's more than I can really describe here in great detail, but take a look at dependency injection so that your classes really only know about ILogger and don't know or care which implementation they get and don't tell it anything about how to do its job.