Monday, August 15, 2005

.NET Application Configuration File Management

1. .NET Application Configuration

One of the nice features .NET is the interoperability story that goes with using a .NET component form a non-.NET client such as an unmanaged C++ Windows® application, a VB application, or even scripting. While the interoperability story is well know and understood, much is not said about using the native .NET configuration files in there mixed mode operation.

It is common for a .NET component or application developer to include a configuration file (i.e., <application name>.exe.config or web.config) file with there application. In most cases the component developer chooses to use the native .NET support for configuration file (i.e., System.Configuration et-al). In a .Net to .NET scenario this works great out-of-the-box, but in a non-.NET to .NET scenario it is not clear to some how this process works. This paper will attempt to shed some light on the process. In this paper I will consider several scenarios dealing with COM Interop. I will not consider C++ interop and I will also discuss the option of not using the native .NET configuration support, but rather opting to read your own configuration data yourself. The options I will consider are as follows:

1) Unmanaged C++ Client using .NET
2) ASP JavaScript Client using .NET
3) HTML JavaScript Client using .NET
4) Any Client using .NET Enterprise Services
5) Use Application Specific Configuration Reading

For the purpose of the scenarios listed above we will use the following .NET component code as a base:

      // ManagedComponent.CS

      using System;

      using System.Configuration;

      using System.Runtime.InteropServices;

      namespace Norman.Headlam.Sample

      {

      /// <summary>

      /// Summary description for Data class.

      /// </summary>

      [ComVisible(true)]

      [ClassInterface(ClassInterfaceType.AutoDual)]

      public class Data

      {

      public Data()

      {

      }

      public string GetData()

      {

      string data = ConfigurationSettings.AppSettings["data"] as string;

      string configFile = AppDomain.CurrentDomain.GetData( "APP_CONFIG_FILE" ) as string;

      return data + configFile;

      }

      }

      }

      // AssemblyInfo.CS

      using System.Reflection;

      using System.Runtime.CompilerServices;

      [assembly: AssemblyTitle("Managed Component")]

      [assembly: AssemblyDescription("Managed Component")]

      [assembly: AssemblyConfiguration("")]

      [assembly: AssemblyCompany("Norman Headlam")]

      [assembly: AssemblyProduct("Managed Component Sample")]

      [assembly: AssemblyCopyright("Norman Headlam (c) 2005")]

      [assembly: AssemblyTrademark("Norman Headlam")]

      [assembly: AssemblyCulture("")]

      [assembly: ComVisible(false)]

      [assembly: AssemblyVersion("1.0.0.0")]

      [assembly: AssemblyDelaySign(false)]

      [assembly: AssemblyKeyFile("")]

      [assembly: AssemblyKeyName("")]

In this simple component, the .NET component exposes a GetData method the simply returns a string that is read from the configuration file that the client can then display.

Note that for this managed component I did not strongly name the assembly with a strong name using a public/private key pair and the sn.exe tool (see the content of the AssemblyInfo.CS file). This is because I’m not going to deploy the component to the global assembly cache (GAC) in one of the scenario and I wanted to make this point. Instead, for this scenario, I’m going to co-locate the .NET DLL assembly along side the client exe to demonstrate that .NET will still find the component. This is possible since the .NET runtime will simply look for the assembly in the AppPath (i.e., the directory of the EXE process) for all referenced assemblies. Also note that I attributed the Data class with the ComVisible and ClassInterface attributes. The ComVisible attribute is needed since I made all component COM invisible (i.e., not accessible via COM … without some work) by default (see the AssemblyInfo.CS file). The ClassInterface attribute is used to expose the public methods of the Data class as a dual interface (i.e., support early and late binding). I wanted early binding to use it easily from the C++ client. This approach is usually not recommended due to versioning issues you might have.

