dotnet / runtime

.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.
https://docs.microsoft.com/dotnet/core/
MIT License
15.35k stars 4.74k forks source link

machine.config for .NET Core #32307

Open eerhardt opened 4 years ago

eerhardt commented 4 years ago

In the .NET Framework, System.Configuration allowed a machine.config file that would apply globally to every .NET process on the machine.

https://docs.microsoft.com/en-us/dotnet/framework/configure-apps/#machine-configuration-files

This made sense, because you could configure the framework using this file, and it would apply for all processes.

In .NET Core this concept makes much less sense.

  1. Nothing in the framework is configured using System.Configuration. Instead, we use runtimeconfig.json.
  2. With the advent of self-contained applications and multiple "hive" installations, the concept of a config file that applies to the whole machine doesn't make sense. A self-contained application should run the same no matter if some global "machine.config" file exists or not.

Today in .NET Core, when you use the System.Configuration.ConfigurationManager package, it will still try to load a machine.config file, however the location it probes for this file is relative to the current application (using AppDomain.CurrentDomain.BaseDirectory):

https://github.com/dotnet/runtime/blob/571f972a5b1c7821870b1111bcc64726740957df/src/libraries/System.Configuration.ConfigurationManager/src/System/Configuration/ClientConfigurationHost.cs#L43-L56

(NOTE: For .NET Core 2.0, the code still used the old .NET Framework implementation of calling RuntimeEnvironment.GetRuntimeDirectory() to get the directory. This was changed in 2.1 to use BaseDirectory with https://github.com/dotnet/corefx/pull/20488.)

Typically that file never exists. When if it doesn't ConfigurationManager will load a default/hard-coded machine.config:

https://github.com/dotnet/runtime/blob/4107a4cf5dda28c815f78dc962e0d1e7286676cc/src/libraries/System.Configuration.ConfigurationManager/src/System/Configuration/ImplicitMachineConfigHost.cs#L75-L95

The problem with the current behavior comes when AppDomain.CurrentDomain.BaseDirectory doesn't return a path. For example, when using a custom native host - see #25027 - BaseDirectory returns null. In this situation, ConfigurationManager tries to load a Config/machine.config stream. And since this path isn't rooted, it tries to issue a WebRequest for this stream:

https://github.com/dotnet/runtime/blob/4107a4cf5dda28c815f78dc962e0d1e7286676cc/src/libraries/System.Configuration.ConfigurationManager/src/System/Configuration/ClientConfigurationHost.cs#L281-L299

WebClient is smart enough to realize this is a request for a file, tries to load the file - relative to the current working directory, which is probably not good from a security perspective. When the file doesn't exist it throws a few 1st chance exceptions, which are caught and then null is returned.

Proposal

We should just cut support for reading a machine.config file completely from ConfigurationManager on .NET Core and always return the default/hard-coded machine.config contents. No need to probe for files that don't exist. No need to issue a WebRequest and catch exceptions either.

All settings can be overridden by the App.config. And since we are only probing the app's directory (or the current working directory if using a native host) it doesn't make sense to split the configuration across multiple files in the app's directory.

/cc @ericstj @JeremyKuhne @maryamariyan @safern

JeremyKuhne commented 4 years ago

I spaced on the change away from RuntimeEnvironment.GetRuntimeDirectory(). :( machine.config is really runtime.config, but there was only ever one instance of the runtime until Core.

The idea here was that you could still set for whichever runtime instance that you were using. You could trump the settings completely, which isn't possible if we load the implicit one. I don't recall if you can override the config sections from the higher-level config, but I think the answer is no. If so, this can potentially be problematic when you take user config files into account.

eerhardt commented 4 years ago

@JeremyKuhne - I'm not sure I follow your response. Do you agree with the proposal to remove trying to probe for a machine.config file on .NET Core?

If we shouldn't remove it, what do you propose we do instead? I don't think we should probe the RuntimeEnvironment.GetRuntimeDirectory() directory because we will never find that file there. Probing the AppDomain.BaseDirectory for Config\machine.config, like we are doing now, doesn't really make sense to me either. Are you saying this is how someone would override our implicit machine.config file, if they need to?

JeremyKuhne commented 4 years ago

Are you saying this is how someone would override our implicit machine.config file, if they need to?

Yes, the need to trump it so you can inject in front of the user.config is important. I'm ok with making the probe app-specific, but I think we still need it.

jkotas commented 4 years ago

Yes, the need to trump it so you can inject in front of the user.config is important.

Do we know about anybody who is using this override for real app?

ericstj commented 4 years ago

I don't believe anyone is using this if we can absorb this breaking change in 5.0.0 that seems best to do now (rather than do it in 6.0.0 which we expect to move to LTS). If it breaks anyone they can revert to an earlier package and let us know they need it and we can bring it back.

ericstj commented 4 years ago

@safern I don't think this meets the bar at this point in 5.0.0. Moving out.

habibcs commented 3 years ago

We have a system with lot of services/APIs with .NET 461 .NET472 and have a huge dependency on the machine level config files. We now gradually adding newer APIs with .NET Core 31 and .NET5.

I do not find any consistent solution, especially every new version update, changes the path to the runtime/sdk.

Therefore I resort to defining the global config location for our middleware (hundreds of) services/APIs. And in those NetCore31/Net5 services we manually load from that global location (via environment variable). And then feed the path to load config via something like ConfigurationManager.OpenMappedMachineConfiguration()

This is our approach. If this is more or less the standard or the right way, then skipping .NET dependency completely away from Machine config should be taken and highlighted clearly in the documentation.

Thanks, Habib

krwq commented 3 years ago

[Triage] Seems it's non essential for 6.0.

khurshid-alam commented 1 year ago

Can anyone answer Where is the machine.config for linux ?

I wanted to set

<system.web>
    <processModel maxWorkerThreads="100" maxIoThreads="100" minWorkerThreads="50"/>
    <httpRuntime minFreeThreads="704" minLocalRequestFreeThreads="608"/>
</system.web>

putting machine.config in the root directory doesn't seem to have worked. We only have web.config in root directory.

We run it as dotnet app.dll --urls=http://0.0.0.0:5000

jkotas commented 1 year ago

I assume that you are using ASP.NET Core. machine.config and web.config is not used by ASP.NET Core. ASP.NET Core uses different configuration system, documented at https://learn.microsoft.com/en-us/aspnet/core/fundamentals/configuration