Allow ConfigurationManager to load when GetEntryAssembly returns null. (#32195)
authorEric Erhardt <eric.erhardt@microsoft.com>
Mon, 24 Feb 2020 22:52:08 +0000 (16:52 -0600)
committerGitHub <noreply@github.com>
Mon, 24 Feb 2020 22:52:08 +0000 (16:52 -0600)
* 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

src/libraries/System.Configuration.ConfigurationManager/src/System/Configuration/ClientConfigPaths.cs
src/libraries/System.Configuration.ConfigurationManager/tests/System.Configuration.ConfigurationManager.Tests.csproj
src/libraries/System.Configuration.ConfigurationManager/tests/System/Configuration/CustomHostTests.cs [new file with mode: 0644]

index 1d37a0d..b6523ee 100644 (file)
@@ -2,9 +2,11 @@
 // The .NET Foundation licenses this file to you under the MIT license.
 // See the LICENSE file in the project root for more information.
 
+using System.Diagnostics;
 using System.Globalization;
 using System.IO;
 using System.Reflection;
+using System.Runtime.InteropServices;
 using System.Security;
 
 namespace System.Configuration
@@ -32,7 +34,6 @@ namespace System.Configuration
             _includesUserConfig = includeUserConfig;
 
             Assembly exeAssembly = null;
-            string applicationFilename = null;
 
             if (exePath != null)
             {
@@ -42,39 +43,42 @@ namespace System.Configuration
                 {
                     throw ExceptionUtil.ParameterInvalid(nameof(exePath));
                 }
-
-                applicationFilename = ApplicationUri;
             }
             else
             {
                 // Exe path wasn't specified, get it from the entry assembly
                 exeAssembly = Assembly.GetEntryAssembly();
 
-                if (exeAssembly == null)
-                    throw new PlatformNotSupportedException();
-
-                HasEntryAssembly = true;
+                if (exeAssembly != null)
+                {
+                    HasEntryAssembly = true;
 
-                // The original .NET Framework code tried to get the local path without using Uri.
-                // If we ever find a need to do this again be careful with the logic. "file:///" is
-                // used for local paths and "file://" for UNCs. Simply removing the prefix will make
-                // local paths relative on Unix (e.g. "file:///home" will become "home" instead of
-                // "/home").
-                string configBasePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, exeAssembly.ManifestModule.Name);
-                Uri uri = new Uri(configBasePath);
+                    // The original .NET Framework code tried to get the local path without using Uri.
+                    // If we ever find a need to do this again be careful with the logic. "file:///" is
+                    // used for local paths and "file://" for UNCs. Simply removing the prefix will make
+                    // local paths relative on Unix (e.g. "file:///home" will become "home" instead of
+                    // "/home").
+                    string configBasePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, exeAssembly.ManifestModule.Name);
+                    Uri uri = new Uri(configBasePath);
 
-                if (uri.IsFile)
-                {
+                    Debug.Assert(uri.IsFile);
                     ApplicationUri = uri.LocalPath;
-                    applicationFilename = uri.LocalPath;
                 }
                 else
                 {
-                    ApplicationUri = Uri.EscapeDataString(configBasePath);
+                    // An EntryAssembly may not be found when running from a custom host.
+                    // Try to find the native entry point.
+                    using (Process currentProcess = Process.GetCurrentProcess())
+                    {
+                        ApplicationUri = currentProcess.MainModule?.FileName;
+                    }
                 }
             }
 
-            ApplicationConfigUri = ApplicationUri + ConfigExtension;
+            if (!string.IsNullOrEmpty(ApplicationUri))
+            {
+                ApplicationConfigUri = ApplicationUri + ConfigExtension;
+            }
 
             // In the case when exePath was explicitly supplied, we will not be able to
             // construct user.config paths, so quit here.
@@ -84,7 +88,7 @@ namespace System.Configuration
             if (!_includesUserConfig) return;
 
             bool isHttp = StringUtil.StartsWithOrdinalIgnoreCase(ApplicationConfigUri, HttpUri);
-            SetNamesAndVersion(applicationFilename, exeAssembly, isHttp);
+            SetNamesAndVersion(exeAssembly, isHttp);
             if (isHttp) return;
 
             // Create a directory suffix for local and roaming config of three parts:
@@ -227,7 +231,7 @@ namespace System.Configuration
             return suffix;
         }
 
-        private void SetNamesAndVersion(string applicationFilename, Assembly exeAssembly, bool isHttp)
+        private void SetNamesAndVersion(Assembly exeAssembly, bool isHttp)
         {
             Type mainType = null;
 
index 6d59c7b..ae77499 100644 (file)
@@ -78,6 +78,7 @@
     <Compile Include="System\Configuration\ConfigurationElementCollectionTests.cs" />
     <Compile Include="System\Configuration\ConfigurationElementTests.cs" />
     <Compile Include="System\Configuration\ConfigurationPropertyAttributeTests.cs" />
+    <Compile Include="System\Configuration\CustomHostTests.cs" />
     <Compile Include="System\Configuration\ConfigurationPropertyTests.cs" />
     <Compile Include="System\Configuration\ConfigurationTests.cs" />
     <Compile Include="System\Configuration\BasicCustomSectionTests.cs" />
diff --git a/src/libraries/System.Configuration.ConfigurationManager/tests/System/Configuration/CustomHostTests.cs b/src/libraries/System.Configuration.ConfigurationManager/tests/System/Configuration/CustomHostTests.cs
new file mode 100644 (file)
index 0000000..11d1add
--- /dev/null
@@ -0,0 +1,46 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Reflection;
+using System.Runtime.InteropServices;
+using Microsoft.DotNet.RemoteExecutor;
+using Xunit;
+
+namespace System.Configuration.Tests
+{
+    /// <summary>
+    /// Tests ConfigurationManager works even when Assembly.GetEntryAssembly() returns null.
+    /// </summary>
+    public class CustomHostTests
+    {
+        [Fact]
+        [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework, "Does not apply to .NET Framework.")]
+        public void FilePathIsPopulatedCorrectly()
+        {
+            RemoteExecutor.Invoke(() =>
+            {
+                MakeAssemblyGetEntryAssemblyReturnNull();
+
+                string expectedFilePathEnding = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ?
+                    "dotnet.exe.config" :
+                    "dotnet.config";
+
+                Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
+                Assert.EndsWith(expectedFilePathEnding, config.FilePath);
+            }).Dispose();
+        }
+
+        /// <summary>
+        /// Makes Assembly.GetEntryAssembly() return null using private reflection.
+        /// </summary>
+        private static void MakeAssemblyGetEntryAssemblyReturnNull()
+        {
+            typeof(Assembly)
+                .GetField("s_forceNullEntryPoint", BindingFlags.NonPublic | BindingFlags.Static)
+                .SetValue(null, true);
+
+            Assert.Null(Assembly.GetEntryAssembly());
+        }
+    }
+}