ASP.NET Core File Providers

时间:2022-04-22
本文章向大家介绍ASP.NET Core File Providers,主要内容包括File Provider 抽象、File Provider 实现、EmbeddedFileProvider、CompositeFileProvider、查看更改、通配符模式、在ASP.NET Core中File Provider的用法、在应用程序中使用的建议、基本概念、基础应用、原理机制和需要注意的事项等,并结合实例形式分析了其使用技巧,希望通过本文能帮助到大家理解应用这部分内容。

原文地址:FileProvider

By Steve Smith

ASP.NET Core通过对File Providers的使用实现了对文件系统访问的抽象。

查看或下载示例代码

File Provider 抽象

File Providers是文件系统之上的一层抽象。它的主要接口是IFileProviderIFileProvider公开了相应方法用来获取文件信息(IFileInfo), 目录信息(IDirectoryContents),以及设置更改通知(通过使用一个IChangeToken)。

IFileInfo接口提供了操作单个文件和目录的方法和属性。它有两个boolean属性,ExistsIsDirectory,以及两个描述文件的两个属性NameLength(按字节),还包括一个LastModified日期属性。你还可以通过CreateReadStream方法读取文件内容。

File Provider 实现

有三种对于IFileProvider的实现可供选择:物理式,嵌入式和复合式。物理式用于访问实际系统中的文件。嵌入式用于访问嵌入在程序集中的文件。 复合式则是对前两种方式的组合使用。

PhysicalFileProvider

PhysicalFileProvider提供了对物理文件系统的访问。它封装了System.IO.File类型,范围限定到一个目录及其子目录的所有路径。这类作用域会限制访问某个目录及其子目录,防止作用域以外的其他操作访问文件系统。当实例化此类provider时,你必须为它提供一个目录路径,以供服务器拿来当做由这个provider发出的所有请求的基础路径(这个provider会限制路径以外的访问请求)。在一个ASP.NET Core应用,你可以直接实例化出一个PhysicalFileProvider provider,或者你也可以通过在控制器和服务中使用构造函数依赖注入的方式,请求一个IFileProvider接口。后者生成的解决方案通常更灵活以及更便于测试。

要创建一个PhysicalFileProvider其实很简单,只需要对其实化,再传递给它一个物理路径。之后你就可以通过它的目录遍历内容或提供子路径获取特定文件的信息。

IFileProvider provider = new PhysicalFileProvider(applicationRoot);
IDirectoryContents contents = provider.GetDirectoryContents(""); // the applicationRoot contents
IFileInfo fileInfo = provider.GetFileInfo("wwwroot/js/site.js"); // a file under applicationRoot

为了在控制器中请求一个provider,需要在控制器的构造函数中指定类型参数并赋值给本地属性。之后你就可以在你的动作器方法中使用本地实例了。

public class HomeController : Controller
{
    private readonly IFileProvider _fileProvider;

    public HomeController(IFileProvider fileProvider)
    {
        _fileProvider = fileProvider;
    }

    public IActionResult Index()
    {
        var contents = _fileProvider.GetDirectoryContents("");
        return View(contents);
    }
}

在应用的Startup类中创建provider的代码如下:

using System.Linq;
using System.Reflection;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Logging;

namespace FileProviderSample
{
    public class Startup
    {
        private IHostingEnvironment _hostingEnvironment;
        public Startup(IHostingEnvironment env)
        {
            var builder = new ConfigurationBuilder()
                .SetBasePath(env.ContentRootPath)
                .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
                .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
                .AddEnvironmentVariables();
            Configuration = builder.Build();

            _hostingEnvironment = env;
        }

        public IConfigurationRoot Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            // Add framework services.
            services.AddMvc();

            var physicalProvider = _hostingEnvironment.ContentRootFileProvider;
            var embeddedProvider = new EmbeddedFileProvider(Assembly.GetEntryAssembly());
            var compositeProvider = new CompositeFileProvider(physicalProvider, embeddedProvider);

            // choose one provider to use for the app and register it
            //services.AddSingleton<IFileProvider>(physicalProvider);
            //services.AddSingleton<IFileProvider>(embeddedProvider);
            services.AddSingleton<IFileProvider>(compositeProvider);
        }
    }
}

Index.chhtml 视图中,可以遍历操作IDirectoryContents模型参数

@using Microsoft.Extensions.FileProviders
@model  IDirectoryContents

<h2>Folder Contents</h2>

<ul>
    @foreach (IFileInfo item in Model)
    {
        if (item.IsDirectory)
        {
            <li><strong>@item.Name</strong></li>
        }
        else
        {
            <li>@item.Name - @item.Length bytes</li>
        }
    }
</ul>

结果如下:

EmbeddedFileProvider

EmbeddedFileProvider用于访问嵌入到程序集中的文件。在.NET Core中,你可以通过修改 project.json 文件的buildOptions属性参数来把文件嵌入到程序集中。

"buildOptions": {
  "emitEntryPoint": true,
  "preserveCompilationContext": true,
  "embed": [
    "Resource.txt",
    "**/*.js"
  ]
}

