4

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

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.

5

3 Answers 3

4
+50

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 (e.g., 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.

-1

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
        });
4
  • 1
    This assumes that it is known upfront that the user is in a virtual directory, and what that directory is mapped to. My assumption is that the OP doesn't necessarily know that in a real world scenario, and wants the code to be able to determine the correct physical path based on no information outside of the relative web path. Commented May 19, 2021 at 19:13
  • @JeremyCaney exactly
    – Impostor
    Commented May 20, 2021 at 14:46
  • You do have an interesting perspective: a. How can a virtual path be dynamic?
    – Byte
    Commented May 20, 2021 at 17:52
  • @byte Ex. The admin for IIS could define "app_data" differently per server, for example. Lets say that APP_DATA was "D:\App_Data" on server 1 but on server 2 it was "F:\Data\App". Let's also assume the same app had to run on both servers. Let's also assume the dev had no control over the IT / devop group. In both cases the folder is not relative to the app and different on each server. Commented Mar 31, 2023 at 21:38
-1

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/");
1
  • 2
    this is not about getting the root path, it's about getting the physical path of a virtual directory, which could be anywhere
    – Impostor
    Commented May 18, 2021 at 9:44

Not the answer you're looking for? Browse other questions tagged or ask your own question.