简体   繁体   中英

Get the local path of a file in a virtual directory

I have an ASP.NET Core 3.0 MVC application with images in it. Eg,

http://foo.bar/images/image.jpg

Now, the folder images is a virtual directory which is mapped to a network drive, such as \\192.168.1.1\images .

Question:

What method turns the information /images/image.jpg into \\192.168.1.1\images\image.jpg ? I need to retrieve the physical path of the file from the relative web path .

In ASP.NET Web Forms, this could be done by something like Server.MapPath("~/images/image.jpg") , but this method doesn't exist in ASP.NET Core's HttpContext anymore.

As noted by @Akshay Gaonkar in the comments, Microsoft has explicitly evaluated and rejected this functionality in ASP.NET Core ( reference ):

We don't have plans to implement this. The concepts don't really map in ASP.NET Core. URLs aren't inherently based on any directory structure. Each component has conventions that may map to directories, but this isn't something that can be generalized.

And while a workaround is proposed using IFileProvider , it doesn't actually work for virtual directories. What you can do, however, is establish a mapping service for translating the base path—and optionally querying IIS to retrieve those mappings dynamically, as I'll discuss below.

Background

This limitation stems from the fact that ASP.NET Core is no longer tied to IIS, but instead relies on an abstraction layer (eg, IWebHostEnvironment ) to talk to the web server; that is further complicated by the fact that the default ASP.NET Core Kestrel web server acts as a reverse proxy ( reference ):

That's going to be rough. I don't think that's even possible for us to implement in the current reverse-proxy architecture. You're going to have to maintain a manual mapping table.

Keep in mind that the concept of a virtual directory (or, even more so, a virtual application) is fairly specific to IIS as a web server.

Workaround

Unfortunately, as mentioned in the previous excerpt, your only real option is to create a mapping between your virtual directories and their physical locations, and then create a service that handles the translation for you.

The following is a basic proof-of-concept for how you might accomplish that—though, of course, you'll probably want something more robust for production code.

Interface

This introduces an abstraction that can be used for dependency injection and testing purposes. I've stuck with MapPath() for consistency with the legacy Web Forms signature.

public interface IVirtualFileProvider
{
    string MapPath(string path);
}

Service

The concrete implementation of the interface might pull the data from a configuration file , a database—or even the Microsoft Web Administration library . For this proof-of-concept, however, I'm just hard-coding them into the provider:

public class VirtualFileProvider: IVirtualFileProvider
{

    // Store dependencies
    private readonly string _webRootPath;

    // Map virtual directories
    private readonly Dictionary<string, string> _virtualDirectories = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase) {
        { "Images", @"\\192.168.1.1\images" }
    };

    public VirtualFileProvider(string webRootPath) {
      _webRootPath = webRootPath;
    }

    public string MapPath(string path)
    {

        // Validate path
        if (String.IsNullOrEmpty(path) || !path.StartsWith("/", StringComparison.Ordinal)) {
            throw new ArgumentException($"The '{path}' should be root relative, and start with a '/'.");
        }

        // Translate path to UNC format
        path                = path.Replace("/", @"\", StringComparison.Ordinal);

        // Isolate first folder (or file)
        var firstFolder     = path.IndexOf(@"\", 1);
        if (firstFolder < 0)
        {
            firstFolder     = path.Length;
        }

        // Parse root directory from remainder of path
        var rootDirectory   = path.Substring(1, firstFolder-1);
        var relativePath    = path.Substring(firstFolder);

        // Return virtual directory
        if (_virtualDirectories.ContainsKey(rootDirectory))
        {
            return _virtualDirectories[rootDirectory] + relativePath;
        }

        // Return non-virtual directory
        return _webRootPath + @"\" + rootDirectory + relativePath;

    }

}

Registration

The implementation requires knowledge of the default web root, for translating the path for files not in a virtual directory. This can be retrieved dynamically, as seen in @Pashyant Srivastava's answer , though I'm using the IWebHostEnvironment here. With that, you can register the VirtualFileProvider as a singleton life style with ASP.NET Core's dependency injection container:

public class Startup 
{

    private readonly IWebHostEnvironment _hostingEnvironment;

    public Startup(IWebHostEnvironment webHostEnvironment) 
    {
        _hostingEnvironment = webHostEnvironment;
    }

    public void ConfigureServices(IServiceCollection services)
    {

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

        // Register virtual file provider
        services.AddSingleton<IVirtualFileProvider>(new VirtualFileProvider(_hostingEnvironment.WebRootPath));

    }

    public static void Configure(IApplicationBuilder app, IWebHostEnvironment env) 
    {
        …
    }

}

Implementation

With your implementation registered, you can inject the provider into your MVC controller's constructor or even directly into your action:

public IActionResult MyAction([FromServices] IVirtualFileProvider fileProvider, string file)
    => Content(fileProvider?.MapPath(file));

Limitations

The above code makes no effort to validate that the file actually exists—though that's easy to add via File.Exists() . That will obviously make the call a bit more expensive.

Dynamic Mapping

The above implementation relies on hard-coded values. As mentioned, though, the Microsoft Web Administration library offers methods for interacting with IIS programmatically. This includes the Application.VirtualDirectories property for pulling a list of virtual directories from IIS:

var directories = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
var manager     = new ServerManager();
var site        = manager.Sites["Default Web Site"];
var application = site[0]; 
                
foreach (var virtualDirectory in application.VirtualDirectories)
{
    directories.Add(virtualDirectory.Path, virtualDirectory.PhysicalPath);
}

This can be integrated with the VirtualFileProvider to dynamically assess the available virtual directories if needed.

Warning: The Microsoft Web Administration library hasn't been updated to support .NET 5, and maintains dependencies on .NET Core 3.x libraries that are not forward compatible. It's unclear when or if Microsoft will be releasing a .NET 5 compatible version. As your question is specific to .NET Core 3.1, this may not be an immediate concern. But as .NET 5 is the current version of .NET, introducing a dependency on the Microsoft Web Administration library may represent a long-term risk.

Conclusion

I know this isn't the approach you were hoping for. Depending on your exact implementation, however, this might be an acceptable workaround. Obviously, if this is a reusable library being placed on a variety of sites where you have no knowledge of the virtual directories, you'll need to separate the data from the implementation. This at least provides a basic structure to work off of, though.

You can get this information from the IHostingEnvironment dependency. This will be populated by the ASP.NET Core framework, and then you can get the value of the current web directory.

private readonly IHostingEnvironment _hostingEnvironment;

public EmployeeController(IHostingEnvironment hostingEnvironment)
{
    _hostingEnvironment = hostingEnvironment;
}

// Get the path to write
string webRootPath = _hostingEnvironment.WebRootPath;

// ASP.NET Core application root directory under the wwwroot directory
 
// Get static directory
string contentRootPath = _hostingEnvironment.ContentRootPath;
// The web root directory refers to the root directory that provides static content; there is no wwwroot.

// Through the path and then through the IO stream to determine whether the file you are passing in the directory exists
DirectoryInfo directoryInfo = new DirectoryInfo(webRootPath+"/uploads/images/");

You can start by mapping the virtual path (Network Drive) to your local device and make use of the PhysicalFileProvider . A more detailed use case of this has be found here

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM