Skip to content

IOptionsMonitor<T> does not work with rooted file paths, but PhysicalFileProvider itself does #114833

Open
@Measurity

Description

@Measurity

Issue

It's possible to have any file be used with PhysicalFileProvider, but they can't be watched for changes if the path isn't relative.

No errors are thrown with a rooted path, simply no watcher is listening for changes. Which means IOptionsMonitor<T> does not work as expected.

Code in question

// Relative paths starting with leading slashes are okay
filter = filter.TrimStart(_pathSeparators);

// Absolute paths and paths traversing above root not permitted.
if (Path.IsPathRooted(filter) || PathUtils.PathNavigatesAboveRoot(filter))
{
return NullChangeToken.Singleton;
}

Workaround

The following extension method is what I use to add a custom config file, while having change detection working. See first comment for workaround.

public static IConfigurationBuilder AddMyConfigFile<TOptions>(this IConfigurationBuilder configurationBuilder, string filePath, string configSectionPath = "", bool optional = true) where TOptions : class, new()
{
    ArgumentException.ThrowIfNullOrWhiteSpace(filePath);
    string dirPath = Path.GetDirectoryName(filePath);
    Directory.CreateDirectory(dirPath ?? throw new ArgumentException(nameof(filePath)));
    if (!File.Exists(filePath))
    {
        MyConfig.CreateFile<TOptions>(filePath);
    }

    // Link the config to a relative path within the working directory so that IOptionsMonitor<T> works. See https://github.com/dotnet/runtime/issues/114833
    try
    {
        FileInfo configFile = new(Path.GetFileName(filePath));
        if (configFile.Exists && configFile.LinkTarget != null)
        {
            configFile.Delete();
        }
        configFile.CreateAsSymbolicLink(filePath);
        // Fix targets to point to symbolic link instead.
        dirPath = AppContext.BaseDirectory;
        filePath = configFile.Name; // Now a relative path.
    }
    catch (IOException)
    {
        // ignored - config change detection isn't critical for server.
    }

    return configurationBuilder.Add(new MyConfigurationSource(filePath, configSectionPath, optional, new PhysicalFileProvider(dirPath)
    {
        UsePollingFileWatcher = true,
        UseActivePolling = true
    }));
}

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions