1

I use a method to handle exceptions - internally it writes to a database, but when released to the web, the source code won't include the connection string necessary to write to the database. Instead it should write to a log file.

Is it possible to accommodate writing to logs when Foo.Private.dll is not present, but write to the database when it is?

//In Foo.Public.dll assembly
public class SimpleLogWriter
{
    public virtual void LogError(Exception ex)
    {
        //Write to log file.
    }
}

...

//In Foo.Private.dll assembly
public class ExtendedLogWriter : SimpleLogWriter
{
    public override void LogError(Exception ex)
    {
        //Write to database
    }
}

I had considered having both log classes implementing a shared interface (instead of extending and overriding) and creating some factory method to render it, but not sure how to validate the existence of an assembly or use its types without adding a reference, in which case the end-project would have a circular reference.

John Saunders
  • 160,644
  • 26
  • 247
  • 397
lukiffer
  • 11,025
  • 8
  • 46
  • 70

4 Answers4

1

I can think of a couple ways you could accomplish this.

Tightly Coupled

Use reflections to detect the presence of the DLL. If it is present, load the appropriate classes, and make extra calls to them.

To do this, use Assembly.LoadFile, Assembly.GetType(string), and Activator.CreateInstance(type). Cast the new instance to your abstract base logger type/interface.

This is more or less what you were describing. I don't recommend this, though, because it isn't very flexible, and there are good alternatives.

Loosly Coupled

create an interface or abstract logger class, and use Dependency Injection (Inversion of Control) to inject the logger into the components that need to do logging. If you choose, you can use a dependency injection library to specify which implementations you want in a loosly coupled manner. Configure the DI library to load dependencies from your extra DLL (if present).

Castle.Windsor has a loosly coupled logging interface (the logging facility) that you could look into for the second option.

There is a sort of spectrum between these, as well.

This is the gist of injecting the logger as a dependency (though I'm not using any libraries in this example):

using System;
using System.IO;

public interface ILogger
{
    void WriteDebug(string debug);
    void WriteInfo(string info);
    void WriteError(string error);
}

public class NullLogger : ILogger
{
    private static ILogger instance = new NullLogger();

    // This singleton pattern is just here for convenience.
    // We do this because pattern has you using null loggers constantly.
    // If you use dependency injection elsewhere,
    // try to avoid the temptation of implementing more singletons :)
    public static ILogger Instance
    {
        get { return instance; }
    }

    public void WriteDebug(string debug) { }
    public void WriteInfo(string info) { }
    public void WriteError(string error) { }
}

public class FileLogger : ILogger, IDisposable
{
    private StreamWriter fileWriter;

    public FileLogger(string filename)
    {
        this.fileWriter = File.CreateText(filename);
    }

    public void Dispose()
    {
        if (fileWriter != null)
            fileWriter.Dispose();
    }

    public void WriteDebug(string debug)
    {
        fileWriter.WriteLine("Debug - {0}", debug);
    }

    // WriteInfo, etc
}

public class SomeBusinessLogic
{
    private ILogger logger = NullLogger.Instance;

    public SomeBusinessLogic()
    {
    }

    public void DoSomething()
    {
        logger.WriteInfo("some info to put in the log");
    }

    public ILogger Logger
    {
        get { return logger; }
        set { logger = value; }
    }
}

public class Program
{
    static void Main(string[] args)
    {
        // You're free to use a dependency injection library for this,
        // or simply check for a DLL via reflections and load a logger from there
        using (var logger = new FileLogger("logfile.txt"))
        {
            var someBusinessLogic = new SomeBusinessLogic()
            {
                // The component won't know which logger it is using - it just uses it
                Logger = logger,
            };

            someBusinessLogic.DoSomething();
        }
    }
}
Merlyn Morgan-Graham
  • 58,163
  • 16
  • 128
  • 183
1

This sounds like a potential use case for the Managed Extensibility Framework (MEF), available in .NET 4.0.

bobbymcr
  • 23,769
  • 3
  • 56
  • 67
  • This is an interesting option because (I believe) it is built into .Net 4. There is a discussion on using MEF vs IoC libraries here - http://stackoverflow.com/questions/216565/why-exactly-isnt-mef-a-di-ioc-container – Merlyn Morgan-Graham Jun 01 '11 at 06:32
  • This looks extremely promising, but I'm unclear on something. Does an assembly with MEF imports automatically go looking through all other assemblies in the `bin` folder for exports or do you have to specify candidate assemblies somehow? – lukiffer Jun 01 '11 at 06:56
  • @lukiffer: I believe you would want to use the DirectoryCatalog to specify a folder to probe for assemblies; see http://msdn.microsoft.com/en-us/library/system.componentmodel.composition.hosting.directorycatalog.aspx . – bobbymcr Jun 01 '11 at 07:36
0

This sounds like it's really just a matter of how you create your log writer. Rather than trying to do it based on whether a DLL is present or not, just allow the class to be instantiated to be part of the overall configuration. Let the user (by which I mean whoever is installing it) specify ExtendedLogWriter if they want and if they've got Foo.Private.dll, and SimpleLogWriter otherwise. There are various IoC containers which would make this easy.

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • In our assembly (which the end-user would add as a project reference) would call `LogClientInstance.GetInstance().LogError(ex)`. On our servers Foo.Private.dll would be present and write to the db, but if it's not present to a file. Foo.Private.dll shouldn't be required to build the end-user's project. The `GetInstance()` method returns ILogClient which should be an instance of either `SimpleLogWriter` or `ExtendedLogWriter` or is that excessively complicated? Also, I haven't used Inversion - any good reads? – lukiffer Jun 01 '11 at 06:35
  • 1
    @lukiffer: Okay, so you've basically got a singleton... Hmm. Logging *is* one of the few places where singletons can genuinely be more use than trouble, but if you can put use the configuration information within LogClientInstance, that would be the most appropriate thing to do IMO... I don't have any specific reading recommendations for inversion of control, but you should be able to find any number of tutorials with a quick search. It's really eye-opening :) (Also search for Dependency Injection.) – Jon Skeet Jun 01 '11 at 06:58
  • Thanks! Just about the time you think you have a firm grasp on C#, you find 10 other paradigms you didn't even consider before. :) – lukiffer Jun 01 '11 at 07:20
0

This could be solved by using inversion of control.

An interface IErrorLogger with a LogError(Exception) method is implemented by:

  • DbErrorLogger
  • FileErrorLogger

Using some inversion of control API like Castle Windsor, you differently configure IErrorLogger component since ASP.NET 4.0 has a Web.debug.config and Web.release.config allowing to configure some Web application for debugging and release-to-web scenarios.

At the end of the day, when you change compilation to release, FileErrorLogger will be the IErrorLogger implementation.

Learn more by following this link: http://docs.castleproject.org/Windsor.MainPage.ashx

Matías Fidemraizer
  • 63,804
  • 18
  • 124
  • 206