One last point; when we compile and generate the TLB for this managed type (i.e., RegAsm /TLB ManagedComponent.DLL). We get the following information in the TLB file (i.e., the IDL information):

      // Generated .IDL file (by the OLE/COM Object Viewer)

      //

      // typelib filename: ManagedComponent.tlb

      [

      uuid(F045BFBB-1349-33B1-A41F-306BA6AA68AE),

      version(1.0),

      custom(90883F05-3D28-11D2-8F17-00A0C9A6186D, ManagedComponent, Version=1.0.2048.16331, Culture=neutral, PublicKeyToken=null)

      ]

      library ManagedComponent

      {

      // TLib : // TLib : Common Language Runtime Library : {BED7F4EA-1A96-11D2-8F08-00A0C9A6186D}

      importlib("mscorlib.tlb");

      // TLib : OLE Automation : {00020430-0000-0000-C000-000000000046}

      importlib("STDOLE2.TLB");

      // Forward declare all types defined in this typelib

      interface _Data;

      [

      uuid(352B66A6-77AE-33CB-B18C-4ABDE732D984),

      version(1.0),

      custom(0F21F359-AB84-41E8-9A78-36D110E6D2F9, Norman.Headlam.Sample.Data)

      ]

      coclass Data {

      [default] interface _Data;

      interface _Object;

      };

      [

      odl,

      uuid(30387035-82EA-3D39-96D0-8BA58475F170),

      hidden,

      dual,

      nonextensible,

      oleautomation,

      custom(0F21F359-AB84-41E8-9A78-36D110E6D2F9, Norman.Headlam.Sample.Data)

      ]

      interface _Data : IDispatch {

      [id(00000000), propget,

      custom(54FC8F55-38DE-4703-9C4E-250351302B1C, 1)]

      HRESULT ToString([out, retval] BSTR* pRetVal);

      [id(0x60020001)]

      HRESULT Equals(

      [in] VARIANT obj,

      [out, retval] VARIANT_BOOL* pRetVal);

      [id(0x60020002)]

      HRESULT GetHashCode([out, retval] long* pRetVal);

      [id(0x60020003)]

      HRESULT GetType([out, retval] _Type** pRetVal);

      [id(0x60020004)]

      HRESULT GetData([out, retval] BSTR* pRetVal);

      };

      };

Note that the methods from System.Object are also exported.

1.1. Unmanaged C++ Client using .NET Component Scenario

    In this scenario we will consider the case of an unmanaged C++ console client (UnmanagedClient.EXE) using a .NET DLL assembly. For this process to work using the native .NET configuration mechanism, the .NET DLL assembly should have the following attributes:

1) The .NET component that is being exposed must have a public default constructor (i.e., take no arguments).
2) At a minimum the .NET component should be COM visible. You can also make all components in the assembly COM visible, but that is not recommended.

3) The .NET DLL assembly must be registered in the system registry (i.e., regasm /tlb assmeblyname.dll).
4) If .NET DLL assembly use other .NET Framework assemblies, make sure there are co-located with the .NET DLL assembly or are deployed into the GAC.

