ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

NetCore 入门 (二) : 文件系统

2022-08-27 15:35:40  阅读:157  来源: 互联网

标签:IChangeToken 文件 入门 NetCore 文件系统 txt root public string


1. Quick Start

ASP.NET Core应用具有很多读取文件的场景,如读取配置文件、静态Web资源文件(js/css/image)、MVC应用的View文件、以及直接编译到程序集中的内嵌资源文件。这些文件的读取都需要一个IFileProvider对象。

IFileProvider对象构建了一个抽象的文件系统,不仅提供了统一的API来读取各种类型的文件,还能及时监控目标文件的变化。

1.1 安装NuGet包

Microsoft.Extensions.FileProviders.Abstractions // 抽象依赖包
Microsoft.Extensions.FileProviders.Embedded // 嵌入式文件系统
Microsoft.Extensions.FileProviders.Physical // 物理文件系统
Microsoft.Extensions.FileProviders.Composite // 复合型文件系统

1.2 示例1 - 遍历目录

现有文件目录如下所示:

F:/root
├─ dir1/
│  ├─ footbar/
│  │  ├─ bar.txt
│  │  └─ foo.txt
│  └─ baz.txt
├─dir2/
│  └─ quz.txt
├─ bzx.txt
└─ coding.txt

现在以F:/root为根目录,构建物理文件系统。

using (var fileProvider = new PhysicalFileProvider(@"F:\root"))
{
    Console.WriteLine("获取根目录");
    IDirectoryContents directoryContents = fileProvider.GetDirectoryContents("/");// 获取根目录

    foreach (var item in directoryContents)
    {
        Console.WriteLine($"Name:{item.Name},   IsDirectory:{item.IsDirectory},   {item.PhysicalPath}");
    }

    Console.WriteLine("获取目录dir1");
    directoryContents = fileProvider.GetDirectoryContents("/dir1");// 获取目录dir1

    foreach (var item in directoryContents)
    {
        Console.WriteLine($"Name:{item.Name},   IsDirectory:{item.IsDirectory},   {item.PhysicalPath}");
    }
}

输出结果如下:

获取根目录
Name:bzx.txt,   IsDirectory:False,   F:\root\bzx.txt
Name:coding.txt,   IsDirectory:False,   F:\root\coding.txt
Name:dir1,   IsDirectory:True,   F:\root\dir1
Name:dir2,   IsDirectory:True,   F:\root\dir2
获取目录dir1
Name:baz.txt,   IsDirectory:False,   F:\root\dir1\baz.txt
Name:footbar,   IsDirectory:True,   F:\root\dir1\footbar

1.3 示例2 - 读取文件内容

using (var fileProvider = new PhysicalFileProvider(@"F:\root"))
{
    IFileInfo fileInfo = fileProvider.GetFileInfo("dir1/baz.txt");

    string content = "";

    using (var stream = fileInfo.CreateReadStream())
    {
        byte[] buffer = new byte[stream.Length];

        stream.Read(buffer, 0, buffer.Length);

        content = Encoding.Default.GetString(buffer);
    }

    Console.WriteLine(content);
}

1.4 示例3 - 监控文件变化

var fileProvider = new PhysicalFileProvider(@"F:\root");

ChangeToken.OnChange(() => fileProvider.Watch("dir1/baz.txt"), callback);

void callback()
{
    IFileInfo fileInfo = fileProvider.GetFileInfo("dir1/baz.txt");

    string content = "";

    using (var stream = fileInfo.CreateReadStream())
    {
        byte[] buffer = new byte[stream.Length];

        stream.Read(buffer, 0, buffer.Length);

        content = Encoding.Default.GetString(buffer);
    }

    Console.WriteLine(content);
}

int i = 0;
while (i++ < 10)
{
    File.WriteAllText(@"F:\root\dir1\baz.txt", DateTime.Now.ToString());

    await Task.Delay(1000);
}

输出结果:

2020/6/5 16:23:24
2020/6/5 16:23:24
2020/6/5 16:23:25
2020/6/5 16:23:26
2020/6/5 16:23:27
2020/6/5 16:23:28
2020/6/5 16:23:29
2020/6/5 16:23:30
2020/6/5 16:23:31
2020/6/5 16:23:32
2020/6/5 16:23:33

