Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Serve static files outside wwwroot, but handle PhysicalFileProvider when directory does not exist

I am trying to serve files from outside wwwroot but also handle a setup situation where the directory may not exist yet. For example, if I built a site which relies on this directory, but a user did not follow install instructions and create the directory first.

For the sake of argument, let's pretend I want a simple website which has a page that will read the contents of that directory and display some files. This page might also allow you to download files from /Documents subdirectory. The example directory would be C:\Example\Documents.

As this is an aspnet core mvc project, I would use UseStaticFiles() in the Configure method inside Startup.cs

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory) {
    if (env.IsDevelopment()) {
        app.UseDeveloperExceptionPage();
        app.UseBrowserLink();
    } else {
        app.UseExceptionHandler("/Home/Error");
    }

    app.UseStaticFiles();

    // If C:/Example/Documents does not exist, an exception is thrown
    // ArgumentException: The directory name C:\Example\Documents\ is invalid
    // What is worse, the users is not directed to /Home/Error due to the application failing to properly start.
    app.UseStaticFiles(new StaticFileOptions() {
        FileProvider = new PhysicalFileProvider("C:/Example/Documents"),
        RequestPath = new PathString("/Documents"),
    });

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

Now I have a two part problem. The app.UseExceptionHandler("/Home/Error"); does not seem to take effect at all if an exception is thrown in this method. If the path used in PhysicalFileProvider does not exist, an internal server error 500 is thrown and the page is then displayed with an application failed to start message. If I am in developer mode, however, I do see the developer exception page with the actual problem. I really just want to make a friendly error page to handle this situation.

The other part of the problem is what happens when the users fix the issue by creating the directory and adding content. Once created, they would then refresh the page. The fix is to force close the application and restart it. Ideally, I would like it to recover on its own. Would I need to do the UseStaticHandle piece in the Controller? Can you even do that? My attempt failed when trying to use dependency injection on IApplicationBuilder in my controller, but I also did not expect it to work.

I am honestly unsure what the best way to handle this would be. The ideal situation is to allow the application to start properly, which could be accomplished with a Directory.Exists() check around the PhysicalFileProvider, and then forward the user to an error page, but the user would still need to restart the application when they fix the issue.

like image 711
Jeff Avatar asked Jun 13 '17 16:06

Jeff


1 Answers

Exception thrown by PhysicalFileProvider:

System.ArgumentException: The directory name D:\Daten7\ is invalid.
Parameter name: path
   at System.IO.FileSystemWatcher..ctor(String path, String filter)
   at System.IO.FileSystemWatcher..ctor(String path)
   at Microsoft.Extensions.FileProviders.PhysicalFileProvider.CreateFileWatcher(String root)
   at Microsoft.Extensions.FileProviders.PhysicalFileProvider..ctor(String root)
   at WebApplication1.Startup.Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)

In Startup.cs replace PhysicalFileProvider with PhysicalFileProvider2. Please find source code of PhysicalFileProvider2 below.

Detailed Description of PhysicalFileProvider2

Class PhysicalFileProvider of ASP.Net core insists that directory given as argument to constructor already exists on startup and if it not exists refuses to start up properly

I wrote class PhysicalFileProvider2 which can be used instead. It can be instantiated even if the directory does not exist. But on first invocation of any of its method the directory is created if it not already exists. Now startup properly works and users can add files to directory, which then are properly served.

public class PhysicalFileProvider2 : IFileProvider
{
    private string root;
    private PhysicalFileProvider physicalFileProvider;

    public PhysicalFileProvider2(string root)
    {
        this.root = root;
    }

    private PhysicalFileProvider GetPhysicalFileProvider()
    {
        if (!File.Exists(root))
        {
            Directory.CreateDirectory(root);
        }
        if (physicalFileProvider == null)
        {
            physicalFileProvider = new PhysicalFileProvider(root);
        }
        return physicalFileProvider;
    }

    public IDirectoryContents GetDirectoryContents(string subpath)
    {
        return GetPhysicalFileProvider().GetDirectoryContents(subpath);
    }

    public IFileInfo GetFileInfo(string subpath)
    {
        return GetPhysicalFileProvider().GetFileInfo(subpath);
    }

    public IChangeToken Watch(string filter)
    {
        return GetPhysicalFileProvider().Watch(filter);
    }
}
like image 87
Aedvald Tseh Avatar answered Nov 10 '22 01:11

Aedvald Tseh