Entlib and interop: does it work, and where does the config file go?

Topics: Enterprise Library Core, General discussion, Logging Application Block
Dec 11, 2008 at 12:28 PM
I'm trying to use the EntLib 3.1 within .net code for a dll which is registered for COM interop. Where do I put the config file?

Following from my problems in this post I created a simple app which uses entlib Logging, with two classes: 'CallingApp' and 'MyComThing'. When I call a method of MyComThing from CallingApp it logs using the configuration in CallingApp's config file. When I call the method of MyComThing from a vbs script, ie through COM, I get an error "The configuration section for Logging cannot be found in the configuration source". My COMThing.dll.config file is in the same folder as the registered COMThing.dll, ie in the bin\debug\ folder.

Is there a way to specify within the dll code where it should get the config file from?

thanks!

- Rory



Dec 11, 2008 at 2:17 PM
Edited Dec 11, 2008 at 2:18 PM

Hi Rory, I had a similar problem once using a.net dll for COM interop from a VB 6 application, the config file must berelated to the .exe and not to a .dll. I had to add the configuration to thefinal VB6 client. The config information must be related to the main process. Iknow this doesn’t solve your problem, but it will avoid you working around it.

Juan Pablo Schmiede 

C# MVP

Dec 11, 2008 at 5:08 PM
Look this other thread http://www.codeplex.com/entlib/Thread/View.aspx?ThreadId=41965
Dec 11, 2008 at 5:11 PM
I think the solution lies here in the examples under 'using several configuration sources'... I'll put together a solution and then post it.
Dec 11, 2008 at 10:26 PM
Edited Dec 11, 2008 at 10:28 PM
The answer is that Enterprise Library by default uses the exe's config file. If you're producing a dll, including COM, then for good reason you might not want to depend on the calling executable. One solution to this (there might be others) is to create the Enterprise Library objects yourself instead of using the default ones, and tell them where to get the configuration from. This isn't as scary as it seems and doesn't require recompiling entlib or anything like that.

Instead of simply using Logger.Write() I did the following:
a) Create the log writer, using the dll's config file:

            string dllConfigFilename = Assembly.GetExecutingAssembly().Location + ".config";
            FileConfigurationSource exceptionsSource = new FileConfigurationSource(dllConfigFilename);
            LogWriterFactory writerFactory = new LogWriterFactory(exceptionsSource);
            logWriter = writerFactory.Create();

b) Then use this log writer within your code:

            LogEntry log = new LogEntry();
            log.Message = message;
            log.Categories = new string[] { "General" };
            logWriter.Write(log);

Here's the full code for a sample object I created (in a project called COMThing, so it'll compile to COMThing.dll). The references were Microsoft.Practices.EnterpriseLibrary.Common, Microsoft.Practices.EnterpriseLibrary.Logging, Microsoft.Practices.ObjectBuilder, System, System.Data, System.Windows.Forms, System.Xml:

    using System;
    using System.Collections.Generic;
    using System.Text;
    using System.Runtime.InteropServices;
    using System.Windows.Forms;
    using System.Reflection;
    using System.IO;
    using Microsoft.Practices.EnterpriseLibrary.Logging;
    using Microsoft.Practices.EnterpriseLibrary.Common.Configuration;
    
    namespace COMThing
    {
        [ComVisible(true)]
        public class MyComThing : MyComInterface
        {
            LogWriter logWriter;
    
            public MyComThing()
            {
                string dllConfigFilename = Assembly.GetExecutingAssembly().Location + ".config";
                FileConfigurationSource exceptionsSource = new FileConfigurationSource(dllConfigFilename);
                LogWriterFactory writerFactory = new LogWriterFactory(exceptionsSource);
                logWriter = writerFactory.Create();
            }
    
            public bool ProcessMessage(string message)
            {
                LogEntry log = new LogEntry();
                log.Message = message;
                log.Categories = new string[] { "General" };
                logWriter.Write(log);
                MessageBox.Show(message);
                return true;
            }
        }
    
    }