1.1.1. Client Application Code (UnmanagedClient.EXE)

    In this scenario we will use the following unmanaged client code console application as the client.

      stdafx.h

      #pragma once


      #include <iostream>

      #include <tchar.h>

      #import "..//ManagedComponent.tlb" no_implementation \

      raw_interfaces_only \

      raw_native_types

      UnmanagedClient.cpp

      #include "stdafx.h"

      #include <atlbase.h>

      #include <atlcom.h>

      #include <comdef.h>

      int _tmain(int argc, _TCHAR* argv[])

      {

      USES_CONVERSION;

      HRESULT hr = CoInitialize( NULL);

      CComPtr< ManagedComponent::_Math > spManaged;

      hr = spManaged.CoCreateInstance( __uuidof( ManagedComponent::Math ) );

      long z;

      hr = spManaged->Add( 2, 2, &z );

      printf( "2 + 2 = %d", z );

      CComPtr< ManagedComponent::_Data > spData;

      hr = spData.CoCreateInstance( __uuidof( ManagedComponent::Data ) );

      CComBSTR data;

      hr = spData->GetData( &data );

      printf( "\n\n%s", OLE2A( data ) );

      data.Empty( );

      CoUninitialize( );

      return 0;

      }

      1.1.2. Client Application Configuration File (UnmanagedClient.EXE.Config)

    The unmanaged console application is called UnmanagedCleint.EXE. As such we will need an associated configuration file called UnmanagedClient.EXE.Config. The configuration file is as follows and is in the same directory as the UnmanagedClient.EXE program.

      UnmanagedClient.EXE.Config

      <?xml version="1.0" encoding="utf-8" ?>

      <configuration>

      <appSettings>

      <add key="data" value="Hello. I'm the unmanaged process Unmanaged.exe. Here is my config file --> " />

      </appSettings>

      </configuration>

      1.1.3. Special Notes

    To get the program to compile we imported the ManagedComponent.TLB, generated using the RegAsm /TLB ManagedComponent.DLL command, into the unmanaged client. This allowed us to access the managed class public methods from the COM client.

    1.1.4. Opportunity for improvements

    We could have improved this code some.

1) First we could have used an interface to convey the public interface to the client. We still have to make the class COM visible, but client could access the managed component via an interface instead.

2) Secondly, we could have strongly named the assembly. This would allow us to deploy the assembly into the GAC and thus share the same assembly between all clients. This feature would also support versioning using the native .NET support for versioning.

1.2. ASP JavaScript Client using .NET Component Scenario

    In this scenario we will consider the case of an unmanaged ASP client using JavaScript to access the function of the .NET DLL assembly. For this process to work using the native .NET configuration mechanism, the .NET DLL assembly should have the following attributes:

3) The .NET component that is being exposed must have a public default constructor (i.e., take no arguments).
4) At a minimum the .NET component should be COM visible. You can also make all components in the assembly COM visible, but that is not recommended.

5) The .NET DLL assembly must be strongly signed with a private/public key pair that was generated with the sn.exe tool (i.e., sn.exe –k sample.snk)

6) The .NET DLL assembly must be registered in the system registry (i.e., regasm /tlb assmeblyname.dll).
7) The .NET DLL assembly must be deployed to the GAC (i.e., gacutil.exe /i ManagedComponent.DLL)
8) If the .NET DLL assembly use other .NET Framework assemblies, make sure there are deployed into the GAC as well.

1.1.5. Client Application Code (ASPClient.ASP)

    In this scenario we will use the following JavaScript in an ASP file (i.e.) as the client.

      ASPClient.asp

      <% @Language=JScript %>

      <%

      data = GetData( );

      Response.Write( "<div>" );

      Response.Write( data );

      Response.Write( "</div>" );

      %>


      <Script Language=JScript RunAt=Server>

      // Define Server Side Script Function

      function GetData( )

      {

      var objData = new ActiveXObject( "Norman.Headlam.Sample.Data" );

      return objData.GetData( );

      }

      </Script>

      1.1.6. Client Application Configuration File (Dllhost.EXE.Config)

    The ASP client application is called ASPClient.ASP. Since ASP applications will get executed in a surrogate process, dllhost.exe in this case. We need to associate a configuration file with the dllhost.exe process. This process is located in the %SystemRoot%\System32 directory. As such we need to copy a version of the dllhost.exe.config file to that directory. The content of the file (for this example) is the same as before.

      dllhost.exe.config

      <?xml version="1.0" encoding="utf-8" ?>

      <configuration>

      <appSettings>

      <add key="data" value="Hello. I'm the unmanaged process dllhost.exe. Here is my config file --> " />

      </appSettings>

      </configuration>

      1.1.7. Special Notes

    The dllhost.exe.config file is a shared asset among all applications that uses it a surrogate. This does not bode well in all scenarios. We will discuss alternatives later. Also note that since the ASP application, running in the dllhost.exe process is running under a credential. If the .NET component needed access to a restricted resource (i.e., a resource that requires certain security access to use such as the certificate store, a special file folder, etc.), the credential that the dllhost.exe process runs under (i.e., IWAM_<machine name>) would need access as well with this default configuration.

    1.1.8. Opportunity for improvements

    The following are opportunities for improvements:

