2

I know we can set FileProvider in Configure method to server other location files:

app.UseStaticFiles(new StaticFileOptions
{
    FileProvider = new PhysicalFileProvider(Path.Combine(env.WebRootPath, "images")),
    RequestPath = "/MyImages"
});

I wonder how to change the FileProvider files location in controller, because I want to reset the path by user input.

2 Answers 2

5

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.

Sign up to request clarification or add additional context in comments.

2 Comments

Wow, that's amazing. Thank you very much @King King
Amazing answer for a question with only two lines of context!
0

Reset the path involves passing parameters, but this middleware UseStaticFiles does not provide context to obtain parameters from the request. Although the path can be reset by custom middleware, this will make the original design more complicated. So you can read the picture into FileStream with method OpenRead.

public IActionResult get(string filePath)
    {
        
        var image = System.IO.File.OpenRead(webHostEnvironment.WebRootPath + filePath);
        return File(image, "image/jpeg");
    }

This will make the picture appear in the view instead of downloading.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.