Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ConfigurationManager is unusable when called from unmanaged code #25027

Closed
Cronan opened this issue Feb 13, 2018 · 8 comments · Fixed by #32195
Closed

ConfigurationManager is unusable when called from unmanaged code #25027

Cronan opened this issue Feb 13, 2018 · 8 comments · Fixed by #32195

Comments

@Cronan
Copy link
Contributor

Cronan commented Feb 13, 2018

OS: RH Linux
Framework: .Net Core 2.0

I have a .dll compiled on windows in .net 4.5.2 and written in C#, called from Linux.
I use the code below to inject an app.config file into the system, like so:

var config_proxy = new ConfigurationProxy("my.dll");
config_proxy.InjectToConfigurationManager();

However, when run in Linux, I get the following exception:

"Operation is not supported on this platform."
"   at System.Configuration.ClientConfigPaths..ctor(String exePath, Boolean includeUserConfig)
   at System.Configuration.ClientConfigPaths.GetPaths(String exePath, Boolean includeUserConfig)
   at System.Configuration.ClientConfigurationHost.get_ConfigPaths()
   at System.Configuration.ClientConfigurationHost.GetStreamName(String configPath)
   at System.Configuration.ClientConfigurationHost.get_IsAppConfigHttp()
   at System.Configuration.ClientConfigurationSystem..ctor()
   at System.Configuration.ConfigurationManager.EnsureConfigurationSystem()"

I've looked at the source here, and can see where the throw is, but can't figure out how to get it working ...

public sealed class ConfigurationProxy : IInternalConfigSystem
{
    Configuration config;
    Dictionary<string, IConfigurationSectionHandler> customSections;

   public ConfigurationProxy(string fileName)
    {
        customSections = new Dictionary<string, IConfigurationSectionHandler>();

        if (!Load(fileName))
            throw new ConfigurationErrorsException(string.Format(
                "File: {0} could not be found or was not a valid cofiguration file.",
                config.FilePath));
    }

    private bool Load(string file)
    {
       config = ConfigurationManager.OpenExeConfiguration(file);
       return config.HasFile;
    }

    public Configuration Configuration
    {
        get { return config; }
    }

    #region IInternalConfigSystem Members

    public object GetSection(string configKey)
    {
        if (configKey == "appSettings")
            return BuildAppSettings();

        object sect = config.GetSection(configKey);

        if (customSections.ContainsKey(configKey) && sect != null)
        {
            var xml = new XmlDocument();

            xml.LoadXml(((ConfigurationSection)sect).SectionInformation.GetRawXml());
            sect = customSections[configKey].Create(config,
                config.EvaluationContext,
                xml.FirstChild);
        }

        return sect;
    }

    public void RefreshConfig(string sectionName)
    {
        Load(config.FilePath);
    }

    public bool SupportsUserConfig
    {
        get { return false; }
    }

    #endregion

    private NameValueCollection BuildAppSettings()
    {
        var coll = new NameValueCollection();

        foreach (var key in config.AppSettings.Settings.AllKeys)
            coll.Add(key, config.AppSettings.Settings[key].Value);

        return coll;
    }

    public bool InjectToConfigurationManager()
    {
        var configSystem = typeof(ConfigurationManager).GetField("s_configSystem",
            BindingFlags.Static | BindingFlags.NonPublic);

        configSystem.SetValue(null, this);

        if (ConfigurationManager.AppSettings.Count == config.AppSettings.Settings.Count)
            return true;

        return false;
    }
}
@Cronan
Copy link
Contributor Author

Cronan commented Feb 13, 2018

I also experimented with using mapped config, e.g.:

var map = new ExeConfigurationFileMap { ExeConfigFilename = file };
config = ConfigurationManager.OpenMappedExeConfiguration(map, ConfigurationUserLevel.None);

called via this:

var config_proxy = new ConfigurationProxy("my.dll.config");
config_proxy.InjectToConfigurationManager();

but with the same exception.

@Cronan Cronan changed the title Exception using IInternalConfigSystem to override app.config Exception using IInternalConfigSystem to inject config Feb 13, 2018
@Cronan
Copy link
Contributor Author

Cronan commented Feb 14, 2018

@joshfree Is this the same issue as #21246?
Assembly.GetEntryAssembly() is returning null - I'm hosting coreclr from unmanaged code in Linux.

@Cronan Cronan changed the title Exception using IInternalConfigSystem to inject config ConfigurationManager is unusable when called from unmanaged code Feb 15, 2018
@JeremyKuhne
Copy link
Member

@Cronan I'm not sure what the expectation is when hosting CoreCLR as to what Assembly.GetEntryAssembly() should return. @jkotas Do you have any idea on that part?

Were you trying to hit other statics on ConfigurationManager? When you're using OpenMappedExeConfiguration it doesn't impact the static entry points into ConfigurationManager. You'd want to get the AppSettings off of the Configuration object returned from the OpenMapped... method. That is, btw, the only real way to have consistency with custom locations on both desktop and core (desktop injection was done through AppDomain initialization, which we don't have in Core). Pulling from a static Configuration (or AppSettings) you control can also facilitate testing- in general it is the pattern I'd recommend.

If you can't even use the returned Configuration object I'd be interested in seeing the exact stack you get in that case.

@jkotas
Copy link
Member

jkotas commented Mar 20, 2018

When hosting CoreCLR, Assembly.GetEntryAssembly() will return the assembly that coreclr_execute_assembly was called on. It will return null if coreclr_execute_assembly was not called.

https://docs.microsoft.com/en-us/dotnet/api/system.reflection.assembly.getentryassembly?view=netcore-2.0#remarks talks about this.

@Cronan
Copy link
Contributor Author

Cronan commented Mar 20, 2018

@jkotas @JeremyKuhne I think Assembly.GetEntryAssembly() is behaving correctly, given the circumstances, but my expectation is that I'm still able to use ConfigurationManager, even when called from unmanaged code, especially when providing the full path to the .config file.

In my code I'm just calling OpenExeConfiguration or OpenMappedExeConfiguration, not using other statics. The returned Configuration throws any time you touch anything that needs to call PrepareConfigSystem, e.g. GetSection(sectionName);

@ericstj
Copy link
Member

ericstj commented Jan 9, 2020

We should consider a fix here, or at least don't throw in the case we cannot locate a config file when run from a host.

@msftgits msftgits transferred this issue from dotnet/corefx Jan 31, 2020
@msftgits msftgits added this to the 5.0 milestone Jan 31, 2020
@danmoseley
Copy link
Member

@eerhardt any chance you could take a look at this one also? it is blocking an investigation that @AArnott is performing. He's targeting Windows though.

@danmoseley danmoseley added this to Proposed/Not Committed in .NET Core impacting internal partners via automation Feb 3, 2020
@eerhardt
Copy link
Member

eerhardt commented Feb 5, 2020

Looking. I'll let you know what I find out.

@eerhardt eerhardt self-assigned this Feb 6, 2020
eerhardt added a commit to eerhardt/runtime that referenced this issue Feb 22, 2020
On Windows, attempt to find the native host to maintain behavior from netfx.

Fix dotnet#25027
.NET Core impacting internal partners automation moved this from Proposed/Not Committed to Done Feb 24, 2020
eerhardt added a commit that referenced this issue Feb 24, 2020
#32195)

* Clean up unused variable in ClientConfigPaths.

* Allow ConfigurationManager to load when GetEntryAssembly returns null.

Attempt to find the native host to maintain behavior from netfx.

Fix #25027
@dotnet dotnet locked as resolved and limited conversation to collaborators Dec 18, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
No open projects
Development

Successfully merging a pull request may close this issue.

7 participants