1) We could have deployed the .NET Component as an Enterprise Services Component. This would allow use to have per-application configuration file and allowed for using a specific id to execute under that would give us a little more control with the least amount of work (i.e., no impersonation, etc.)

1.3. HTML JavaScript Client using .NET Component Scenario

    In this scenario we will consider the case of an unmanaged client using JavaScript to access the function of the .NET DLL assembly (i.e., an html file executing code locally, etc.). For this process to work using the native .NET configuration mechanism, the .NET DLL assembly should have the following attributes:

1) The .NET component that is being exposed must have a public default constructor (i.e., take no arguments).
2) At a minimum the .NET component should be COM visible. You can also make all components in the assembly COM visible, but that is not recommended.

3) The .NET DLL assembly must be strongly signed with a private/public key pair that was generated with the sn.exe tool (i.e., sn.exe –k sample.snk)

4) The .NET DLL assembly must be registered in the system registry (i.e., regasm /tlb assmeblyname.dll).
5) The .NET DLL assembly must be deployed to the GAC (i.e., gacutil.exe /i ManagedComponent.DLL)
6) If the .NET DLL assembly use other .NET Framework assemblies, make sure there are deployed into the GAC as well.

1.1.9. Client Application Code (HtmlClient.htm)

    In this scenario we will use the following JavaScript in an htm file (i.e., HTMLClient.htm) as the client.

      HTMLClient.htm

      <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">

      <html>

      <head>

      <title>HTML Client Calling .NET Assembly</title>

      <meta name="GENERATOR" content="Microsoft Visual Studio .NET 7.1">

      <meta name="ProgId" content="VisualStudio.HTML">

      <meta name="Originator" content="Microsoft Visual Studio .NET 7.1">

      <Script Language="JScript">

      function GetData( )

      {

      var objData = new ActiveXObject( "Norman.Headlam.Sample.Data" );

      return objData.GetData( );

      }

      </Script>

      </head>

      <body>

      <input type="button" value="Button" ID="idGetData" NAME="dataButton" onclick="document.getElementById('data').value = GetData()">

      <input name="data" type="text" readonly value="Returned data goes here" size="46">

      </body>

      </html>

      1.1.10. Client Application Configuration File (Iexplore.Exe.Config)

    The client application is a file called HTMLClient.HTM. Since the client code is executing in the context of the internet explorer browser applications (i.e., iexplore.exe) we need to configure the configuration file for iexplore.exe.config. The iexplore.exe process is located in the %SystemRoot%\System32 directory. As such we need to copy a version of the iexplore.exe.config file to that directory. The content of the file (for this example) is the same as before.

      iexplore.exe.config

      <?xml version="1.0" encoding="utf-8" ?>

      <configuration>

      <appSettings>

      <add key="data" value="Hello. I'm the unmanaged process iexplore.exe. Here is my config file --> " />

      </appSettings>

      </configuration>

    This scenario is the same as it was for the ASP application with the exception of the name of the process. Had we execute this application in a custom browser, we would simply use its config file in stead (i.e., <custom application>.exe.config).

    1.1.11. Special Notes

    The iexplore.exe.config file is a shared asset among all applications that uses it as a surrogate. This does not bode well in all scenarios. We will discuss alternatives later. Also note that since the iexplore.exe application runs with the user’s credential (unless otherwise instructed to), if the .NET component needed access to a restricted resource (i.e., a resource that requires certain security access to use such as the certificate store, a special file folder, etc.), the credential that the iexplore.exe process runs under would need access as well.

    1.1.12. Opportunity for improvements

    I don’t find much room for improvement here. That said here is one improvement:

