Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Get PhysicalPath after setting up app.UseFileServer in ASP.NET Core

I have virtual directories that point to some file servers in IIS. I learned that ASP.NET Core cannot "see" the virtual directories in IIS because it is running on Kestral.

I'm told the workaround is to use app.UseFileServer:

app.UseFileServer(new FileServerOptions
{
    FileProvider = new PhysicalFileProvider(@"\\server\path"),
    RequestPath = new PathString("/MyPath"),
    EnableDirectoryBrowsing = true
});

I set that up, and it works when I type in /MyPath in the browser address bar. However, I want to be able to get the physical path while in a controller.

Something like:

//Server.Map path no longer exists in ASP.NET Core...
var filePath = Server.MapPath("/MyPath");
var fileName = "MyFile.txt";
System.IO.File.OpenRead(Path.Combine(filePath , fileName));

Is there a way I can get the physical path by providing the RequestPath I setup in app.UseFileServer?

like image 484
John-Luke Laue Avatar asked Apr 03 '17 17:04

John-Luke Laue


1 Answers

The core of this problem is getting access to the correct IFileProvider (which points to the physical path of your directory) in your controller.

You could go about this by configuring these paths in a custom Service so you could access them anywhere:

  • In your Configure() method to set up UseFileServer
  • In your Controllers, etc

I have some code to demonstrate how you could go about this:

Service and convenience methods

First, create a custom service, which will hold your FileServerOptions that can be accessed by UseFileServer() or your controller. I opted for a List of them, so you can add multiple paths.

public interface IFileServerProvider
{
    /// <summary>
    /// Contains a list of FileServer options, a combination of virtual + physical paths we can access at any time
    /// </summary>
    IList<FileServerOptions> FileServerOptionsCollection { get; }

    /// <summary>
    /// Gets the IFileProvider to access a physical location by using its virtual path
    /// </summary>
    IFileProvider GetProvider(string virtualPath);
}

/// <summary>
/// Implements IFileServerProvider in a very simple way, for demonstration only
/// </summary>
public class FileServerProvider : IFileServerProvider
{
    public FileServerProvider(IList<FileServerOptions> fileServerOptions)
    {
        FileServerOptionsCollection = fileServerOptions;
    }

    public IList<FileServerOptions> FileServerOptionsCollection { get; }

    public IFileProvider GetProvider(string virtualPath)
    {
        var options = FileServerOptionsCollection.FirstOrDefault(e => e.RequestPath == virtualPath);
        if (options != null)
            return options.FileProvider;

        throw new FileNotFoundException($"virtual path {virtualPath} is not registered in the fileserver provider");
    }
}

Then, for convenience, you could also add an Extension method which would call the UseFileServer middleware with the options from the service above:

/// <summary>
/// Wrapper for UseFileServer to easily use the FileServerOptions registered in the IFileServerProvider
/// </summary>
public static class FileServerProviderExtensions
{
    public static IApplicationBuilder UseFileServerProvider(this IApplicationBuilder application, IFileServerProvider fileServerprovider)
    {
        foreach (var option in fileServerprovider.FileServerOptionsCollection)
        {
            application.UseFileServer(option);
        }
        return application;
    }
}

Startup methods

Now, you'd only have to register the IFileServerProvider with your chosen paths (1) and hand it over to the convenience method to actually use them (2).

(1) First, register the IFileServerProvider with your chosen paths as a Singleton service:

public void ConfigureServices(IServiceCollection services)
{
    //Add our IFileServerProvider implementation as a singleton
    services.AddSingleton<IFileServerProvider>(new FileServerProvider(
        new List<FileServerOptions>
        {
            new FileServerOptions
            {
                FileProvider = new PhysicalFileProvider(@"D:\\somepath"),
                RequestPath = new PathString("/OtherPath"),
                EnableDirectoryBrowsing = true
            },
            new FileServerOptions
            {
                FileProvider = new PhysicalFileProvider(@"\\server\path"),
                RequestPath = new PathString("/MyPath"),
                EnableDirectoryBrowsing = true
            }
        }));

    // Add framework services.
    services.AddMvc();
}

(2) And then actually use them in UseFileServer by calling the extension method created earlier. Note the extra argument IFileServerProvider fileServerprovider in the Configure method:

// Note: accessing the IFileServerProvider implemantion here by adding it 
// in the arguments list!
public void Configure(IApplicationBuilder app, IFileServerProvider fileServerprovider)
{
    //call convenience method which adds our FileServerOptions from 
    // the IFileServerProvider service
    app.UseFileServerProvider(fileServerprovider);

    app.UseMvc(routes =>
    {
        routes.MapRoute(
            name: "default",
            template: "{controller=Home}/{action=Index}/{id?}");
    });
}

Usage

Now, when you browse to the paths /MyPath or /OtherPath, you should get a directory listing as before.

But, because we have everything tidy in a Service it's now easier to get to these paths programmatically in a Controller:

public class HomeController : Controller
{
    private IFileServerProvider _fileServerProvider;

    public HomeController(IFileServerProvider fileServerProvider)
    {
        _fileServerProvider = fileServerProvider;
    }

    public IActionResult Index()
    {
        var fileProvider = _fileServerProvider.GetProvider("/MyPath");
        var fileInfo = fileProvider.GetFileInfo("MyFile.txt");
        using (var stream = System.IO.File.OpenRead(fileInfo.PhysicalPath))
        {
            string contents = new StreamReader(stream).ReadToEnd();
        }
        return View();
    }
}

This code could probably be written in a more abstract and more adequately named version, but it demonstrates a working solution to your problem.

like image 77
Sigge Avatar answered Oct 21 '22 14:10

Sigge