当你把文件嵌入到程序集中时,你可以使用通配符模式。这些模式可以被用来匹配一个或多个文件。

Note 把项目中所有的.js文件都嵌入到项目程序集里的情况是不太可能发生的,以上示例仅作为demo给出。

当创建一个EmbeddedFileProvider时,请在其构造函数中传入一个程序集实例供其读取。

var embeddedProvider = new EmbeddedFileProvider(Assembly.GetEntryAssembly());

以上的代码片段描述了如何创建一个能访问当前工作程序集的EmbeddedFileProvider类型变量。

使用EmbeddedFileProvider更新示例项目代码后的输出结果如下:

Note 如上图所示,嵌入式资源不会公开目录。相反的,资源路径(经由资源的命名空间)会被嵌入到它的文件名中并以.作为分隔符。

Tip EmbeddedFileProvider构造器接受一个可选的baseNamespace参数,指定此参数将限定GetDirectoryContents方法调用该命名空间下的资源。

CompositeFileProvider

CompositeFileProvider联合IFileProvider实例公开了一个单一的接口,用以和来自多种provider的文件工作。当创建一个CompositeFileProvider时,你可以为它的构造函数传入一个或多个IFileProvider实例。

var physicalProvider = _hostingEnvironment.ContentRootFileProvider;
var embeddedProvider = new EmbeddedFileProvider(Assembly.GetEntryAssembly());
var compositeProvider = new CompositeFileProvider(physicalProvider, embeddedProvider);

使用包含物理式provider(在前)和嵌入式provider的CompositeFileProvider更新示例项目代码后的输出结果如下:

查看更改

IFileProviderWatch方法能用来查看一个或多个文件/目录的更改信息。Watch方法接受一个路径字符串,它也可以使用通配符模式来指定多个文件,Watch方法最终返回一个IChangeToken。这个token公开了一个HasChanged属性用以检视状态,公开了一个RegisterChangeCallback方法,此方法会在指定的路径字符串检测到更改时被调用。请注意每个更改token只调用其关联回调以响应单次更改。为了使监控持续,你可以使用如下所示的TaskCompletionSource方法,或者重建IChangeToken以响应更改。

在这个文章的示例中,无论何时当文本文件内容发生修改,按如下代码配置的console应用都会显示相应的信息。

using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Primitives;

namespace WatchConsole
{
    public class Program
    {
        private static PhysicalFileProvider _fileProvider = 
            new PhysicalFileProvider(Directory.GetCurrentDirectory());
        public static void Main(string[] args)
        {
            Console.WriteLine("Monitoring quotes.txt for changes (ctrl-c to quit)...");
            while (true)
            {
                MainAsync().GetAwaiter().GetResult();
            }
        }

        private static async Task MainAsync()
        {
            IChangeToken token = _fileProvider.Watch("quotes.txt");
            var tcs = new TaskCompletionSource<object>();
            token.RegisterChangeCallback(state => 
                ((TaskCompletionSource<object>)state).TrySetResult(null), tcs);
            await tcs.Task.ConfigureAwait(false);
            Console.WriteLine("quotes.txt changed");
        }
    }
}

以下是执行过几次文本保存动作后的运行结果截图:

Note 有一些文件系统,例如Docker容器和网络共享,可能不能很可靠地发送更改通知。设置环境变量DOTNET_USE_POLLINGFILEWATCHER的值为1true,使得每四秒轮询一次文件系统的变更。

通配符模式

文件系统路径规则使用叫作globbing patterns的通配符模式,这类简单模式可以被用来指定文件组。这两个通配符分别是***

* *表示在当前文件夹级别上匹配任何文件名称或文件扩展名。匹配以文件路径字符串中的/.符号结尾。

** **表示在多个目录级别上匹配任何文件名称或文件扩展名。可用于在一个目录层次结构中递归地匹配多个文件。

通配符模式示例

directory/file.txt

在指定的文件夹中匹配指定的文件。

directory/*.txt

在指定的文件夹中匹配所有以.txt扩展名结尾的文件。

directory/*/project.json

在指定的directory文件夹下的一级目录位置中匹配所有符合project.json名称的文件

directory/**/*.txt

在指定的directory文件夹下的所有位置中匹配所有以.txt扩展名结尾的文件。

在ASP.NET Core中File Provider的用法

ASP.NET Core有几个组件使用file provider功能。IHostingEnvironmentIFileProvider接口类型公开了应用的目录根和Web根。静态文件中间件使用file provider来定位静态文件。Razor更是大量使用IFileProvider来定位视图。Dotnet的发布功能使用file provider和通配符模式来指定需要跟随发布的文件。

在应用程序中使用的建议

如果你的ASP.NET Core应用需要访问文件系统,你可以通过依赖注入创建IFileProvider接口实例,然后再通过前文所示的相应方法执行访问。当应用启动的时候,这些方法允许你一次性配置provider并减少应用初始化时生成的实例类型数目。