1) Don’t use this approach.

1.4. .NET Component Configured as a .NET Enterprise Services Application Scenario

    In this scenario we will use an ASP client application that calls a component in a .NET Enterprise Services Application (a.k.a. COM+ Application). While this scenario uses an ASP client application, we could have used any other type of client (i.e., an unmanaged C++ console application, a .NET application, etc.).

    For this scenario, the .NET component must have the following attributes:

1) The .NET component that is being exposed must have a public default constructor (i.e., take no arguments).
2) The .NET component must should be based on an interface.
3) The .NET component must extend the System.EnterpriseServices.ServiceComponent class.
4) The .NET DLL assembly must reference the System.EnterpriseServices.DLL
5) At a minimum the .NET component should be COM visible. You can also make all components in the assembly COM visible, but that is not recommended.

6) The .NET DLL assembly must be strongly signed with a private/public key pair that was generated with the sn.exe tool (i.e., sn.exe –k sample.snk)

7) The .NET DLL assembly must be registered in the system registry (i.e., regasm /tlb assmeblyname.dll).
8) The .NET DLL assembly must be deployed to the GAC (i.e., gacutil.exe /i ManagedComponent.DLL)
9) If the .NET DLL assembly used other .NET Framework assemblies, make sure there are deployed into the GAC as well.

10) The .NET DLL assembly must be registered with .NET Enterprise Services (i.e., COM+). There are several ways to do this (see the MSDN docs for more info or consult “COM and .NET Component Services” by Juval Lowy published by O’Reilly. To register the assembly, use the following command:

      regsvcs.exe /appname:ManagedComponent ManagedComponentES.DLL

      This registration process will also create a type library that can be used by a C++ unmanaged client (i.e., ManagedComponentES.TLB). To make sure the application gets registered as a “Server Application” add the following to the assemblyinfo.cs file

      assembly: using System.EnterpriseServices;

      ApplicationActivation(ActivationOption.Server)]

      1.1.13. .NET Enterprise Services Component (ManagedComponentES.DLL)

    The following code is what our test .NET Component looks like after it is converted to be a .NET Enterprise Services component.

      Data.cs

      using System;

      using System.Configuration;

      using System.Runtime.InteropServices;

      using System.EnterpriseServices;

      namespace Norman.Headlam.Sample.EX

      {

      /// <summary>

      /// Summary description for Data class.

      /// </summary>

      [ComVisible(true)]

      [ClassInterface(ClassInterfaceType.AutoDual)]

      public class Data : ServicedComponent, IGetData

      {

      public Data()

      {

      }

      public string GetData()

      {

      return ConfigurationSettings.AppSettings["data"] as string;

      }

      }

      }

      IGetData.cs

      using System;

      namespace Norman.Headlam.Sample.ES

      {

      /// <summary>

      /// Summary description for IGetData.

      /// </summary>

      public interface IGetData

      {

      string GetData();

      }

      }

      AssemblyInfo.CS

      using System.EnterpriseServices;

      using System.Reflection;

      using System.Runtime.CompilerServices;

      using System.Runtime.InteropServices;

      [assembly: AssemblyTitle("Managed Component")]

      [assembly: AssemblyDescription("Managed Component")]

      [assembly: AssemblyConfiguration("")]

      [assembly: AssemblyCompany("Norman Headlam")]

      [assembly: AssemblyProduct("Managed Component Sample")]

      [assembly: AssemblyCopyright("Norman Headlam (c) 2005")]

      [assembly: AssemblyTrademark("Norman Headlam")]

      [assembly: AssemblyCulture("")]

      [assembly: ComVisible(false)]

      [assembly: ApplicationActivation(ActivationOption.Server)]

      [assembly: AssemblyVersion("1.0.0.0")]

      [assembly: AssemblyDelaySign(false)]

      [assembly: AssemblyKeyFile(@"..\..\..\sample.snk")]

      [assembly: AssemblyKeyName("")]

    After the registration of the component, the Component Manager Explorer will look something like this:



      Figure 1 - Managed Component in Component Services Manager

      1.1.14. Configuring the .NET Enterprise Services Application

    We next need to configure the .NET Enterprise Services Application to run under a specific credential and setup the application to use a “per application configuration file” (i.e., we will no longer use the dllhost.exe.config file across all applications). Instead we will configure each .NET Enterprise Services Application (i.e., COM+ Application). This process works for Windows XP SP2 with .NET Framework 1.1 and Windows 2003 with the .NET 1.1 Framework.

