By Steve Smith
ASP.NET Core通过对File Providers的使用实现了对文件系统访问的抽象。
File Providers是文件系统之上的一层抽象。它的主要接口是IFileProvider
。IFileProvider
公开了相应方法用来获取文件信息(IFileInfo
), 目录信息(IDirectoryContents
),以及设置更改通知(通过使用一个IChangeToken
)。
IFileInfo
接口提供了操作单个文件和目录的方法和属性。它有两个boolean
属性,Exists
和IsDirectory
,以及两个描述文件的两个属性Name
和Length
(按字节),还包括一个LastModified
日期属性。你还可以通过CreateReadStream
方法读取文件内容。
有三种对于IFileProvider
的实现可供选择:物理式,嵌入式和复合式。物理式用于访问实际系统中的文件。嵌入式用于访问嵌入在程序集中的文件。 复合式则是对前两种方式的组合使用。
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
用于访问嵌入到程序集中的文件。在.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
联合IFileProvider
实例公开了一个单一的接口,用以和来自多种provider的文件工作。当创建一个CompositeFileProvider
时,你可以为它的构造函数传入一个或多个IFileProvider
实例。
var physicalProvider = _hostingEnvironment.ContentRootFileProvider;
var embeddedProvider = new EmbeddedFileProvider(Assembly.GetEntryAssembly());
var compositeProvider = new CompositeFileProvider(physicalProvider, embeddedProvider);
使用包含物理式provider(在前)和嵌入式provider的CompositeFileProvider
更新示例项目代码后的输出结果如下:
IFileProvider
的Watch
方法能用来查看一个或多个文件/目录的更改信息。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
的值为1
或true
,使得每四秒轮询一次文件系统的变更。
文件系统路径规则使用叫作globbing patterns的通配符模式,这类简单模式可以被用来指定文件组。这两个通配符分别是*
和**
。
*
*
表示在当前文件夹级别上匹配任何文件名称或文件扩展名。匹配以文件路径字符串中的/
和.
符号结尾。
**
**
表示在多个目录级别上匹配任何文件名称或文件扩展名。可用于在一个目录层次结构中递归地匹配多个文件。
directory/file.txt
在指定的文件夹中匹配指定的文件。
directory/*.txt
在指定的文件夹中匹配所有以.txt
扩展名结尾的文件。
directory/*/project.json
在指定的directory
文件夹下的一级目录位置中匹配所有符合project.json
名称的文件
directory/**/*.txt
在指定的directory
文件夹下的所有位置中匹配所有以.txt
扩展名结尾的文件。
ASP.NET Core有几个组件使用file provider功能。IHostingEnvironment
以IFileProvider
接口类型公开了应用的目录根和Web根。静态文件中间件使用file provider来定位静态文件。Razor更是大量使用IFileProvider
来定位视图。Dotnet的发布功能使用file provider和通配符模式来指定需要跟随发布的文件。
如果你的ASP.NET Core应用需要访问文件系统,你可以通过依赖注入创建IFileProvider
接口实例,然后再通过前文所示的相应方法执行访问。当应用启动的时候,这些方法允许你一次性配置provider并减少应用初始化时生成的实例类型数目。