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

Allow ConfigurationManager to load when GetEntryAssembly returns null. #32195

Merged
merged 6 commits into from
Feb 24, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -32,7 +34,6 @@ private ClientConfigPaths(string exePath, bool includeUserConfig)
_includesUserConfig = includeUserConfig;

Assembly exeAssembly = null;
string applicationFilename = null;

if (exePath != null)
{
Expand All @@ -42,39 +43,42 @@ private ClientConfigPaths(string exePath, bool includeUserConfig)
{
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.
eerhardt marked this conversation as resolved.
Show resolved Hide resolved
// 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.
Expand All @@ -84,7 +88,7 @@ private ClientConfigPaths(string exePath, bool includeUserConfig)
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:
Expand Down Expand Up @@ -227,7 +231,7 @@ private static string GetTypeAndHashSuffix(string exePath)
return suffix;
}

private void SetNamesAndVersion(string applicationFilename, Assembly exeAssembly, bool isHttp)
private void SetNamesAndVersion(Assembly exeAssembly, bool isHttp)
{
Type mainType = null;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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" />
Expand Down
Original file line number Diff line number Diff line change
@@ -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());
}
}
}