2. 模型解析

2.1 Globbing Pattern

Globbing Pattern表达式体现为一个文件路径,用于筛选目标目录或文件。Globbing Pattern表达式只包含2个通配符:

  • *: 代表所有不包含路径分隔符的所有字符。
  • **: 代表路径分隔符在内的所有字符。

下面列举几种常见的Globbing Pattern表达式:

Globbing Pattern表达式 匹配的文件
src/foobar/foo/settings.* 目录“src/foobar/foo/”下所有名为“settings”的文件,如settings.json、settigns.ini、settings.xml等
src/foobar/foo/*.cs 目录“src/foobar/foo/”下所有.cs文件
src/foobar/foo/ * .* 目录“src/foobar/foo/”下的所有文件
src/**/*.cs 目录"src"及其子目录下的所有.cs文件

2.2 核心接口

接口关系图

IFileProvider

IFileProvider是文件系统的核心接口,该接口定义了3个方法,体现了文件系统的3个基本功能。

public interface IFileProvider
{
    // 遍历目录
    IDirectoryContents GetDirectoryContents(string subpath);
    // 读取文件
    IFileInfo GetFileInfo(string subpath);
    // 监控文件变化
    IChangeToken Watch(string filter);
}
  • Watch方法监控目录或文件的变化。该方法接收一个Globbing Pattern字符串参数filter,用来筛选需要监控的目标文件。

IFileInfo

public interface IFileInfo
{
    bool Exists { get; }

    long Length { get; }

    string PhysicalPath { get; }

    string Name { get; }

    DateTimeOffset LastModified { get; }

    bool IsDirectory { get; }

    Stream CreateReadStream();
}
  1. 虽然文件系统采用目录组织文件,但不论文件还是目录都都通过一个IFileInfo对象表示。至于具体是目录还是文件,要通过IsDirectory属性来判断。
  2. 一般来说,不论指定的文件是否存在,GetFileInfo方法总是返回一个具体的IFileInfo对象。目标文件的存在与否由Exists属性决定。
  3. 可以借助CreateReadStream方法读取文件的内容。

IDirectoryContents

public interface IDirectoryContents : IEnumerable<IFileInfo>
{
    bool Exists { get; }
}

一个IDirectoryContents对象实际上是一组IFileInfo对象的集合。和GetFileInfo方法一样,不论指定的目录是否存在,GetDirectoryContents方法总是返回一个具体的IDirectoryContents对象,它的Exists属性可以确定目录是否存在。

IChangeToken

public interface IChangeToken
{
    // Gets a value that indicates if a change has occurred.
    bool HasChanged { get; }

    // Indicates if this token will pro-actively raise callbacks. If false, the token
    // consumer must poll Microsoft.Extensions.Primitives.IChangeToken.HasChanged to
    // detect changes.
    bool ActiveChangeCallbacks { get; }

    IDisposable RegisterChangeCallback(Action<object> callback, object state);
}

IChangeToken对象是一个监控数据的“令牌”,它能够在数据发生变更时及时对外发出一个通知。

我们可以调用RegisterChangeCallback方法注册一个回调函数,在数据发生变化时自动做出响应。该函数的返回值是一个IDisposable对象,调用Dispose方法可以解除注册的回调。

2.3 ChangeToken

IChangeToken 类图

CancellationChangeToken

.Net Core提供了若干原生的IChangeToken接口实现,我们最常用的是CancellationChangeToken。它的实现原理很简单,就是借助System.Threading.CancellationToken对象来发送通知的。

public class CancellationChangeToken : IChangeToken
{
    private readonly CancellationToken _token;
    // 参数:
    //   cancellationToken:
    //     The System.Threading.CancellationToken
    public CancellationChangeToken(CancellationToken cancellationToken)
        => _token = cancellationToken;

    public bool ActiveChangeCallbacks { get; } = true;
    public bool HasChanged { get; } = _token.IsCancellationRequested;

    public IDisposable RegisterChangeCallback(Action<object> callback, object state)
        => _token.Register(callback, state);
}

CompositeChangeToken

public class CompositeChangeToken : IChangeToken
{

    public CompositeChangeToken(IReadOnlyList<IChangeToken> changeTokens);

    public IReadOnlyList<IChangeToken> ChangeTokens { get; }
    public bool HasChanged { get; }
    public bool ActiveChangeCallbacks { get; }

    public IDisposable RegisterChangeCallback(Action<object> callback, object state);
}
  1. CompositeChangeToken代表由多个IChangeToken组合而成的复合型IChangeToken对象, 在创建CompositeChangeToken时需要提供这些IChangeToken
  2. 对于一个CompositeChangeToken对象来说,只要组成它的任何一个IChangeToken发生改变,其HasChanged属性就返回True, 并调用其注册的回调函数。
  3. 任何一个IChangeTokenActiveChangeCallbacks属性返回True, 则ActiveChangeCallbacks属性返回True。

ChangeToken

我们可以使用IChangeTokenRegisterChangeCallback方法来注册回调函数。但是这种方法存在一点不足:一个IChangeToken对象仅能发送一次通知。也就是说,如果想要连续不断的检测文件变化,就必须多次调用Watch方法并注册回调函数。

好在ChangeToken类帮我们解决了这个问题:OnChange方法会在一个IChangeToken对象发送通知后再次添加监控。

public static class ChangeToken
{
    public static IDisposable OnChange(Func<IChangeToken> changeTokenProducer, Action changeTokenConsumer);
    public static IDisposable OnChange<TState>(Func<IChangeToken> changeTokenProducer, 
        ction<TState> changeTokenConsumer, TState state);
}

使用示例:

ChangeToken.OnChange(() => fileProvider.Watch("dir1/*.txt"), ()=>{ ... });

3. 物理文件系统

类关系图

3.1 PhysicalFileProvider

物理文件系统是由PhysicalFileProvider构建的。一个PhysicalFileProvider总是映射到一个具体的物理目录上,并把该目录作为文件系统的根目录。

public class PhysicalFileProvider : IFileProvider, IDisposable
{
    public PhysicalFileProvider(string root);
    public PhysicalFileProvider(string root, ExclusionFilters filters);

    public string Root { get; }

    public IDirectoryContents GetDirectoryContents(string subpath);
    public IFileInfo GetFileInfo(string subpath);
    public IChangeToken Watch(string filter);

    ...
}

3.2 获取文件

PhysicalFileInfo

如果存在指定的物理文件,则GetFileInfo方法返回一个PhysicalFileInfo对象。该对象实际上是对System.IO.FileInfo的封装。

public class PhysicalFileInfo : IFileInfo
{
    public PhysicalFileInfo(FileInfo info);
    ...
}

NotFoundFileInfo

PhysicalFileProvider会将以下场景视为“目标文件不存在”,并让GetFileInfo方法返回NotFoundFileInfo对象。

  • 确实不存在一个物理文件与指定的路径相匹配。
  • 指定的是绝对路径。
  • 路径指向一个隐藏文件。
public class NotFoundFileInfo : IFileInfo
{
    public bool Exists { get; } = false;
    ...
}

PhysicalDirectoryInfo

如果指向的路径代表一个目录,则GetFileInfo方法返回PhysicalDirectoryInfo对象。该对象是对System.IO.DirectoryInfo的封装。

public class PhysicalDirectoryInfo : IFileInfo
{
    public PhysicalDirectoryInfo(DirectoryInfo info);
    ...
}

3.3 遍历目录

PhysicalDirectoryContents

调用GetDirectoryContents方法时,如果路径指向一个存在的目录,则返回PhysicalDirectoryContents对象。

public class PhysicalDirectoryContents : IDirectoryContents, IEnumerable<IFileInfo>
{
    public PhysicalDirectoryContents(string directory);

    public bool Exists { get; } 

    ...
}

NotFoundDirectoryContents

如果路径指向一个不存在的目录,或者是一个绝对路径,则返回NotFoundDirectoryContents对象。

public class NotFoundDirectoryContents : IDirectoryContents, IEnumerable<IFileInfo>
{
    public static NotFoundDirectoryContents Singleton { get; }
    public bool Exists { get; } = false;
    ...
}

