Skip to content
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 @@ -69,58 +69,107 @@ private void Load(bool reload)
IFileInfo? file = Source.FileProvider?.GetFileInfo(Source.Path ?? string.Empty);
if (file == null || !file.Exists)
{
if (Source.Optional || reload) // Always optional on reload
{
Data = new Dictionary<string, string?>(StringComparer.OrdinalIgnoreCase);
}
else
HandleLoadingNonExisting(reload, file);
return;
}

static Stream OpenRead(IFileInfo fileInfo)
{
if (fileInfo.PhysicalPath != null)
{
var error = new StringBuilder(SR.Format(SR.Error_FileNotFound, Source.Path));
if (!string.IsNullOrEmpty(file?.PhysicalPath))
{
error.Append(SR.Format(SR.Error_ExpectedPhysicalPath, file.PhysicalPath));
}
HandleException(ExceptionDispatchInfo.Capture(new FileNotFoundException(error.ToString())));
// The default physical file info assumes asynchronous IO which results in unnecessary overhead
// especially since the configuration system is synchronous. This uses the same settings
// and disables async IO.
return new FileStream(
fileInfo.PhysicalPath,
FileMode.Open,
FileAccess.Read,
FileShare.ReadWrite,
bufferSize: 1,
FileOptions.SequentialScan);
}

return fileInfo.CreateReadStream();
}
else

Stream stream;
try
{
static Stream OpenRead(IFileInfo fileInfo)
{
if (fileInfo.PhysicalPath != null)
{
// The default physical file info assumes asynchronous IO which results in unnecessary overhead
// especially since the configuration system is synchronous. This uses the same settings
// and disables async IO.
return new FileStream(
fileInfo.PhysicalPath,
FileMode.Open,
FileAccess.Read,
FileShare.ReadWrite,
bufferSize: 1,
FileOptions.SequentialScan);
}
stream = OpenRead(file);
}
catch (Exception ex) when (ex is FileNotFoundException or DirectoryNotFoundException)
{
// assuming file was deleted in meantime, we already checked existence at the beginning once
HandleLoadingNonExisting(reload, file, ex is DirectoryNotFoundException);
return;
}
catch (Exception ex)
{
// IO error on file open, preserve existing Data
HandleException(ExceptionDispatchInfo.Capture(ex));
return;
}

return fileInfo.CreateReadStream();
}
bool updated = false;

using Stream stream = OpenRead(file);
using (stream)
{
try
{
Load(stream);
updated = true;
}
catch (Exception ex)
{
if (reload)
{
Data = new Dictionary<string, string?>(StringComparer.OrdinalIgnoreCase);
ClearData();
updated = true;
}
var exception = new InvalidDataException(SR.Format(SR.Error_FailedToLoad, file.PhysicalPath), ex);
HandleException(ExceptionDispatchInfo.Capture(exception));
string filePath = file.PhysicalPath ?? Source.Path ?? file.Name;
var wrapped = new InvalidDataException(SR.Format(SR.Error_FailedToLoad, filePath), ex);
HandleException(ExceptionDispatchInfo.Capture(wrapped));
}
}

if (updated)
{
OnReload();
}
}

/// <summary>
/// Handles a missing configuration file during the initial load or a reload.
/// </summary>
/// <param name="reload"><see langword="true"/> when the provider is reloading after a change notification;
/// <see langword="false"/> when loading for first time.</param>
/// <param name="file">The file information returned by the <see cref="FileConfigurationSource.FileProvider"/>,
/// or <see langword="null"/> if no file information is available.</param>
/// <param name="directoryException">Determines if <see cref="FileNotFoundException"/> or
/// <see cref="DirectoryNotFoundException"/> is thrown on error.</param>
private void HandleLoadingNonExisting(bool reload, IFileInfo? file, bool directoryException = false)
{
if (Source.Optional || reload) // Always optional on reload
{
ClearData();
OnReload();
}
else
{
var error = new StringBuilder(SR.Format(SR.Error_FileNotFound, Source.Path ?? file?.Name));
if (!string.IsNullOrEmpty(file?.PhysicalPath))
{
error.Append(SR.Format(SR.Error_ExpectedPhysicalPath, file.PhysicalPath));
}
if (!directoryException)
{
HandleException(ExceptionDispatchInfo.Capture(new FileNotFoundException(error.ToString())));
}
else
{
HandleException(ExceptionDispatchInfo.Capture(new DirectoryNotFoundException(error.ToString())));
}
}
// REVIEW: Should we raise this in the base as well / instead?
OnReload();
}

/// <summary>
Expand All @@ -133,6 +182,9 @@ static Stream OpenRead(IFileInfo fileInfo)
/// <exception cref="InvalidDataException">An exception was thrown by the concrete implementation of the
/// <see cref="Load()"/> method. Use the source <see cref="FileConfigurationSource.OnLoadException"/> callback
/// if you need more control over the exception.</exception>
/// <exception cref="IOException">An I/O error occurred when opening the file. Other exceptions from the
/// underlying file provider may also be thrown. Use the source <see cref="FileConfigurationSource.OnLoadException"/>
/// callback if you need more control over the exception.</exception>
public override void Load()
{
Load(reload: false);
Expand Down Expand Up @@ -163,6 +215,11 @@ private void HandleException(ExceptionDispatchInfo info)
}
}

private void ClearData()
{
Data = new Dictionary<string, string?>(StringComparer.OrdinalIgnoreCase);
}

/// <inheritdoc />
public void Dispose() => Dispose(true);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@ public abstract class FileConfigurationSource : IConfigurationSource
/// <summary>
/// Gets or sets the action that's called if an uncaught exception occurs in FileConfigurationProvider.Load.
/// </summary>
/// <remarks>
/// When <see cref="ReloadOnChange"/> is enabled, this callback is also invoked on background reload failures.
/// If the callback is not set or does not set <see cref="FileLoadExceptionContext.Ignore"/> to <see langword="true"/>,
/// exceptions from background reloads will propagate unhandled on the thread pool.
/// </remarks>
public Action<FileLoadExceptionContext>? OnLoadException { get; set; }

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public class FileLoadExceptionContext
public FileConfigurationProvider Provider { get; set; } = null!;

/// <summary>
/// Gets or sets the exception that occurred in Load.
/// Gets or sets the exception that occurred during file loading.
/// </summary>
public Exception Exception { get; set; } = null!;

Expand Down
Loading
Loading