前言

在开发过程中,肯定避免不了读取文件操作,比如读取配置文件、上传和下载文件、Web中html、js、css、图片等静态资源的访问;在配置文件读取章节中有说到,针对不同配置源数据读取由对应的IConfigurationProvider进行读取,其实读取文件也是一样,针对于不同类型(物理文件、嵌入文件、云端文件等)文件,就由对应的IFileProvider的实现进行读取,下面详细说说;

正文

由于通过IFileProvider将目录文件进行抽象化,统一规范读取操作,使得读取不同地方的文件就显得更加方便,如物理文件、嵌入文件,只要有对应的实现即可;而框架针对物理文件和嵌入文件已经进行了具体实现,如下:

  • PhysicalFileProvider:物理文件提供程序,用来读取物理文件,就是平时使用的文件,不管是扩展名是什么;
  • EmbeddedFileProvider:嵌入文件提供程序,用来读取嵌入文件,就是程序编译时嵌入到程序集内部的文件,就像资源文件一样;
  • CompositeFileProvider:组合提供程序,同时可以读取物理文件和嵌入文件,就是可以指定多种数据源,这样的好处就是像操作同一个数据源一样;后续也可以与自定义的提供程序进行组合;

为了避免直接扒代码懵圈,先来个控制台例子,体验一下以上xxxProvider的使用:

运行结果:

读取物理文件是不是很简单,其实就是创建了一个PhysicalFileProvider对象时指定了一个路径,然后就能很方便的获取到对应目录下的信息;

嵌入文件也是如此,只需指定对应程序集即可(因为嵌入文件已经编译到程序集中),如下优化代码:

运行结果如下:

同样也是使用很简单,只是在创建EmbeddedFileProvider对象时指定一下对应的程序集即可,后续便可以用统一的方式进行文件和目录操作;

组合提供程序的目的就是将不同提供程序整合,就像使用同一个源一样,如下:

当然,按老套路走,不能用用就行了,继续扒扒代码,先看看IFileProvider:

namespace Microsoft.Extensions.FileProviders
{
// IFileProvider定义的三个方法其实就是其对应的三大功能
public interface IFileProvider
{
// 获取指定文件的信息,之后可以文件进行读取操作
IFileInfo GetFileInfo(string subpath);
// 获取指定目录下所有内容
IDirectoryContents GetDirectoryContents(string subpath);
// 用于监听文件改变
IChangeToken Watch(string filter);
}
}

再来看看返回的IFileInfo和IDirectoryContents :

namespace Microsoft.Extensions.FileProviders
{
public interface IFileInfo
{
// 标识是否存在
bool Exists
{
get;
}
// 文件大小,如果不存在或是目录,这个值就是-1
long Length
{
get;
}
// 对应的物理路径,其实就是文件的实际路径
string PhysicalPath
{
get;
}
// 文件名字
string Name
{
get;
}
// 文件最后的修改时间
DateTimeOffset LastModified
{
get;
}
// 标识是否是目录
bool IsDirectory
{
get;
}
// 返回的留可以进行文件读取
Stream CreateReadStream();
} // 其他信息继承了IFileInfo信息
public interface IDirectoryContents : IEnumerable<IFileInfo>, IEnumerable
{
// 标识指定目录是否存在
bool Exists
{
get;
}
}
}

IChangeToken 之前在配置文件监听的时候有提到过,是用来监听到文件改变时进行发送通知的,这里就不深入了,感兴趣的小伙伴可以研究研究;

PhysicalFileProvider和EmbeddedFileProvider两个挑PhysicalFileProvider这个看看,后者小伙伴私下去扒吧:

namespace Microsoft.Extensions.FileProviders
{
// 这里只挑了几个关键方法说明,其他属性和方法删除
public class PhysicalFileProvider : IFileProvider, IDisposable
{
// 判断路径是否在指定的根路径下
private bool IsUnderneathRoot(string fullPath)
{
return fullPath.StartsWith(Root, StringComparison.OrdinalIgnoreCase);
}
// 获取指定路径文件的FileInfo信息
public IFileInfo GetFileInfo(string subpath)
{
// 判断路径是否处匹配
if (string.IsNullOrEmpty(subpath) || PathUtils.HasInvalidPathChars(subpath))
{
return new NotFoundFileInfo(subpath);
}
// 判断指定的路径是否是在根目录下
subpath = subpath.TrimStart(_pathSeparators);
if (Path.IsPathRooted(subpath))
{
return new NotFoundFileInfo(subpath);
}
// 获取全路径,因为一般在外面操作是根据相对路径进行操作
string fullPath = GetFullPath(subpath);
if (fullPath == null)
{
return new NotFoundFileInfo(subpath);
}
// 构建了一个文件信息,包含文件的的操作和属性;
FileInfo fileInfo = new FileInfo(fullPath);
if (FileSystemInfoHelper.IsExcluded(fileInfo, _filters))
{
return new NotFoundFileInfo(subpath);
}
// 封装成PhysicalFileInfo对象
return new PhysicalFileInfo(fileInfo);
}
// 获取指定目录下的所有内容
public IDirectoryContents GetDirectoryContents(string subpath)
{
try
{
// 路径校验和上面一样
if (subpath == null || PathUtils.HasInvalidPathChars(subpath))
{
return NotFoundDirectoryContents.Singleton;
} subpath = subpath.TrimStart(_pathSeparators);
if (Path.IsPathRooted(subpath))
{
return NotFoundDirectoryContents.Singleton;
} string fullPath = GetFullPath(subpath);
if (fullPath == null || !Directory.Exists(fullPath))
{
return NotFoundDirectoryContents.Singleton;
}
// 封装为PhysicalDirectoryContents对象
return new PhysicalDirectoryContents(fullPath, _filters);
}
catch (DirectoryNotFoundException)
{
}
catch (IOException)
{
} return NotFoundDirectoryContents.Singleton;
}
// 用监听文件改变的,通过文件匹配模式来指定需要监控的文件
public IChangeToken Watch(string filter)
{
if (filter == null || PathUtils.HasInvalidFilterChars(filter))
{
return NullChangeToken.Singleton;
} filter = filter.TrimStart(_pathSeparators);
return FileWatcher.CreateFileChangeToken(filter);
}
}
}

以上GetDirectoryContents和GetFileInfo分别返回的PhysicalDirectoryContents和PhysicalFileInfo才是关键,进去瞅瞅:

public class PhysicalDirectoryContents : IDirectoryContents, IEnumerable<IFileInfo>, IEnumerable
{
// 用于存放指定目录下的全部内容的
private IEnumerable<IFileInfo> _entries;
// 判断指定目录是否存在
public bool Exists => Directory.Exists(_directory);
// 读取目录内容的关键方法
private void EnsureInitialized()
{
try
{
// 根据指定的目录,获取目录下的所有内容,将其保存在集合中
_entries = new DirectoryInfo(_directory).EnumerateFileSystemInfos().Where((Func<FileSystemInfo, bool>)((FileSystemInfo info) => !FileSystemInfoHelper.IsExcluded(info, _filters))).Select((Func<FileSystemInfo, IFileInfo>)delegate (FileSystemInfo info)
{
// 将取到的内容封装为PhysicalFileInfo对象
FileInfo fileInfo = info as FileInfo;
if (fileInfo != null)
{
return new PhysicalFileInfo(fileInfo);
}
// 将取到的内容封装为PhysicalFileInfo对象
DirectoryInfo directoryInfo = info as DirectoryInfo;
if (directoryInfo != null)
{
return new PhysicalDirectoryInfo(directoryInfo);
} throw new InvalidOperationException("Unexpected type of FileSystemInfo");
});
}
catch (Exception ex) when (ex is DirectoryNotFoundException || ex is IOException)
{
_entries = Enumerable.Empty<IFileInfo>();
}
}
}

PhysicalFileInfo