3.4 文件监控

PhysicalFilesWatcher

PhysicalFileProvider利用PhysicalFilesWatcher来实现文件监控。文件或目录的任何变化(创建、修改、重命名和删除)都会实时地反映到IChangeToken对象上。

public class PhysicalFilesWatcher : IDisposable
{
    public PhysicalFilesWatcher(string root, FileSystemWatcher fileSystemWatcher, bool pollForChanges);
    public IChangeToken CreateFileChangeToken(string filter);
    ...
}

从构造函数可以看出,PhysicalFilesWatcher是利用System.IO.FileSystemWatcher来实现文件监控功能的。

4. 嵌入式文件系统

4.1 内嵌资源介绍

4.1.1 添加内嵌资源

如果需要把资源内嵌至程序集中,就需要修改当前项目的.csproj文件。通过添加<EmbeddedResource>,把对应的文件包含进来。

现将如下目录结构的文件内嵌至程序集中(root位于项目的根目录下):

root
├─ dir1/
│  ├─ footbar/
│  │  └─ bar.txt
│  └─ baz.txt
├─dir2/
│  └─ quz.txt
└─ coding.txt

修改项目的.csproj文件:

<Project Sdk="Microsoft.NET.Sdk">
...

<ItemGroup>
  <EmbeddedResource Include="root\**"  Exclude=""/>
</ItemGroup>

...
</Project>

<EmbeddedResource>具有两个属性,每个属性的值都可以使用Globbing Pattern表达式:

  • Include: 添加内嵌资源文件
  • Exclude: 排除不符合要求的文件。

4.1.2 文件命名空间

每个程序集都有一个清单文件(Manifest),其作用是记录组成程序集的所有文件。如果将上述文件文件内嵌到程序集中,清单文件将采用如下方式来记录它们。

.mresource public App.root.dir1.footbar.bar.txt
{
    // Offset: 0x00000000 Length: 0x0000000C
}
.mresource public App.root.dir1.baz.txt
{
    // Offset: 0x00000020 Length: 0x0000000C
}
.mresource public App.root.dir2.quz.txt
{
    // Offset: 0x00000030 Length: 0x0000000C
}
.mresource public App.root.coding.txt
{
    // Offset: 0x00000050 Length: 0x0000000C
}

虽然文件在原始项目中具有层次化的目录结构,但是在编译生成的程序集中,目录结构将不复存在,所有的文件按照命名空间的形式存放在同一个容器中。在生成程序集的过程中,编译器会按照一定的规则对文件进行重命名。

其规则是:{BaseNamespace}.{Path},目录分隔符(“/”)将替换成“.”。

::: tip BaseNamespace
这里的BaseNamespace不是程序集的名称,而是项目设置的默认命名空间的名称。
BaseNamespace
:::

4.1.3 读取资源文件

表示程序集的Assembly对象定义了如下几个方法,用来操作内嵌资源文件。

public abstract class Assembly
{
    // 获取记录在程序集Manifest中的资源文件名
    public virtual string[] GetManifestResourceNames();
    // 获取指定资源文件的描述信息
    public virtual ManifestResourceInfo? GetManifestResourceInfo(string resourceName);
    // 返回一个读取文件内容的stream对象
    public virtual Stream? GetManifestResourceStream(string name);
    ...
}

4.2 EmbeddedFileProvider

通过EmbeddedFileProvider构建的文件系统没有目录层级的概念,我们可以认为所有的资源文件都保存在根目录下。

public class EmbeddedFileProvider : IFileProvider
{
    public EmbeddedFileProvider(Assembly assembly);
    public EmbeddedFileProvider(Assembly assembly, string baseNamespace);

    public IDirectoryContents GetDirectoryContents(string subpath);
    public IFileInfo GetFileInfo(string subpath);
    public IChangeToken Watch(string pattern);
}

::: tip baseNamespace
构建EmbeddedFileProvider时如果没有指定baseNamespace, 则采用程序集的名称作为命名空间。
:::

示例:

Console.WriteLine("================默认baseNamespace");
var provider = new EmbeddedFileProvider(Assembly.GetExecutingAssembly());

