简体   繁体   English

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

[英]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.我有一个 ASP.NET Core 3.0 MVC 应用程序,其中包含图像。 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 .现在,文件夹images是一个映射到网络驱动器的虚拟目录,例如\\192.168.1.1\images

Question:问题:

What method turns the information /images/image.jpg into \\192.168.1.1\images\image.jpg ?什么方法将信息/images/image.jpg变成\\192.168.1.1\images\image.jpg I need to retrieve the physical path of the file from the relative web path .我需要从相对 web 路径中检索文件的物理路径。

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. 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 ):正如@Akshay Gaonkar在评论中指出的那样,微软已经明确评估并拒绝了 ASP.NET Core 中的此功能(参考):

We don't have plans to implement this.我们没有计划实施这一点。 The concepts don't really map in ASP.NET Core. ASP.NET 核心中的概念并不是真正的 map。 URLs aren't inherently based on any directory structure. URL 本身并不基于任何目录结构。 Each component has conventions that may map to directories, but this isn't something that can be generalized.每个组件都有可能 map 到目录的约定,但这不是可以概括的东西。

And while a workaround is proposed using IFileProvider , it doesn't actually work for virtual directories.虽然提出了使用IFileProvider的解决方法,但它实际上不适用于虚拟目录。 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.但是,您可以做的是建立一个映射服务来转换基本路径,并可选择查询 IIS 以动态检索这些映射,我将在下面讨论。

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;此限制源于 ASP.NET 内核不再与 IIS 相关联,而是依赖于抽象层(例如IWebHostEnvironment )与 Z2567A5EC9705EB7AC2C984033E061 服务器通信; that is further complicated by the fact that the default ASP.NET Core Kestrel web server acts as a reverse proxy ( reference ):由于默认的 ASP.NET Core Kestrel web 服务器充当反向代理( 参考),这一事实更加复杂:

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.请记住,虚拟目录(或者更重要的是,虚拟应用程序)的概念对于 IIS 作为 web 服务器是相当特定的。

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.为了与旧版 Web Forms 签名保持一致,我坚持使用MapPath()

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 .接口的具体实现可能会从配置文件、数据库甚至是Microsoft Web 管理库中提取数据。 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.该实现需要了解默认的 web 根目录,用于转换不在虚拟目录中的文件的路径。 This can be retrieved dynamically, as seen in @Pashyant Srivastava's answer , though I'm using the IWebHostEnvironment here.这可以动态检索,如@Pashyant Srivastava 的回答所示,尽管我在这里使用IWebHostEnvironment With that, you can register the VirtualFileProvider as a singleton life style with ASP.NET Core's dependency injection container:这样,您可以使用 ASP.NET Core 的依赖注入容器将VirtualFileProvider注册为 singleton 生活方式:

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

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() .上面的代码没有努力验证文件是否确实存在——尽管通过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.但是,如上所述, Microsoft Web 管理库提供了以编程方式与 IIS 交互的方法。 This includes the Application.VirtualDirectories property for pulling a list of virtual directories from IIS:这包括用于从 IIS 中提取虚拟目录列表的Application.VirtualDirectories属性

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.如果需要,这可以与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.警告: Microsoft Web 管理库尚未更新以支持 .NET 5,并保持对不向前兼容的 .NET Core 3.x 库的依赖关系。 It's unclear when or if Microsoft will be releasing a .NET 5 compatible version.目前尚不清楚微软何时或是否会发布 .NET 5 兼容版本。 As your question is specific to .NET Core 3.1, this may not be an immediate concern.由于您的问题特定于 .NET Core 3.1,因此这可能不是一个直接的问题。 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.但是由于 .NET 5 是 .NET 的当前版本,因此引入对 Microsoft Web 管理库的依赖可能会带来长期风险。

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.您可以从IHostingEnvironment依赖项中获取此信息。 This will be populated by the ASP.NET Core framework, and then you can get the value of the current web directory.这将由 ASP.NET Core 框架填充,然后您可以获取当前 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/");

You can start by mapping the virtual path (Network Drive) to your local device and make use of the PhysicalFileProvider .您可以首先将虚拟路径(网络驱动器)映射到本地设备并使用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
        });

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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