// 其实里面就是封装了IO文件操作的相关属性和操作
public class PhysicalFileInfo : IFileInfo
{
// 文件信息,就是平时咱们直接读取到文件的那些信息
private readonly FileInfo _info;
// 是否存在
public bool Exists => _info.Exists;
// 文件大小
public long Length => _info.Length;
// 文件的全路径
public string PhysicalPath => _info.FullName;
// 文件名称
public string Name => _info.Name;
// 文件的最后修改时间
public DateTimeOffset LastModified => _info.LastWriteTimeUtc;
// 默认就是false,所以这里只能对文件有效
public bool IsDirectory => false;
public PhysicalFileInfo(FileInfo info)
{
_info = info;
}
// 获取文件流,并设置了只读权限
public Stream CreateReadStream()
{
int bufferSize = 1;
// 这里就熟悉了,平时直接读取文件就是这样的
return new FileStream(PhysicalPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, bufferSize, FileOptions.SequentialScan | FileOptions.Asynchronous);
}
}

好了,到这其实差不多就明白了,至少知道为什么IFileInfo只能获取到文件件信息,目录信息获取不到;至少在写文件的时候不再懵逼的在想:为什么不能写文件了,如果直接用返回的流进行文件写操作,就会报以下错:

总结

框架只是实现了本地读取的两个IFileProvider,如果针对于云端文件、FTP文件等有统一的读取需求,则就需要自己实现了;所以源码是不错的参考,封装之后,结合组合提供程序,后续使用就能像使用本地文件一样简便;

加上这篇,总共十五篇,把.NetCore中比较关键的核心都过了一遍,其中包含了启动流程、依赖注入、配置、选项、日志、中间件、文件,在每个章节中都会针对对应的核心类型进行源代码分析,虽然只是浅读,但也能明白其中缘由;后续的文章将会偏应用,比如静态文件目录配置、API的最佳实现、JWT使用、IdentityServer4的集成等等一堆组件的应用;

同时,后续将同步开启另一个专题:跟我一起学Redis,欢迎一起来学习;

------------------------------------------------

CSDN:Code综艺圈

知乎:Code综艺圈

掘金:Code综艺圈

博客园:Code综艺圈

bilibili:Code综艺圈

------------------------------------------------

一个被程序搞丑的帅小伙,关注"Code综艺圈",识别关注跟我一起学~~~

撸文不易,莫要白瞟,三连走起~~~~

跟我一起学.NetCore之文件系统应用及核心浅析的更多相关文章

  1. 跟我一起学.NetCore之日志(Log)模型核心

    前言 鲁迅都说:没有日志的系统不能上线(鲁迅说:这句我没说过,但是在理)!日志对于一个系统而言,特别重要,不管是用于事务审计,还是用于系统排错,还是用于安全追踪.....都扮演了很重要的角色:之前有很 ...

  2. 跟我一起学.NetCore之选项(Options)核心类型简介

    前言 .NetCore中提供的选项框架,我把其理解为配置组,主要是将服务中可供配置的项提取出来,封装成一个类型:从而服务可根据应用场景进行相关配置项的设置来满足需求,其中使用了依赖注入的形式,使得更加 ...

  3. 跟我一起学.NetCore之静态文件处理的那些事

    前言 如今前后端分离开发模式如火如荼,开发职责更加分明(当然前后端一起搞的模式也没有完全褪去):而对于每个公司产品实施来说,部署模式会稍有差别,有的会单独将前端文件部署为一个站点,有的会将前端文件和后 ...

  4. 跟我一起学.NetCore之WebApi接口裸奔有风险(Jwt)

    前言 撸码需谨慎,裸奔有风险.经常在一些技术交流群中了解到,还有很多小伙伴的项目中Api接口没有做任何安全机制验证,直接就裸奔了,对于一些临时项目或是个人小项目还好,其余的话,建议小伙伴们酌情考虑都加 ...

  5. 跟我一起学.NetCore之熟悉的接口权限验证不能少(Jwt)

    前言 权限管控对于一个系统来说是非常重要的,最熟悉不过的是菜单权限和数据权限,上一节通过Jwt实现了认证,接下来用它实现接口权限的验证,为什么不是菜单权限呢?对于前后端分离而言,称其为接口权限感觉比较 ...

  6. 跟我一起学.NetCore之MVC过滤器,这篇看完走路可以仰着头走

    前言 MVC过滤器在之前Asp.Net的时候就已经广泛使用啦,不管是面试还是工作,总有一个考点或是需求涉及到,可以毫不疑问的说,这个技术点是非常重要的: 在之前参与的面试中,得知很多小伙伴只知道有一两 ...

  7. 跟我一起学.NetCore之中间件(Middleware)简介和解析请求管道构建

    前言 中间件(Middleware)对于Asp.NetCore项目来说,不能说重要,而是不能缺少,因为Asp.NetCore的请求管道就是通过一系列的中间件组成的:在服务器接收到请求之后,请求会经过请 ...

  8. 跟我一起学.NetCore之中间件(Middleware)应用和自定义

    前言 Asp.NetCore中的请求管道是通过一系列的中间件组成的,使得请求会根据需求进行对应的过滤和加工处理.在平时开发中会时常引用别人定义好的中间件,只需简单进行app.Usexxx就能完成中间件 ...

  9. 跟我一起学.NetCore之路由的最佳实现

    前言 路由,这词绝对不陌生,不管在前端还是后端都经常提到,而这节不说其他,就聊.NetCore的路由:在之前的Asp.Net MVC 中,路由算是面试时必问的考点,可见其重要性,它的主要作用是映射UR ...

随机推荐

  1. Jsp内置对象application之统计浏览网页的次数

    <% Object obj = application.getAttribute("count"); if(obj !=null){ Integer sum = (Integ ...

  2. 用过 mongodb 吧, 这三个大坑踩过吗?

    一:背景 1. 讲故事 前段时间有位朋友在微信群问,在向 mongodb 中插入的时间为啥取出来的时候少了 8 个小时,8 在时间处理上是一个非常敏感的数字,又吉利又是一个普适的话题,后来我想想初次使 ...

  3. three.js 制作逻辑转体游戏(下)

    上一篇已经对绕非定轴转动有所了解,这篇郭先生继续说一说逻辑转体游戏的制作,这部分我们同样会遇到一些小问题,首先是根据数据渲染陷阱和目标区域,然后是对可以转动的判定,最后是获胜的判定. 1. 根据数据渲 ...

  4. Jmeter 常用函数(17)- 详解 __substring

    如果你想查看更多 Jmeter 常用函数可以在这篇文章找找哦 https://www.cnblogs.com/poloyy/p/13291704.htm 作用 从字符串中获取指定范围的子串 语法格式 ...

  5. 学习一下 JVM (二) -- 学习一下 JVM 中对象、String 相关知识

    一.JDK 8 版本下 JVM 对象的分配.布局.访问(简单了解下) 1.对象的创建过程 (1)前言 Java 是一门面向对象的编程语言,程序运行过程中在任意时刻都可能有对象被创建.开发中常用 new ...

  6. Azure Logic App 入门(一)

    一,引言 前两天看一个azure相关的题,接触到一个叫 “Azure Logic App” 的服务,刚好,今天抽空学习以下,顺便结合它做一篇入门的分析文章. 首先,我们得对它有个大概的认识,了解以下A ...

  7. 从零开始的SpringBoot项目 ( 四 ) 整合mybatis

    一.创建一个SpringBoot项目 从零开始的SpringBoot项目 ( 二 ) 使用IDEA创建一个SpringBoot项目 二.引入相关依赖 <!--mysql数据库驱动--> & ...

  8. go语言之字符串、指针、数组、切片、结构struct、面向对象

    一: 字符串 概述: Go 语言将字符串作为 种原生的基本数据类型,字 符串的初始化可以使用字符串字面量. (1)字符串是常量,可以通过类 数组 索引访问其字节单元,但是不能修改某个字节的值 (2)宇 ...

  9. AlexNet实现cifar10数据集分类

    import tensorflow as tf import os from matplotlib import pyplot as plt import tensorflow.keras.datas ...

  10. miniapp基础

    文件目录 component 公共组件 img 图片 libs 插件,外部引入 pages 页面 utils 封装公共方法 wxParse html转wxml-->插件 app.js 公共逻辑方 ...