var contents = provider.GetDirectoryContents("/");

foreach (var info in contents)
{
    Console.WriteLine(info.Name);
}

Console.WriteLine("================设置baseNamespace");
provider = new EmbeddedFileProvider(Assembly.GetExecutingAssembly(), "FileProviderTutorial.root.dir1");

contents = provider.GetDirectoryContents("/");

foreach (var info in contents)
{
    Console.WriteLine(info.Name);
}

输出:

================默认baseNamespace
root.bzx.txt
root.dir1.baz.txt
root.dir1.footbar.bar.txt
root.dir2.quz.txt
================设置baseNamespace
baz.txt
footbar.bar.txt

4.3 ManifestEmbeddedFileProvider

EmbeddedFileProvider的功能基本一致,不同点在于:它根据清单来重建嵌入文件的原始路径。也就是说,ManifestEmbeddedFileProvider构建的文件系统是有目录结构的。

public class ManifestEmbeddedFileProvider : IFileProvider
{
    public ManifestEmbeddedFileProvider(Assembly assembly);
    public ManifestEmbeddedFileProvider(Assembly assembly, string root);

    public IDirectoryContents GetDirectoryContents(string subpath);
    public IFileInfo GetFileInfo(string subpath);
    public IChangeToken Watch(string filter);
}

5 其他文件系统

5.1 复合型文件系统

5.1.1 CompositeFileProvider

CompositeFileProvider代表由多个IFileProvider构建的复合型文件系统。当创建一个CompositeFileProvider对象实例时,需要提供一组IFileProvider对象。

public class CompositeFileProvider : IFileProvider
{
    public CompositeFileProvider(params IFileProvider[] fileProviders);
    public CompositeFileProvider(IEnumerable<IFileProvider> fileProviders);

    public IEnumerable<IFileProvider> FileProviders { get; }

    public IDirectoryContents GetDirectoryContents(string subpath);
    public IFileInfo GetFileInfo(string subpath);
    public IChangeToken Watch(string pattern);
}

5.1.2 GetFileInfo

当调用GetFileInfo方法时,它会遍历这些IFileProvider对象,直到找到一个与路径相匹配的文件。如果所有的IFileProvider都不能提供这个文件,则返回NotFoundFileInfo对象。

由于遍历的顺序取决于构建CompositeFileProvider对象时提供的IFileProvider的顺序,所以如果对IFileProvider具有优先级的要求,应该将优先级高的IFileProvider对象放在前面。

5.1.3 CompositeDirectoryContents

GetDirectoryContents方法返回一个复合型的IDirectoryContents,即CompositeDirectoryContents。如果多个IFileProvider中存在路径相同的文件,与GetFileInfo一样,总是从优先提供的IFileProvider中提取。

public class CompositeDirectoryContents : IDirectoryContents, IEnumerable<IFileInfo>
{
    public CompositeDirectoryContents(IList<IFileProvider> fileProviders, string subpath);
    public bool Exists { get; }
}

5.1.4 CompositeChangeToken

Watch方法也返回一个复合型对象,即CompositeChangeToken。这个对象由所有的IFileProvider调用Watch方法来构建,所以它能监控所有的IFileProvider对象对应的文件系统。

public class CompositeChangeToken : IChangeToken
{
    public CompositeChangeToken(IReadOnlyList<IChangeToken> changeTokens);

    ...
}

5.2 空文件系统

NullFileProvider代表一个不包含任何内容的空文件系统。

public class NullFileProvider : IFileProvider
{
    public IDirectoryContents GetDirectoryContents(string subpath) => new NotFoundDirectoryContents();
    public IFileInfo GetFileInfo(string subpath) => new NotFoundFileInfo(subpath);
    public IChangeToken Watch(string filter) => NullChangeToken.Singleton;
}
  • GetDirectoryContents 返回NotFoundDirectoryContents对象。
  • GetFileInfo 返回NotFoundFileInfo对象。
  • Watch返回NullChangeToken对象。

标签:IChangeToken,文件,入门,NetCore,文件系统,txt,root,public,string
来源: https://www.cnblogs.com/renzhsh/p/16630604.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有