The project included a COMThing.dll.config file which i set 'Copy to Output Directory' to 'Copy always'. This is a trivial config that writes log information to the Application Event Log. The contents of the config file are:

    <?xml version="1.0" encoding="utf-8"?>
    <configuration>
      <configSections>
        <section name="loggingConfiguration" type="Microsoft.Practices.EnterpriseLibrary.Logging.Configuration.LoggingSettings, Microsoft.Practices.EnterpriseLibrary.Logging, Version=3.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
        <section name="dataConfiguration" type="Microsoft.Practices.EnterpriseLibrary.Data.Configuration.DatabaseSettings, Microsoft.Practices.EnterpriseLibrary.Data, Version=3.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
      </configSections>
      <loggingConfiguration name="Logging Application Block" tracingEnabled="true"
        defaultCategory="General" logWarningsWhenNoCategoriesMatch="true">
        <listeners>
          <add source="COMThing Logger" formatter="Text Formatter" log="Application"
            machineName="" listenerDataType="Microsoft.Practices.EnterpriseLibrary.Logging.Configuration.FormattedEventLogTraceListenerData, Microsoft.Practices.EnterpriseLibrary.Logging, Version=3.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"
            traceOutputOptions="None" type="Microsoft.Practices.EnterpriseLibrary.Logging.TraceListeners.FormattedEventLogTraceListener, Microsoft.Practices.EnterpriseLibrary.Logging, Version=3.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"
            name="Formatted EventLog TraceListener" />
        </listeners>
        <formatters>
          <add template="Timestamp: {timestamp}&#xD;&#xA;Message: {message}&#xD;&#xA;Category: {category}&#xD;&#xA;Priority: {priority}&#xD;&#xA;EventId: {eventid}&#xD;&#xA;Severity: {severity}&#xD;&#xA;Title:{title}&#xD;&#xA;Machine: {machine}&#xD;&#xA;Application Domain: {appDomain}&#xD;&#xA;Process Id: {processId}&#xD;&#xA;Process Name: {processName}&#xD;&#xA;Win32 Thread Id: {win32ThreadId}&#xD;&#xA;Thread Name: {threadName}&#xD;&#xA;Extended Properties: {dictionary({key} - {value}&#xD;&#xA;)}"
            type="Microsoft.Practices.EnterpriseLibrary.Logging.Formatters.TextFormatter, Microsoft.Practices.EnterpriseLibrary.Logging, Version=3.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"
            name="Text Formatter" />
        </formatters>
        <categorySources>
          <add switchValue="All" name="General">
            <listeners>
              <add name="Formatted EventLog TraceListener" />
            </listeners>
          </add>
        </categorySources>
        <specialSources>
          <allEvents switchValue="All" name="All Events">
            <listeners>
              <add name="Formatted EventLog TraceListener" />
            </listeners>
          </allEvents>
          <notProcessed switchValue="All" name="Unprocessed Category" />
          <errors switchValue="All" name="Logging Errors &amp; Warnings">
            <listeners>
              <add name="Formatted EventLog TraceListener" />
            </listeners>
          </errors>
        </specialSources>
      </loggingConfiguration>
    </configuration>

In the project properties under Build check 'Register for COM interop'. Build the project, then create the following .vbs file:

    Set obj = CreateObject("COMThing.MyComThing")
    obj.ProcessMessage("called from com by vbs")

If you double-click this vbs file it should show a message box with the text 'called from com by vbs' and write an entry to your Application Event Log. This demonstrates that while the executing process is C:\WINDOWS\System32\WScript.exe (or similar), it's getting the config from your dll's config file.

I based this on the information [here][1] under 'Using Several ConfigurationSources'.

Note that the Logger class includes lots of nice helper methods with different arguments. Since we're using the LogWriter class we don't get this magic. Personally I'll be creating another class within my library to perform the same job, based on Logger.

The referenced article shows the same principle applied to Database and Exception application blocks. Presumably the same model can be applied to most/all of them.

  [1]: http://blogs.msdn.com/tomholl/archive/2006/04/02/entlib2externalconfig.aspx

Dec 12, 2008 at 5:14 AM
Hi,

Thanks for the Information.

Valiant Dudan
Global Technology and Solutions
Avanade, Inc.
entlib.support@avanade.com