Can I use / access the app.config from .net code,

2019-01-25 13:09发布

问题:

I have an existing set of .net libraries that I wish to call from Excel VBA (that part is working fine). These libraries rely on settings in the app.config. I know I can enter these settings in a excel.exe.config file (placed in the same directory as the excel.exe), but this doesn't really seem like a very manageable solution to me, as I can see causing conflicts if more than one application wants to do this.

My question is simple: is there any way of COM exposed dlls referring to their respective config files?

Sample app.config:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <appSettings>
    <add key="test" value="Hello world!" />
  </appSettings>
</configuration>

Sample c#:

namespace ExcelVbaFacingCode
{
    public class SimpleAppSettingReader
    {
        public string GetValue(string key)
        {
            return ConfigurationManager.AppSettings[key];
        }
    }
}

Sample VBA:

Public Sub Test()
    Dim Test As New SimpleAppSettingReader
    Dim sampleValue As String
    sampleValue = Test.GetValue("test")
    MsgBox "Value: '" + sampleValue + "'", vbOKOnly
End Sub

回答1:

You can indeed specify the configuration file that will be used by the .NET configuration API's but you have to do it before the AppDomain is created (which is not as easy as it sounds.) This is how ASP.NET works for example. It sets up the AppDomain to use a "web.config" file located in the virtual directory as opposed to requiring an w3p.exe.config or what not.

You can do this too but you need to create a secondary AppDomain. This in itself is a good idea for a lot of reasons that have to do with multiple add-ins and assembly references. But one of the things you can put in the AppDomain's setup is the name/path of the "app.config" file.

To create the new AppDomain you can use a "shim" which is a bit of boilerplate C++ code that configures and starts the AppDomain and the rest of the code from that point on can be managed code.

There's an article on MSDN written by Andrew Whitechapel and called Isolating Office Extensions with the COM Shim Wizard. I won't lie, it's not a trivial concept, but it's also not too bad. There's a wizard that creates the C++ project for you which loads the .NET addin into a new AppDomain. The configuration of the AppDomain must be done before it is loaded so you'll want to put the following in the wizard-created code.

// CLRLoader.cpp
HRESULT CCLRLoader::CreateLocalAppDomain()
{

    ... snip ...

    // Configure the AppDomain to use a specific configuration file
    pDomainSetup->put_ConfigurationFile(CComBSTR(szConfigFile));

    ... snip ...

}

It's also worth noting that if/when you convert to a VSTO project, the VSTO runtime does all this AppDomain configuration for you. Each VSTO addin runs in its own AppDomain with its own private app.config file.



回答2:

Whenever ConfigurationManager.AppSettings is called, it automatically opens the configuration file of the executing assembly. In your case, Excel is the executing application, so you cannot open the config file of the .Net DLL.

This was done for a reason. A single DLL can be called from multiple types of application, each of which might have different requirements (and possibly different databases, for example). For this reason, the application specifies the settings, not the referenced assembly.

I think that what you already suggested is the best way to do this. I know it seems like you are copying your config file, and that it means that you have duplicates out there to worry about when code changes, but its the best thing to do.



回答3:

I was facing similar problems with web.config.... I find an interesting solution. You can capsulate configuration reading function, eg. something like this:

public class MyClass {

public static Func<string, string> GetConfigValue = s => ConfigurationManager.AppSettings[s];

//...

}

And then normally use

string connectionString = MyClass.GetConfigValue("myConfigValue");

but in a special case first "override" the function like this:

MyClass.GetConfigValue = s =>  s == "myConfigValue" ? "Hi", "string.Empty";

More about it:

http://rogeralsing.com/2009/05/07/the-simplest-form-of-configurable-dependency-injection/



回答4:

Try:

ConfigurationManager.OpenExeConfiguration("foo.config");


回答5:

As I understand it, you want the configuration to be global to the machine where the dll is installed. There is a machine.config file for machine global configuration options. I have never used it myself, so I don't have any more detailed information, but it might be worth looking into. http://msdn.microsoft.com/en-us/library/ms229697(VS.71).aspx. A Google search for "machine.config" gives more hits which look informative.

Another way is to put a custom xml file in the installation directory of the dll file. To get the path to the file, use System.Reflection.Assembly.GetExecutingAssembly.Location (or maybe CodeBase instead of Location). Unfortunately you will have to build your own config management on top of the xml file, but thanks to Linq to XML I think that XML is more painless than ever.



回答6:

There is another way to load .config files from COM components that I have been using for several years.

It works like this:

  • Open dcomcnfg.exe
  • Create a new COM+ application
  • Enter the folder with your com object in "Application Root Directory" on the "Activation" tab
  • Add your com object to the application
  • Put a file named "Application.manifest" in the folder. The file must contain:

    <?xml version="1.0" encoding="UTF-8" standalone="yes"?> <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0" />

  • Put your config file in the same folder. The name of the config file must be "Application.config", i.e. not "myapp.config"

You can use all normal tags in application.config (e.g. runtime, system.diagnostics etc)


I use a vbscript to create the COM+ application:

Dim allapps, newapp
Set allapps = objCatalog.GetCollection("Applications")

Set newapp = allapps.Add()
newapp.Value("Name") = MyComAppName
newapp.Value("ApplicationAccessChecksEnabled") = False 
newapp.Value("Activation") = 1 '  own process
newapp.Value("ApplicationDirectory") = WScript.CreateObject("WScript.Shell").CurrentDirectory
allapps.SaveChanges
WScript.Echo "Created application " & ComAppName

objCatalog.ImportComponent ComAppName, MyComName
WScript.Echo "Installed component " & MyComName