In your scenarios, I think you should have a preset of different web root paths for the user to select. Why does that make sense? Because usually the root paths should exist and already contain some contents (just like the wwwroot). If that fits your need, we can have a simple solution like this:
//inside Startup.Configure
app.UseWhen(context => {
var wr = context.Request.Cookies["wwwroot"];
return string.IsNullOrEmpty(wr) || wr == "wwwroot";
}, subApp => {
subApp.UseStaticFiles();
}).UseWhen(context => {
var wr = context.Request.Cookies["wwwroot"];
return wr == "YAwwwroot";
}, subApp => {
subApp.UseStaticFiles(new StaticFileOptions
{
FileProvider = new PhysicalFileProvider(Path.Combine(HostingEnvironment.ContentRootPath, "YAwwwroot"))
});
});
The code above use UseWhen to dynamically swap the static file middleware with preconditions (known beforehand). I just take it as a simple example to demonstrate how simple it is. The other root path you want to use is YAwwwroot. The use of Cookies is just for demonstrating purpose, you can access your data from the HttpContext by any other means (e.g: a singleton service or options which holds the state about the active root path). As you can see, we must know beforehand the root paths to write the UseWhen code accordingly. Also you can write your own extension method to avoid the repetition of UseWhen (so in case you have many root paths, you don't have to repeat many UseWhen).
In some controller action, you can set the active root path (here paired with the code above, we set the active root path via cookies):
public IActionResult SetStaticFileRoot(string root)
{
root = root ?? "wwwroot";
Response.Cookies.Append("wwwroot", root);
return Ok(root);
}
Now if you really want the active root path set by users to be dynamic (and of course completely controlled by the user). We need to create a custom middleware for static files. However we don't have to re-implement much. We can reuse the StaticFileMiddleware. That default middleware is a singleton and it captures the StaticFileOptions once. That's why even when you can update the StaticFileOptions, it will have no effect.
We can implement 2 kinds of middleware for static files here, scoped and singleton.
They both work in the same way in which for each request, the latest value of StaticFileOptions will be passed to the default StaticFileMiddleware (when instantiating it). So your controller action can update the StaticFileOptions directly to change the static file's root path.
Here's the scoped version:
public class ScopedStaticFileMiddleware : IMiddleware
{
readonly IHostingEnvironment _hostingEnvironment;
readonly IOptions<StaticFileOptions> _staticFileOptions;
readonly ILoggerFactory _loggerFactory;
public ScopedStaticFileMiddleware(IHostingEnvironment hostingEnvironment,
IOptions<StaticFileOptions> staticFileOptions,
ILoggerFactory loggerFactory)
{
_hostingEnvironment = hostingEnvironment;
_staticFileOptions = staticFileOptions;
_loggerFactory = loggerFactory;
}
public Task InvokeAsync(HttpContext context, RequestDelegate next)
{
//here we create the default static file middleware
var staticFileMiddleware = new StaticFileMiddleware(next, _hostingEnvironment, _staticFileOptions, _loggerFactory);
return staticFileMiddleware.Invoke(context);
}
}
To register the scoped middleware:
//inside Startup.ConfigureServices
services.AddScoped<ScopedStaticFileMiddleware>();
//inside Startup.Configure (replacing app.UseStaticFiles)
app.UseMiddleware<ScopedStaticFileMiddleware>();
Here's the singleton version:
public class ScopedOptionsStaticFileMiddleware
{
readonly RequestDelegate _next;
readonly IHostingEnvironment _hostingEnvironment;
readonly IOptions<StaticFileOptions> _staticFileOptions;
readonly ILoggerFactory _loggerFactory;
public ScopedOptionsStaticFileMiddleware(RequestDelegate next,
IHostingEnvironment hostingEnvironment,
IOptions<StaticFileOptions> staticFileOptions,
ILoggerFactory loggerFactory)
{
_next = next;
_hostingEnvironment = hostingEnvironment;
_staticFileOptions = staticFileOptions;
_loggerFactory = loggerFactory;
}
public Task Invoke(HttpContext context)
{
//we create the default middleware each time processing a request
//so the latest static file options is always used
var defaultMiddleware = new StaticFileMiddleware(_next, _hostingEnvironment,
_staticFileOptions, _loggerFactory);
return defaultMiddleware.Invoke(context);
}
}
To use the singleton middleware:
//inside Startup.Configure
app.UseMiddleware<ScopedOptionsStaticFileMiddleware>();
Using the singleton middleware may save you a bit of cost/resources due to objects instantiation.
Finally, to update your root path, change the FileProvider of StaticFileOptions accordingly:
//inject IOptions<StaticFileOptions> and get its value into _staticFileOptions
//inject IWebHostEnvironment as _environment
...
public IActionResult SetStaticFileRoot(string root) {
root = root ?? "wwwroot";
//create a new file provider with the new root path
_staticFileOptions.FileProvider = new PhysicalFileProvider(Path.Combine(_environment.ContentRootPath, root));
return Ok(root);
}
Note: the code above does not take care of checking if the absolute root path exists or it should be created then. That's your part to handle it.