Skip to content

Commit

Permalink
FileConfigurationProvider.Dispose should dispose FileProvider when it…
Browse files Browse the repository at this point in the history
… ows it (#86455)

fixes #86146
  • Loading branch information
adamsitnik committed May 19, 2023
1 parent 8967844 commit 63fad3c
Show file tree
Hide file tree
Showing 5 changed files with 142 additions and 6 deletions.
Expand Up @@ -29,6 +29,9 @@ public static IConfigurationBuilder SetFileProvider(this IConfigurationBuilder b
return builder;
}

internal static IFileProvider? GetUserDefinedFileProvider(this IConfigurationBuilder builder)
=> builder.Properties.TryGetValue(FileProviderKey, out object? provider) ? (IFileProvider)provider : null;

/// <summary>
/// Gets the default <see cref="IFileProvider"/> to be used for file-based providers.
/// </summary>
Expand All @@ -38,12 +41,7 @@ public static IFileProvider GetFileProvider(this IConfigurationBuilder builder)
{
ThrowHelper.ThrowIfNull(builder);

if (builder.Properties.TryGetValue(FileProviderKey, out object? provider))
{
return (IFileProvider)provider;
}

return new PhysicalFileProvider(AppContext.BaseDirectory ?? string.Empty);
return GetUserDefinedFileProvider(builder) ?? new PhysicalFileProvider(AppContext.BaseDirectory ?? string.Empty);
}

/// <summary>
Expand Down
Expand Up @@ -162,6 +162,11 @@ private void HandleException(ExceptionDispatchInfo info)
protected virtual void Dispose(bool disposing)
{
_changeTokenRegistration?.Dispose();

if (Source.OwnsFileProvider)
{
(Source.FileProvider as IDisposable)?.Dispose();
}
}
}
}
Expand Up @@ -18,6 +18,11 @@ public abstract class FileConfigurationSource : IConfigurationSource
/// </summary>
public IFileProvider? FileProvider { get; set; }

/// <summary>
/// Set to true when <see cref="FileProvider"/> was not provided by user and can be safely disposed.
/// </summary>
internal bool OwnsFileProvider { get; private set; }

/// <summary>
/// The path to the file.
/// </summary>
Expand Down Expand Up @@ -58,6 +63,11 @@ public abstract class FileConfigurationSource : IConfigurationSource
/// <param name="builder">The <see cref="IConfigurationBuilder"/>.</param>
public void EnsureDefaults(IConfigurationBuilder builder)
{
if (FileProvider is null && builder.GetUserDefinedFileProvider() is null)
{
OwnsFileProvider = true;
}

FileProvider ??= builder.GetFileProvider();
OnLoadException ??= builder.GetFileLoadExceptionHandler();
}
Expand All @@ -81,6 +91,7 @@ public void ResolveFileProvider()
}
if (Directory.Exists(directory))
{
OwnsFileProvider = true;
FileProvider = new PhysicalFileProvider(directory);
Path = pathToFile;
}
Expand Down
Expand Up @@ -4,8 +4,10 @@
using System;
using System.Globalization;
using System.IO;
using System.Linq;
using Microsoft.Extensions.Configuration.Json;
using Microsoft.Extensions.Configuration.Test;
using Microsoft.Extensions.FileProviders;
using Xunit;

namespace Microsoft.Extensions.Configuration
Expand Down Expand Up @@ -219,5 +221,64 @@ public void ThrowFormatExceptionWhenFileIsEmpty()
var exception = Assert.Throws<FormatException>(() => LoadProvider(@""));
Assert.Contains("Could not parse the JSON file.", exception.Message);
}

[Theory]
[InlineData(true)]
[InlineData(false)]
public void AddJsonFile_FileProvider_Gets_Disposed_When_It_Was_Not_Created_By_The_User(bool disposeConfigRoot)
{
string filePath = Path.Combine(Path.GetTempPath(), $"{nameof(AddJsonFile_FileProvider_Gets_Disposed_When_It_Was_Not_Created_By_The_User)}.json");
File.WriteAllText(filePath, @"{ ""some"": ""value"" }");

IConfigurationRoot config = new ConfigurationBuilder().AddJsonFile(filePath, optional: false).Build();
JsonConfigurationProvider jsonConfigurationProvider = config.Providers.OfType<JsonConfigurationProvider>().Single();

Assert.NotNull(jsonConfigurationProvider.Source.FileProvider);
PhysicalFileProvider fileProvider = (PhysicalFileProvider)jsonConfigurationProvider.Source.FileProvider;
Assert.False(GetIsDisposed(fileProvider));

if (disposeConfigRoot)
{
(config as IDisposable).Dispose(); // disposing ConfigurationRoot
}
else
{
jsonConfigurationProvider.Dispose(); // disposing JsonConfigurationProvider
}

Assert.True(GetIsDisposed(fileProvider));
}