1) Create an “Application Root Directory” and config the setting in the “Activation” properties for the .NET Enterprise Services Application. You can use the Component Services Explorer as shown here.





      Figure 2 - Configure Application Root Directory for Managed Component

2) Add a file called application.manifest to the “Application Root Directory” folder (i.e., C:\TEMP\Unmanaged to Managed). This file should have the following content

      <?xml version="1.0" encoding="UTF-8" standalone="yes"?>

      <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">

      </assembly>

3) Add a file called application.config to the “Application Root Directory” folder (i.e., C:\TEMP\Unmanaged to Managed). This represents the per application configuration. For this example the content of the file should be as follows.

      <?xml version="1.0" encoding="utf-8" ?>

      <configuration>

      <appSettings>

      <add key="data" value="Hello. I'm the Enterprise Services config file as configured in Component Manager Explorer and is being used by dllhost.exe. Here is my config file --> " />

      </appSettings>

      </configuration>

      1.1.15. Client Application Code (ASPClientUsingES.ASP)

    The ASP client application we will use in this scenario is called ASPClientUsingES.ASP. In this scenario the .NET component will get executed in the dllhost.exe process, but instead of using the dllhost.exe.config file, it will use application.config located in the “Application Root Directory”

      ASPClientUsingES.ASP

      <% @Language=JScript %>

      <%

      data = GetData( );

      Response.Write( "<div>" );

      Response.Write( data );

      Response.Write( "</div>" );

      %>

      <Script Language=JScript RunAt=Server>

      // Define Server Side Script Function

      function GetData( )

      {

      var objData = new ActiveXObject( "Norman.Headlam.Sample.ES.Data" );

      return objData.GetData( );

      }

      </Script>

      <%

      data = GetData( );

      Response.Write( "<div>" );

      Response.Write( data );

      Response.Write( "</div>" );

      %>


      <Script Language=JScript RunAt=Server>

      // Define Server Side Script Function

      function GetData( )

      {

      var objData = new ActiveXObject( "Norman.Headlam.Sample.ES.Data" );

      return objData.GetData( );

      }

      </Script>

      1.1.16. Special Notes

    You might experience a problem like so when you execute the application. As usually check to make sure the .NET Enterprise Services Application is configured with the correct credentials to do its job and interact with the client (ASPClientUsngES.ASP in this case).


      Figure 3 - Error Message with Improper Security Credentials

    You can configure the credentials for the .NET Enterprise Services Application using the Component Services Explorer as shown here. In this example I’m configuring it to use my personal domain ID (this is not the recommended approach). It is best to create a special account to execute you application under.

    Figure 4 - Configure Security for Managed Component

      1.5. Changing the Default Config File Name

At this time you might be wondering how was it possible to have a configuration file with a name other that <application name>.exe.config; such as web.config or application.config. As it turns out the application configuration file a .NET component uses is determined independently for each AppDomain within a process and more importantly you can change it. To change the location and name of the configuration file you must follow the rules below:

1) In your .NET component (or exe process) you should change the configuration file locations as shown below. Notice that you can give the file any name and place it in any folder that the process has access to.

      AppDomain.CurrentDomain.SetData( “APP_CONFIG_FILE”, “C:\TEMP\Unmanaged to Managed\Foo.MyConfig” );

