重新整理 .net core 實踐篇—————檔案系統[二十二]

不問前世發表於2021-06-17

前言

簡單介紹一下檔案系統。

正文

檔案系統,主要是下面3個介面組成:

  1. IFileProvider

  2. IFileInfo

  3. IDirectoryContents

那麼他們的實現是:

  1. physicalFileProvider 物理檔案提供程式

  2. enbeddedFileProvider 嵌入式檔案提供程式

  3. compositeFileProvider 組合檔案提供程式

/// <summary>A read-only file provider abstraction.</summary>
public interface IFileProvider
{
/// <summary>Locate a file at the given path.</summary>
/// <param name="subpath">Relative path that identifies the file.</param>
/// <returns>The file information. Caller must check Exists property.</returns>
IFileInfo GetFileInfo(string subpath);

/// <summary>Enumerate a directory at the given path, if any.</summary>
/// <param name="subpath">Relative path that identifies the directory.</param>
/// <returns>Returns the contents of the directory.</returns>
IDirectoryContents GetDirectoryContents(string subpath);

/// <summary>
/// Creates a <see cref="T:Microsoft.Extensions.Primitives.IChangeToken" /> for the specified <paramref name="filter" />.
/// </summary>
/// <param name="filter">Filter string used to determine what files or folders to monitor. Example: **/*.cs, *.*, subFolder/**/*.cshtml.</param>
/// <returns>An <see cref="T:Microsoft.Extensions.Primitives.IChangeToken" /> that is notified when a file matching <paramref name="filter" /> is added, modified or deleted.</returns>
IChangeToken Watch(string filter);
}

IFileProvider A read-only file provider abstraction

只讀檔案提供程式抽象。

那麼這個三個方法分別是:

  1. GetFileInfo 獲取指定檔案

  2. GetDirectoryContents 獲取指定目錄,subpath是相對路徑

看下其返回值IDirectoryContents:

  public interface IDirectoryContents : IEnumerable<IFileInfo>, IEnumerable
  {
    /// <summary>True if a directory was located at the given path.</summary>
    bool Exists { get; }
  }

其繼承IEnumerable說明其實可以遍歷檔案的,同時有一個Exists 方法,判斷這個目錄是否存在

IFileInfo 可以獲取的資訊如下:

  public interface IFileInfo
  {
    /// <summary>
    /// True if resource exists in the underlying storage system.
    /// </summary>
    bool Exists { get; }

    /// <summary>
    /// The length of the file in bytes, or -1 for a directory or non-existing files.
    /// </summary>
    long Length { get; }

    /// <summary>
    /// The path to the file, including the file name. Return null if the file is not directly accessible.
    /// </summary>
    string PhysicalPath { get; }

    /// <summary>
    /// The name of the file or directory, not including any path.
    /// </summary>
    string Name { get; }

    /// <summary>When the file was last modified</summary>
    DateTimeOffset LastModified { get; }

    /// <summary>
    /// True for the case TryGetDirectoryContents has enumerated a sub-directory
    /// </summary>
    bool IsDirectory { get; }

    /// <summary>
    /// Return file contents as readonly stream. Caller should dispose stream when complete.
    /// </summary>
    /// <returns>The file stream</returns>
    Stream CreateReadStream();
  }

這裡面有一個IsDirectory 屬性,看來目錄也是當成了檔案的,這樣設計可能是相容linux的原因吧,一切皆檔案。

  1. 第三個 Watch,看到IChangeToken 就知道肯定是搞監聽的。

測試:

static void Main(string[] args)
{
	IFileProvider provider = new PhysicalFileProvider(AppDomain.CurrentDomain.BaseDirectory);

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

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

	Console.ReadLine();
}

列印根目錄全部檔案。

檢視內嵌檔案:

在根目錄建立emb.html,設定為內嵌。

裡面為:

<!DOCTYPE html>

<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="utf-8" />
    <title></title>
</head>
<body>
    這個是一個內嵌檔案
</body>
</html>

測試程式碼:

IFileProvider provider = new EmbeddedFileProvider(typeof(Program).Assembly);

var html = provider.GetFileInfo("emb.html");

Console.ReadLine();

結果:

下面看下組合檔案提供程式:

static void Main(string[] args)
{
	IFileProvider provider1 = new PhysicalFileProvider(AppDomain.CurrentDomain.BaseDirectory);

	IFileProvider provider2 = new EmbeddedFileProvider(typeof(Program).Assembly);

	IFileProvider providerSum = new CompositeFileProvider(provider1, provider2);

	var contents = providerSum.GetDirectoryContents("/");
	foreach (var item in contents)
	{
		Console.WriteLine(item.Name);
	}

	Console.ReadLine();
}

簡單看下這個組合是如何實現的。

public CompositeFileProvider(params IFileProvider[] fileProviders)
{
  this._fileProviders = fileProviders ?? Array.Empty<IFileProvider>();
}

先是放置在一個陣列中,名為_fileProviders 。

看下GetDirectoryContents:

public IDirectoryContents GetDirectoryContents(string subpath)
{
  return (IDirectoryContents) new CompositeDirectoryContents((IList<IFileProvider>) this._fileProviders, subpath);
}

看下CompositeDirectoryContents:

public CompositeDirectoryContents(IList<IFileProvider> fileProviders, string subpath)
{
  if (fileProviders == null)
	throw new ArgumentNullException(nameof (fileProviders));
  this._fileProviders = fileProviders;
  this._subPath = subpath;
}

然後遍歷的時候呼叫:

/// <summary>Creates an enumerator for all files in all providers given.
/// Ensures each item in the collection is distinct.</summary>
/// <returns>An enumerator over all files in all given providers</returns>
public IEnumerator<IFileInfo> GetEnumerator()
{
  this.EnsureFilesAreInitialized();
  return (IEnumerator<IFileInfo>) this._files.GetEnumerator();
}

檢視EnsureFilesAreInitialized:

private void EnsureFilesAreInitialized()
{
  this.EnsureDirectoriesAreInitialized();
  if (this._files != null)
	return;
  this._files = new List<IFileInfo>();
  HashSet<string> stringSet = new HashSet<string>();
  for (int index = 0; index < this._directories.Count; ++index)
  {
	foreach (IFileInfo fileInfo in (IEnumerable<IFileInfo>) this._directories[index])
	{
	  if (stringSet.Add(fileInfo.Name))
		this._files.Add(fileInfo);
	}
  }
}

繼續檢視EnsureDirectoriesAreInitialized:

private void EnsureDirectoriesAreInitialized()
{
  if (this._directories != null)
	return;
  this._directories = new List<IDirectoryContents>();
  foreach (IFileProvider fileProvider in (IEnumerable<IFileProvider>) this._fileProviders)
  {
	IDirectoryContents directoryContents = fileProvider.GetDirectoryContents(this._subPath);
	if (directoryContents != null && directoryContents.Exists)
	{
	  this._exists = true;
	  this._directories.Add(directoryContents);
	}
  }
}

這樣看來就是每隔檔案的provider 呼叫GetDirectoryContents,然後再次遍歷套娃把檔案資訊都放入List _files,然後遍歷的時候遍歷的就是_files。

功能挺少的,這樣看來必須是同一個subpath,只能在外面根目錄做文章。

以上只是個人整理,如有錯誤,望請指點。

下一節路由與終結點。

相關文章