[Fact]
public void AddJsonFile_FileProvider_Is_Not_Disposed_When_It_Is_Owned_By_The_User()
{
string filePath = Path.Combine(Path.GetTempPath(), $"{nameof(AddJsonFile_FileProvider_Is_Not_Disposed_When_It_Is_Owned_By_The_User)}.json");
File.WriteAllText(filePath, @"{ ""some"": ""value"" }");

PhysicalFileProvider fileProvider = new(Path.GetDirectoryName(filePath));
JsonConfigurationProvider configurationProvider = new(new JsonConfigurationSource()
{
Path = filePath,
FileProvider = fileProvider
});
IConfigurationRoot config = new ConfigurationBuilder().AddJsonFile(configurationProvider.Source.FileProvider, filePath, optional: true, reloadOnChange: false).Build();

Assert.False(GetIsDisposed(fileProvider));

(config as IDisposable).Dispose(); // disposing ConfigurationRoot that does not own the provider
Assert.False(GetIsDisposed(fileProvider));

configurationProvider.Dispose(); // disposing JsonConfigurationProvider that does not own the provider
Assert.False(GetIsDisposed(fileProvider));

fileProvider.Dispose(); // disposing PhysicalFileProvider itself
Assert.True(GetIsDisposed(fileProvider));
}

private static bool GetIsDisposed(PhysicalFileProvider fileProvider)
{
System.Reflection.FieldInfo isDisposedField = typeof(PhysicalFileProvider).GetField("_disposed", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);
return (bool)isDisposedField.GetValue(fileProvider);
}
}
}
Expand Up @@ -3,11 +3,13 @@

using System;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Security.Cryptography.Xml;
using System.Tests;
using System.Xml;
using Microsoft.Extensions.Configuration.Test;
using Microsoft.Extensions.FileProviders;
using Xunit;

namespace Microsoft.Extensions.Configuration.Xml.Test
Expand Down Expand Up @@ -779,5 +781,64 @@ public void LoadKeyValuePairsFromValidEncryptedXml()
Assert.Equal("AnotherTestConnectionString", xmlConfigSrc.Get("data.setting:inventory:connectionstring"));
Assert.Equal("MySql", xmlConfigSrc.Get("Data.setting:Inventory:Provider"));
}

[Theory]
[InlineData(true)]
[InlineData(false)]
public void AddXmlFile_FileProvider_Gets_Disposed_When_It_Was_Not_Created_By_The_User(bool disposeConfigRoot)
{
string filePath = Path.Combine(Path.GetTempPath(), $"{nameof(AddXmlFile_FileProvider_Gets_Disposed_When_It_Was_Not_Created_By_The_User)}.xml");
File.WriteAllText(filePath, @"<settings><My><Nice>Settings</Nice></My></settings>");

IConfigurationRoot config = new ConfigurationBuilder().AddXmlFile(filePath, optional: false).Build();
XmlConfigurationProvider xmlConfigurationProvider = config.Providers.OfType<XmlConfigurationProvider>().Single();

Assert.NotNull(xmlConfigurationProvider.Source.FileProvider);
PhysicalFileProvider fileProvider = (PhysicalFileProvider)xmlConfigurationProvider.Source.FileProvider;
Assert.False(GetIsDisposed(fileProvider));

if (disposeConfigRoot)
{
(config as IDisposable).Dispose(); // disposing ConfigurationRoot
}
else
{
xmlConfigurationProvider.Dispose(); // disposing XmlConfigurationProvider
}

Assert.True(GetIsDisposed(fileProvider));
}

[Fact]
public void AddXmlFile_FileProvider_Is_Not_Disposed_When_It_Is_Owned_By_The_User()
{
string filePath = Path.Combine(Path.GetTempPath(), $"{nameof(AddXmlFile_FileProvider_Is_Not_Disposed_When_It_Is_Owned_By_The_User)}.xml");
File.WriteAllText(filePath, @"<settings><My><Nice>Settings</Nice></My></settings>");

PhysicalFileProvider fileProvider = new(Path.GetDirectoryName(filePath));
XmlConfigurationProvider configurationProvider = new(new XmlConfigurationSource()
{
Path = filePath,
FileProvider = fileProvider
});
IConfigurationRoot config = new ConfigurationBuilder().AddXmlFile(configurationProvider.Source.FileProvider, filePath, optional: true, reloadOnChange: false).Build();

Assert.False(GetIsDisposed(fileProvider));

(config as IDisposable).Dispose(); // disposing ConfigurationRoot that does not own the provider
Assert.False(GetIsDisposed(fileProvider));

configurationProvider.Dispose(); // disposing XmlConfigurationProvider
Assert.False(GetIsDisposed(fileProvider));

fileProvider.Dispose(); // disposing PhysicalFileProvider itself
Assert.True(GetIsDisposed(fileProvider));
}

private static bool GetIsDisposed(PhysicalFileProvider fileProvider)
{
System.Reflection.FieldInfo isDisposedField = typeof(PhysicalFileProvider).GetField("_disposed", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);
return (bool)isDisposedField.GetValue(fileProvider);
}
}
}

0 comments on commit 63fad3c

Please sign in to comment.