zoukankan      html  css  js  c++  java
  • 跟我一起学.NetCore之文件系统应用及核心浅析

    前言

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

    正文

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

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

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

    img

    运行结果:

    img

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

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

    img

    运行结果如下:

    img

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

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

    img

    当然,按老套路走,不能用用就行了,继续扒扒代码,先看看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只能获取到文件件信息,目录信息获取不到;至少在写文件的时候不再懵逼的在想:为什么不能写文件了,如果直接用返回的流进行文件写操作,就会报以下错:

    img

    总结

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

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

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

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

    CSDN:Code综艺圈

    知乎:Code综艺圈

    掘金:Code综艺圈

    博客园:Code综艺圈

    bilibili:Code综艺圈

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

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

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

  • 相关阅读:
    二重积分与三重积分补充习题
    一二三章基础测试
    17. 继承之同名处理
    第二章 进程通信(五)
    16. 继承(一)
    15. 函数运算符重载及转换的知识点小结
    Hadoop基础-06-HDFS数据读写
    Hadoop基础-05-HDFS项目(词频统计)
    Hadoop基础-04-HDFS API编程
    Hadoop基础-03-HDFS(Hadoop Distributed File System)基本概念
  • 原文地址:https://www.cnblogs.com/zoe-zyq/p/13665275.html
Copyright © 2011-2022 走看看