2) You must make the call above before making any other calls to the .NET configuration API. You only have one chance to change the location of the configuration file for each unique AppDomain.

You should know that this technique will work in all the scenarios we discussed so far, but you should only use it in rare cases. If you are using a .NET EXE assembly, it is safe to use this approach. In this case you would change the configuration location in the process startup. However, in the COM interop or a .NET Enterprise Services case, you really don’t have control over which component will be called first and thus not have a chance to set the configuration file location in all cases. In the case of the .NET Enterprise Services configuration I would always opt to using the “Application Root Directory” approach as it is more safe and configurable out-of-band.

Here is an example of such an approach where we change the config file in the component constructor. This assume of course we are only using this single component or this component is always called before calling another method, property, etc. that uses the native .NET configuration API.

      Data.cs

      using System;

      using System.Configuration;

      using System.Runtime.InteropServices;

      using System.EnterpriseServices;


      namespace Norman.Headlam.Sample.CustomConfig

      {

      /// <summary>

      /// Summary description for Data class.

      /// </summary>

      [ComVisible(true)]

      [ClassInterface(ClassInterfaceType.AutoDual)]

      public class Data : ServicedComponent, IGetData

      {

      public Data()

      {

      // set the location of the configuration file

      AppDomain.CurrentDomain.SetData( "APP_CONFIG_FILE", @"C:\TEMP\Unmanaged to Managed\Foo.MyConfig" );

      }

      public string GetData()

      {

      string data = ConfigurationSettings.AppSettings["data"] as string;

      string configFile = AppDomain.CurrentDomain.GetData( "APP_CONFIG_FILE" ) as string;

      return data + configFile;

      }

      }

      }

      1.6. Custom Config File API

Another option that is available to you is to opt out of the native .NET support for configuration files and use you own custom approach.

In this scenario you can just load up a configuration file using possibly the Xml DOM API, a File Stream API, etc. From there you would just read the configuration file as needed to get you configuration information, perhaps using XML DOM or deserializing it into an object. When this approach is use you clearly will not have the benefits of the native support provide by .NET, but that might, and quite often is, acceptable. Another feature you will loose is the application settings feature offered by WinForms. Another option also is to not require any out of band configuration information. If you choose this approach and do not need XML support I would also recommend that you remove the Syste.Xml.DLL reference to your assembly as well. This will save you a lot in runtime memory overhead.

The following is an example of a custom configuration that loads a config file that is assumed to be in the same directory as the exe process.

      Data.cs

      using System;

      using System.Configuration;

      using System.Runtime.InteropServices;

      using System.EnterpriseServices;

      using System.IO;

      using System.Xml;

      using System.Xml.Serialization;

      namespace Norman.Headlam.Sample.NoConfig

      {

      /// <summary>

      /// Summary description for Data class.

      /// </summary>

      [ComVisible(true)]

      [ClassInterface(ClassInterfaceType.AutoDual)]

      public class Data : ServicedComponent, IGetData

      {

      public Data()

      {

      }

      public string GetData()

      {

      MyConfigurationType config = null;

      FileStream fs = null;

      XmlTextReader reader = null;

      XmlSerializer ser = null;

      // get the location of the configuration file

      string configFile = AppDomain.CurrentDomain.SetupInformation.ApplicationBase + "custom.xml";

      if( File.Exists( configFile ) )

      {

      // open the conlig file

      using(fs = new FileStream( configFile, FileMode.Open) )

      {

      try

      {

      // deserialize config file into an object

      reader = new XmlTextReader( fs );

      ser = new XmlSerializer(typeof(MyConfigurationType));

      config = ser.Deserialize( reader ) as MyConfigurationType;

      }

      catch( Exception ex )

      {

      return ex.Message;

      }

      }

      }

      return config.data + configFile;

      }

      }

      }