none
AppDomainSetup.SetConfigurationBytes has no visible effect on configuration...

    Question

  • Per the Library, "The SetConfigurationBytes method provides a way to override the configuration information of an application that creates a new application domain. The configuration file information in value overrides the configuration file information for the application. For example, when the Example.exe application creates a new application domain, it can override the configuration information originally obtained from the Example.exe.config file."

     

    Except... it doesn't. Or at least it doesn't as far as I can tell; I can store an array of byte in an AppDomainSetup and I can get it out again using GetConfigurationBytes, but I can't actually access the config data it represents via System.Configuration.ConfigurationManager and friends. Does anyone have a sample showing this working, or am I doomed to provide dynamic config to my appdomains via the old-skool "write a file to disk" approach?

     

    (if anyone's interested, I can supply a sample of code that *doesn't* work)

    Saturday, September 08, 2007 12:33 AM

Answers

  • It works the way it is documented, it is just not documented very clearly.  You can request documentation improvements and feature additions at Product Feedback.
    Friday, September 14, 2007 4:35 PM
    Moderator

All replies

  • Hi JonK,

    To troubleshoot this issue, we really need the source code to reproduce the problem, so that we can investigate the issue in house. It is not necessary that you send out the complete source of your project. We just need a simplest sample to reproduce the problem. You can remove any confidential information or business logic from it.

    Thanks!

    Monday, September 10, 2007 5:54 AM
  • Well, I've built a minimal repro case (below), and there's some additional oddness. Here's the scenarios:

    Default appdomain configured without custom config section, child appdomain configured with custom config section using config file. Result: everything works as advertised, happiness

    Default appdomain configured with custom config section, child appdomain configured with custom config section using config file. Result: everything works as advertised, happiness

    Default appdomain configured without custom config section, child appdomain configured with custom config section using SetConfigBytes. Result: ConfigurationException, misery

    Default appdomain configured with custom config section, child appdomain configured with custom config section using SetConfigBytes. Result: get default appdomain config values in child appdomain, confusion

    Is this enough to go on?

     

    (also please let me know if something gets mangled in the code-and-config -> markup translation process)

    --

    Jon

    Repro code and config:

    Repro code

    using System;

    using System.Configuration;

    using System.IO;

    using System.Reflection;

    using System.Text;

    namespace ProductConfigChecker

    {

    internal class Program

    {

    private byte[] configBytes = null;

    private string configFileName;

    private static void Main(string[] args)

    {

    Program program = new Program();

    program.Run(args);

    Console.WriteLine("Press <ENTER> to finish");

    Console.ReadLine();

    }

    private static void Usage()

    {

    Console.WriteLine("\nUsage: ConfigChecker [config file]\n");

    }

    private void Run(string[] args)

    {

    if(args.Length != 1) {

    Usage();

    return;

    }

    if(!File.Exists(args[0])) {

    Console.WriteLine("Couldn't find {0}", args[0]);

    return;

    }

    if(!args[0].EndsWith(".config")) {

    Usage();

    return;

    }

    // Uncomment (together with associated chunk of app.config) for more wierdness...

    // Console.WriteLine("");

    // Console.WriteLine("Reading default appdomain configuration");

    // Console.WriteLine("=======================================");

    // Console.WriteLine("");

    // ConfigChecker checker = new ConfigChecker();

    // checker.CheckConfigValid();

    CheckFile(args[0]);

    }

    private void CheckFile(string filename)

    {

    // load the file into memory

    configFileName = filename;

    configBytes = File.ReadAllBytes(filename);

    Console.WriteLine("");

    Console.WriteLine("Checking we can configure child appdomain with .config file");

    Console.WriteLine("===========================================================");

    Console.WriteLine("");

    CheckConfigByWritingConfigFile();

    Console.WriteLine("");

    Console.WriteLine("Checking we can configure child appdomain with byte array");

    Console.WriteLine("=========================================================");

    Console.WriteLine("");

    CheckConfigByWritingConfigBytes();

    }

    private void CheckConfigByWritingConfigFile()

    {

    // create a configured appdomain using a config file and load our config checker in it

    AppDomainSetup setup = new AppDomainSetup();

    setup.ConfigurationFile = configFileName;

    AppDomain configuredAppDomain =

    AppDomain.CreateDomain(

    "configuredUsingFile", AppDomain.CurrentDomain.Evidence, setup);

    ConfigChecker configChecker =

    (ConfigChecker)

    configuredAppDomain.CreateInstanceAndUnwrap(

    Assembly.GetExecutingAssembly().FullName,

    typeof(ConfigChecker).FullName);

    // run the config checker, forwarding configuration exceptions to our exception visualiser

    try {

    configChecker.CheckConfigValid();

    }

    catch(Exception ex) {

    if(ex.InnerException != null && ex.InnerException is ConfigurationErrorsException) {

    Console.WriteLine(ex.InnerException);

    } else if(ex is ConfigurationErrorsException) {

    Console.WriteLine(ex);

    } else {

    throw;

    }

    }

    finally {

    File.Delete(configFileName);

    AppDomain.Unload(configuredAppDomain);

    }

    }

    private void CheckConfigByWritingConfigBytes()

    {

    // create a configured appdomain using a byte[] and load our config checker in it

    AppDomainSetup setup = new AppDomainSetup();

    setup.SetConfigurationBytes(configBytes);

    AppDomain configuredAppDomain =

    AppDomain.CreateDomain(

    "configuredUsingSetConfigurationBytes", AppDomain.CurrentDomain.Evidence, setup);

    ConfigChecker configChecker =

    (ConfigChecker)

    configuredAppDomain.CreateInstanceAndUnwrap(

    Assembly.GetExecutingAssembly().FullName,

    typeof(ConfigChecker).FullName);

    // run the config checker, forwarding configuration exceptions to our exception visualiser

    try {

    configChecker.CheckConfigValid();

    }

    catch(Exception ex) {

    if(ex.InnerException != null && ex.InnerException is ConfigurationErrorsException) {

    Console.WriteLine(ex.InnerException);

    } else if(ex is ConfigurationErrorsException) {

    Console.WriteLine(ex);

    } else {

    throw;

    }

    }

    finally {

    AppDomain.Unload(configuredAppDomain);

    }

    }

    }

    internal class ConfigChecker : MarshalByRefObject

    {

    public void CheckConfigValid()

    {

    AppDomainSetup setup = AppDomain.CurrentDomain.SetupInformation;

    byte[] configBytes = setup.GetConfigurationBytes();

    if(configBytes != null) {

    Console.WriteLine(Encoding.UTF8.GetString(configBytes));

    }

    CustomConfigurationSection section = CustomConfigurationSection.GetSection();

    Console.WriteLine("Name: {0}", section.Name);

    Console.WriteLine(

    "In {0}, {1} is married to {2}", section.DisplayName,

    ConfigurationManager.AppSettings["Husband"],

    ConfigurationManager.AppSettings["Wife"]);

    }

    }

    internal class CustomConfigurationSection : ConfigurationSection

    {

    private static ConfigurationPropertyCollection propertiesCollection;

    static CustomConfigurationSection()

    {

    propertiesCollection = new ConfigurationPropertyCollection();

    name =

    new ConfigurationProperty(

    "name", typeof(string), null, ConfigurationPropertyOptions.IsRequired);

    propertiesCollection.Add(name);

    displayName =

    new ConfigurationProperty("displayName", typeof(string), null);

    propertiesCollection.Add(displayName);

    }

    private static CustomConfigurationSection section;

    private static object sectionLoaderLock = new object();

     

    public static CustomConfigurationSection GetSection()

    {

    return GetSection("customConfiguration");

    }

     

    public static CustomConfigurationSection GetSection(string sectionName)

    {

    if(section == null) {

    lock(sectionLoaderLock) {

    section =

    ConfigurationManager.GetSection(sectionName) as CustomConfigurationSection;

    if(section == null) {

    string message =

    String.Format(

    "The <{0}> section is not defined in your .config file",

    sectionName);

    throw new ConfigurationErrorsException(message);

    }

    }

    }

    return section;

    }

    private static ConfigurationProperty name;

    [ConfigurationProperty("name")]

    public string Name

    {

    get { return (string) base[name]; }

    set { base[name] = value; }

    }

    private static ConfigurationProperty displayName;

    [ConfigurationProperty("displayName")]

    public string DisplayName

    {

    get { return (string) base[displayName]; }

    set { base[displayName] = value; }

    }

    protected override ConfigurationPropertyCollection Properties

    {

    get { return propertiesCollection; }

    }

    }

    }

    Default app.config

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

    <configuration>

    <appSettings>

    <add key="Husband"

    value="Fred"/>

    <add key="Wife"

    value="Wilma"/>

    </appSettings>

    </configuration>

    Alternate.config

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

    <configuration>

    <!-- Configuration Section -->

    <configSections>

    <section name="customConfiguration"

        type="ProductConfigChecker.CustomConfigurationSection, SetConfigurationBytesRepro, Version=1.0.0.0"/>

    </configSections>

    <customConfiguration

    name="Rubble"

    displayName="the Rubble Household" />

    <appSettings>

    <add key="Husband"

    value="Barney"/>

    <add key="Wife"

    value="Betty"/>

    </appSettings>

    </configuration>

    Monday, September 10, 2007 10:34 AM
  • I couldn't get the SetConfigurationBytes to work at all, period. And couldn't find a single working example on the web either. I appreciate that JonK posted such detailed example; I tried for a whole day to get this thing to work, without success.

    Could any one at Microsoft post a simple XML that *works* with SetConfigurationBytes?

    Thank you!


    Tuesday, September 11, 2007 6:47 PM
  • The term "configuration" is pretty heavily overloaded.  The XML set by SetConfigurationBytes() is accessible with AppDomain.GetData("APP_CONFIG_BLOB").  I see that being used in the Rotor source code to override fusion binding policy.  I think.  Very far removed from your own custom configuration.

    To move away from file based configuration, you'd have to write your own SettingsProvider derived provider.  The framework only supplies LocalFileSettingsProvider.  I took a poke at it once but hit the wall pretty hard.  System.Configuration is a miserable namespace.
    Wednesday, September 12, 2007 8:11 PM
    Moderator
  • I don't know if creating my own SettingsProvider will help. Even if I manage to do so, how can I load an assembly in a new AppDomain that instead of getting the settings from a config file instead gets it from my new provider?

    That is, when my dynamically loaded assembly calls

    ConfigurationManager.Settings["blah"]

    it is oblivious to where it comes from (if in an AppDomain that uses LocalFileSettingsProvider, from the config file, if in an AppDomain that uses my new settings provider, then from wherever it defines)

    Why don't Microsoft just get the SetConfigurationBytes working as stated in the documentation? Or at least offer an example of how to use SetConfigurationBytes. Is that so hard?


    Friday, September 14, 2007 3:18 PM
  • It works the way it is documented, it is just not documented very clearly.  You can request documentation improvements and feature additions at Product Feedback.
    Friday, September 14, 2007 4:35 PM
    Moderator
  • I know it doesn't matter, but I disagree. The documentation is clear:

    "The SetConfigurationBytes method provides a way to override the configuration information of an application that creates a new application domain. The configuration file information in value overrides the configuration file information for the application. For example, when the Example.exe application creates a new application domain, it can override the configuration information originally obtained from the Example.exe.config file."

    And that is *exactly* what is needed.



    Friday, September 14, 2007 6:05 PM
  • Just out of curiosity, why do you need this to work the way you expected it to work?  You could put custom config in the app.config file for the new domain.  Or use AppDomainSetup.AppDomainInitializerArguments to do it at runtime.
    Friday, September 14, 2007 6:55 PM
    Moderator
  • Sure.

    I have a "method broker" that dynamically loads a .NET dll and calls a method from a class in it. The class, assembly, function, etc, are all configured in a database. Each assembly expects certain settings in a config file, but I dont't want to create one static config file for each one, I want to read them from the database as well, prepare the settings (as they would be in a config file), call SetConfigurationBytes to establish them and finally call the method. At some point we are planning to store the DLL itself in a blob column.

    This way configuration can be changed through the database, instead of requiring the system admin to go through the perils of editing config files by hand, as well as allowing me to store sensitive configuration information in a database, and run decryption routines for some configuration items. The flexibility that would give us is what we are looking for - we are not at this point concerned with how fast this will / will not run.

    If I do as you suggest, I will end up with either one humongous config file (in which case there will be no loading of new AppDomains) or one config file per dll, which is not good either (I've been blasted by many sys admins because I'm storing database connection information in the config file).

    AppDomainInitializerArguments allows me to send some string[] to the callback called when the appdomain is created, but the dll loaded under that new domain has no way (that I know of) of accessing such values, and they woudnt't mean much for them anyway.

    Typical case: you want to launch a dll, set its app settings connection string from whatever is set up in the broker database and let it run. When the sys admin wants to change the password, she  would be able to change it in the database, hopefully using an admin util we planned to build.

    Thank you for your attention.

    Otavio

    Friday, September 14, 2007 7:13 PM
  • Similarly.

     

    My scenario is that I'm creating the configuration for the (many) child appdomains on the fly by merging various sources together, and the workaround I'm going with is to merge the various configuration sources and then serialise to a temporary file and use that to configure the child appdomain and, as mentioned, this works quite nicely.

     

    However, the serialisation to disk followed by immediate deserialisation feels both wasteful and clumsy, and it leaves me needing to track the temporary file so I can reliably delete it once the appdomain's torn down. Being able to configure the appdomain with a blob of data in memory rather than on disk seems like a very obvious win and, to be frank, I'm of the opinion that someone's taken their eye off the ball here - it should be as simple for the configuration manager to construct a graph of config objects by reading from a MemoryStream constructed on top of a byte[] as it is to read it from a Stream backed by a file on disk.

     

    According to Pratschner ("Customizing the Microsoft .NET Framework Common Language Runtime", Chapter 6):

     

    "The ConfigurationBytes property enables you to specify your configuration file as an array of bytes instead of by supplying the name of a configuration file on disk. The byte array you pass to ConfigurationBytes is simply the contents of your XML configuration file laid out in memory. Specifying a configuration file with ConfigurationBytes is convenient for scenarios in which you generate configuration information dynamically instead of storing the data ahead of time in a disk file."

     

    If I can dig out one of the Fx2.0 beta release I'm going to stick it in a VM and see if this scenario ever worked as described...

    Friday, September 14, 2007 10:04 PM
  • Well, it seems like there are valid reasons to use SetConfigurationBytes as stated in the documentation, but It is frustrating  that there is no one at Microsoft willing to simply say "it doesn't work as documented" or ok, we are fixing that in service pack xyz. Saying that the documentation is wrong is just not acceptable.


    Wednesday, September 19, 2007 2:31 PM
  • As I indicated before, if you want to talk to a Microsoft engineer, you'll have to post to Product Feedback or call Microsoft Support.  This community support forum can only tell you how it is implemented, we don't get to change the software.
    Wednesday, September 19, 2007 3:00 PM
    Moderator
  • nobugz -

    Hey, I'd appreciate it if you could deliver on your statement. How exactly IS it implemented? Saying that "it's not documented clearly" STILL does not describe what it does.
    Thursday, November 06, 2008 2:47 PM