Wednesday, June 25, 2014

Activation error occurred while trying to get instance of type LogWriter, key ""

I have blogged about this error before and this post has become one of my most viewed. I hope it provides the answer you need and it makes sense. Please feel free to leave a comment!


Microsoft.Practices.ServiceLocation.ActivationException : Activation error occurred while trying to get instance of type LogWriter, key ""
 ----> Microsoft.Practices.Unity.ResolutionFailedException : Resolution of the dependency failed, type = "Microsoft.Practices.EnterpriseLibrary.Logging.LogWriter", name = "(none)".
Exception occurred while: while resolving.
Exception is: InvalidOperationException - The type LogWriter cannot be constructed. You must configure the container to supply this value.



It occurs when using Microsoft.Practices.EnterpriseLibrary for logging errors. There are lots of blogs that reference this error and they mostly say the error is caused by:

a) an error in your configuration (often the database connection string)
b) not all the DLLs are present

That was not my situation. I was logging just to the event log and I was certain I had all the DLLs in the bin directory of my web service. The error occurred when I deployed the web service to a new environment. 

I spent many hours tracking down the cause. In this case it was because Microsoft.Practices.EnterpriseLibrary was installed on the target environment and the DLLs were registered in the GAC. 

Now as you know, .NET will look for the location of DLLs in a specific order and the GAC is at the top of the list.  The problem is, when the Enterprise Library DLLs are installed in the GAC then it is unable to determine the location of the configuration file.  I guess that when they are just present in the bin directory the location of the configuration file is presumed to be "up a level".  Removing the DLLs from the GAC was not an option, I needed to find a way that would work with the DLLs in the bin directory or in the GAC.

I have a DLL that I use to simplify the calls when logging an error.  It has several constructors to create log entries of different severities.  Below is the code that includes just the critical error constructor. 

using Microsoft.Practices.EnterpriseLibrary.Logging;

namespace Ciber.Common.Logging
{

 public static class Logging
    {

       public static void LogCritical(string message, string title, string category)
        {
            Logger.Write(message, category, 3, 1, TraceEventType.Critical, title);
        }

     }
}

Now I have many places in my code where I reference the LogCritical method and I didn't want to change them. What I needed was a way to link up to the loggingConfiguration section in my web.config.

Firstly I added two additional references and using statements

using Microsoft.Practices.EnterpriseLibrary.Common.Configuration;
using Microsoft.Practices.EnterpriseLibrary.Logging.Configuration;


Then I added this method to my class:

public static LogWriter CreateLogger()
{
   EnterpriseLibraryContainer.Current = EnterpriseLibraryContainer.CreateDefaultContainer(ReadConfigSource());
   var logWriter = EnterpriseLibraryContainer.Current.GetInstance();
   return logWriter;
}


What that allows is to create a default container from an XML location that I specify and which in my case contains the loggingConfiguration section. 

I can then get an instance of the LogWriter class by calling EnterpriseLibraryContainer.Current.GetInstance(). 

Before I list the ReadConfigSource method let me first show the modified constructor from above which now reads:

public static void LogCritical(string message, string title, string category)
{
   LogWriter logger = CreateLogger();
   logger.Write(message, category, 3, 1, TraceEventType.Critical, title);
}

Note the lower case L on the logger object.  So great, my calls that write to the event log don't need to be changed and I have the ability to specify where my loggingConfiguration section is located. 

Below is the code for the ReadConfigSource() method.  Two very important points here.

1) I use OpenWebConfiguration() with HttpRuntime.AppDomainAppVirtualPath which will resolve correctly both in debug mode and in deployed mode.  Don't be tempted to use OpenWebConfiguration(null) or OpenWebConfiguration("/") which will work fine in Debug mode but will fail when deployed.

2) I added this to the start of my loggingConfiguration string @"<?xml version=""1.0"" encoding=""utf-8"" ?>".  Without it the loggingSection.ReadXml(xmlReader); line will fail. 

 public static IConfigurationSource ReadConfigSource()
{
   var configSource = new DictionaryConfigurationSource();
   string virpath = HttpRuntime.AppDomainAppVirtualPath;
   Configuration configFile = System.Web.Configuration.WebConfigurationManager.OpenWebConfiguration(virpath);

   ConfigurationSection section = configFile.GetSection("loggingConfiguration");
   if (section != null)
   {
      string loggingConfig = @"<?xml version=""1.0"" encoding=""utf-8"" ?>";
      loggingConfig = loggingConfig + section.SectionInformation.GetRawXml();

      LoggingSettings loggingSection = null;
      using (var stringReader = new StringReader(loggingConfig))
      {
          var xmlReader = XmlReader.Create(stringReader,
             new XmlReaderSettings() { IgnoreWhitespace = true });

          loggingSection = new LoggingSettings();
          loggingSection.ReadXml(xmlReader);
          xmlReader.Close();
       }

       configSource.Add(LoggingSettings.SectionName, loggingSection);
     }

      return configSource;
}

I am pleased with the result.  Hope it works for you.

No comments: