获取虚拟目录中文件的本地路径

Get the local path of a file in a virtual directory

我有一个 ASP.NET Core 3.0 MVC 应用程序,里面有图像。例如,

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

现在,文件夹 images 是映射到网络驱动器的 虚拟目录 ,例如 \192.168.1.1\images.

问题:

什么方法把信息/images/image.jpg变成\192.168.1.1\images\image.jpg?我需要从 相对网络路径 .

检索文件的 物理路径

在 ASP.NET Web Forms 中,这可以通过 Server.MapPath("~/images/image.jpg") 之类的方法来完成,但是 ASP.NET Core 的 HttpContext 中不再存在这种方法。

您可以从 IHostingEnvironment 依赖项中获取此信息。这个会被ASP.NET核心框架填充,然后就可以得到当前web目录的值

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/");

您可以先将虚拟路径(网络驱动器)映射到您的本地设备,然后使用 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
        });

@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.

虽然 a workaround is proposed using IFileProvider,但它实际上不适用于虚拟目录。然而,您 可以 做的是建立映射服务来转换基本路径,并可选择查询 IIS 以动态检索这些映射,我将在下面讨论。

背景

此限制源于以下事实:ASP.NET Core 不再与 IIS 绑定,而是依赖抽象层(例如,IWebHostEnvironment)与 Web 服务器通信;默认的 ASP.NET Core Kestrel Web 服务器充当反向代理 (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.

请记住,虚拟目录(或者更确切地说,虚拟应用程序)的概念对于作为 Web 服务器的 IIS 来说是相当特定的。

解决方法

不幸的是,正如前面摘录中提到的,您唯一真正的选择是在您的虚拟目录和它们的物理位置之间创建一个映射,然后创建一个为您处理翻译的服务。

以下是关于如何实现该目标的基本概念验证 — 当然,您可能需要更强大的生产代码。

界面

这引入了可用于依赖注入和测试目的的抽象。为了与旧版 Web 表单签名保持一致,我坚持使用 MapPath()

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

服务

接口的具体实现可能会从 , a database—or even the Microsoft Web Administration library 中提取数据。然而,对于这个概念验证,我只是将它们硬编码到提供程序中:

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;

    }

}

注册

实现需要了解默认的 Web 根目录,以便为不在虚拟目录中的文件转换路径。这可以动态检索,如 中所示,尽管我在这里使用 IWebHostEnvironment。这样,您就可以使用 ASP.NET 核心的依赖注入容器将 VirtualFileProvider 注册为单例生活方式:

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) 
    {
        …
    }

}

实施

注册您的实现后,您可以将提供程序注入到 MVC 控制器的构造函数中,甚至直接注入到您的操作中:

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

限制

上面的代码没有努力验证文件 实际上 是否存在——尽管通过 File.Exists() 添加很容易。这显然会使通话费用增加一些。

动态映射

以上实现依赖于硬编码值。但是,如前所述,用于从 IIS 中提取虚拟目录列表的 Microsoft Web Administration library offers methods for interacting with IIS programmatically. This includes the Application.VirtualDirectories property

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);
}

这可以与 VirtualFileProvider 集成,以便在需要时动态评估可用的虚拟目录。

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.

结论

我知道这不是您所希望的方法。但是,根据您的具体实施,这可能是一个可接受的解决方法。显然,如果这是一个可重用的库,被放置在您不了解虚拟目录的各种站点上,则需要将数据与实现分开。不过,这至少提供了一个基本的工作结构。