乘风破浪,遇见最佳跨平台跨终端框架.Net Core/.Net生态 - 浅析ASP.NET Core文件提供程序,让你可以将文件存放在任何地方

什么是文件提供程序

ASP.NET Core通过文件提供程序来抽象化文件系统访问。在ASP.NET Core框架中使用文件提供程序。例如:

  • IWebHostEnvironment将应用的内容根目录和Web根目录作为IFileProvider类型公开。
  • 静态文件中间件使用文件提供程序来查找静态文件。
  • Razor使用文件提供程序来查找页面和视图。
  • .NET Core工具使用文件提供程序和glob模式来指定应该发布哪些文件。

image

动手实践

https://github.com/TaylorShi/HelloFileProvider

核心类型

  • IFileProvider,文件提供程序接口
  • IFileInfo,获取文件信息
  • IDirectoryContents,获取目录信息

内置文件提供程序

  • PhysicalFileProvider,物理文件提供程序
  • EmbeddedFileProvider,嵌入式文件提供程序
  • CompositeFileProvider,组合文件提供程序,当我们有多个数据来源的时候,可以将源合并成一个目录的效果

基础概念和定义

先来看下IFileProvider的定义。

public interface IFileProvider
{
    IDirectoryContents GetDirectoryContents(string subpath);

    IFileInfo GetFileInfo(string subpath);

    IChangeToken Watch(string filter);
}

它提供了三个方法,其中GetFileInfo方法,获取一个相对路径下的文件信息、GetDirectoryContents方法获取指定目录下的目录信息。

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

这个IDirectoryContents本质是IFileInfo的一个集合,存在一个Exists属性,表示当前目录是否存在。

public interface IFileInfo
{
    bool Exists { get; }

    bool IsDirectory { get; }

    DateTimeOffset LastModified { get; }

    long Length { get; }

    string Name { get; }

    string PhysicalPath { get; }

    Stream CreateReadStream();
}

这个IFileInfo包括几个关键的属性,Exists表示文件是否存在,Length表示文件的长度,PhysicalPath表示物理的地址,Name表示文件名,LastModified表示最后修改时间,IsDirectory是否是一个目录,以及读取文件流的方法CreateReadStream

使用文件提供程序(PhysicalFileProvider)

依赖包

dotnet add package Microsoft.Extensions.FileProviders.Abstractions
dotnet add package Microsoft.Extensions.FileProviders.Physical

准备一个自定义的小文件,让它复制到生成目录。

image

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

    var dirContents = fileProvider.GetDirectoryContents("/");

    foreach (var fileInfo in dirContents)
    {
        Console.WriteLine(fileInfo.Name);
    }

    Console.ReadKey();
}

我们获取当前程序目录下的文件内容,依次输出下文件名。

consoleForFileProvider31.deps.json
consoleForFileProvider31.dll
consoleForFileProvider31.exe
consoleForFileProvider31.pdb
consoleForFileProvider31.runtimeconfig.dev.json
consoleForFileProvider31.runtimeconfig.json
Microsoft.Extensions.FileProviders.Abstractions.dll
Microsoft.Extensions.FileProviders.Physical.dll
Microsoft.Extensions.FileSystemGlobbing.dll
Microsoft.Extensions.Primitives.dll
System.Runtime.CompilerServices.Unsafe.dll
Tesla.html

读取到了所有的文件,包括我们自定义的Tesla.html

如果要进一步读取文件流,那非常简单,直接只用IFileInfo自带的CreateReadStream方法即可。

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

    var dirContents = fileProvider.GetDirectoryContents("/");

    foreach (var fileInfo in dirContents)
    {
        var fileStream = fileInfo.CreateReadStream();
        Console.WriteLine($"FileName:{fileInfo.Name}, FileLength:{fileStream.Length}");
    }

    Console.ReadKey();
}

image

使用嵌入式文件提供程序(EmbeddedFileProvider)

依赖包

dotnet add package Microsoft.Extensions.FileProviders.Abstractions
dotnet add package Microsoft.Extensions.FileProviders.Embedded

我们准备好一个Embedded.html,然后将它的属性设置为嵌入的资源

image

接下来,我们使用EmbeddedFileProvider嵌入式提供程序可以找到它。

static void Main(string[] args)
{
    IFileProvider fileProvider = new EmbeddedFileProvider(typeof(Program).Assembly);

    var dirContents = fileProvider.GetDirectoryContents("/");

    foreach (var fileInfo in dirContents)
    {
        var fileStream = fileInfo.CreateReadStream();
        Console.WriteLine($"FileName:{fileInfo.Name}, FileLength:{fileStream.Length}");
    }

    Console.ReadKey();
}

运行结果

image

使用组合文件提供程序(CompositeFileProvider)

依赖包

dotnet add package Microsoft.Extensions.FileProviders.Abstractions
dotnet add package Microsoft.Extensions.FileProviders.Composite

查看下CompositeFileProvider定义,发现它这里是可以传入多个可选的文件提供程序的。

public class CompositeFileProvider : IFileProvider
{
    private readonly IFileProvider[] _fileProviders;

    /// <summary>
    /// Initializes a new instance of the <see cref="CompositeFileProvider" /> class using a collection of file provider.
    /// </summary>
    /// <param name="fileProviders">The collection of <see cref="IFileProvider" /></param>
    public CompositeFileProvider(params IFileProvider[] fileProviders)
    {
        _fileProviders = fileProviders ?? Array.Empty<IFileProvider>();
    }

我们直接把前面两种文件源组合起来,这里可以使用CompositeFileProvider

static void Main(string[] args)
{
    IFileProvider embeddedFileProvider = new EmbeddedFileProvider(typeof(Program).Assembly);
    IFileProvider physicalFileProvider = new PhysicalFileProvider(AppDomain.CurrentDomain.BaseDirectory);
    IFileProvider fileProvider = new CompositeFileProvider(embeddedFileProvider, physicalFileProvider);

    var dirContents = fileProvider.GetDirectoryContents("/");

    foreach (var fileInfo in dirContents)
    {
        var fileStream = fileInfo.CreateReadStream();
        Console.WriteLine($"FileName:{fileInfo.Name}, FileLength:{fileStream.Length}");
    }

    Console.ReadKey();
}

输出效果:

image

自定义阿里云OSS对象存储文件提供程序

参考准备共享类库

创建一个.Net Standard的类库项目Microsoft.Extensions.FileProviders.AliyunOssStorage

依赖包

dotnet add package Microsoft.Extensions.FileProviders.Abstractions

定义阿里云OSS授权和访问相关配置信息

public class AliyunOssOptions
{
    public string Endpoint { get; set; }

    public string AccessKeyId { get; set; }

    public string AccessKeySecret { get; set; }

    public string Bucket { get; set; }
}

定义继承自IFileInfo的AliyunOssFileInfo

public class AliyunOssFileInfo : IFileInfo
{
    public bool Exists { get { return true; } }

    public long Length { get; set; }

    public string PhysicalPath { get; set; }

    public string Name { get; set;}

    public DateTimeOffset LastModified { get; set; }

    public bool IsDirectory { get { return false; } }

    public Stream CreateReadStream()
    {
        throw new NotImplementedException();
    }
}

定义继承自IDirectoryContents的AliyunOssDirectoryContents

public class AliyunOssDirectoryContents : IDirectoryContents
{
    private List<IFileInfo> _listInfos;

    public AliyunOssDirectoryContents(List<IFileInfo> listInfos)
    {
        _listInfos = listInfos;
    }

    public bool Exists => true;

    public IEnumerator<IFileInfo> GetEnumerator()
    {
        return _listInfos.GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return _listInfos.GetEnumerator();
    }
}

引入阿里云官方SDK包

https://www.nuget.org/packages/Aliyun.OSS.SDK.NetCore

依赖包

dotnet add package Aliyun.OSS.SDK.NetCore

定义继承自IFileProvider的AliyunOssFileProvider

public class AliyunOssFileProvider : IFileProvider
{
    readonly OssClient ossClient;
    readonly Bucket bucket;

    public AliyunOssFileProvider(AliyunOssOptions aliyunOssOptions)
    {
        ossClient = new OssClient(aliyunOssOptions.Endpoint, aliyunOssOptions.AccessKeyId, aliyunOssOptions.AccessKeySecret);
        var buckets = ossClient.ListBuckets();
        bucket = buckets.FirstOrDefault(x => x.Name == aliyunOssOptions.Bucket);
    }

    public IDirectoryContents GetDirectoryContents(string subpath)
    {
        var listObjectsRequest = new ListObjectsRequest(bucket.Name)
        {
            // 通过MaxKeys指定列举文件的最大个数为200。 MaxKeys默认值为100,最大值为1000。
            MaxKeys = 200,
        };
        var oSSObjects = ossClient.ListObjects(listObjectsRequest);
        return FromAliyunObjects(oSSObjects);
    }

    public IFileInfo GetFileInfo(string subpath)
    {
        return null;
    }

    public IChangeToken Watch(string filter)
    {
        return null;
    }

    private IDirectoryContents FromAliyunObjects(ObjectListing oSSObjects)
    {
        var files = new List<IFileInfo>();

        foreach (var aliyunObject in oSSObjects.ObjectSummaries)
        {
            var aliyunOssFileInfo = new AliyunOssFileInfo()
            {
                Name = aliyunObject.Key,
            };
            files.Add(aliyunOssFileInfo);
        }

        return new AliyunOssDirectoryContents(files);
    }
}

这里暂时只实现简单的文件列举。

使用阿里云Oss文件提供程序

首先我们要读取相关配置,然后再使用这个文件提供程序。

依赖包

dotnet add package Microsoft.Extensions.Configuration
dotnet add package Microsoft.Extensions.Configuration.Json
dotnet add package Microsoft.Extensions.Configuration.Binder
static void Main(string[] args)
{
    IConfigurationBuilder builder = new ConfigurationBuilder();
    builder.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true);
    var config = builder.Build();
    var aliyunOssConfigSection = config.GetSection("AliyunOss");
    var aliyunOssOptions = new AliyunOssOptions();
    aliyunOssConfigSection.Bind(aliyunOssOptions);

    IFileProvider fileProvider = new AliyunOssFileProvider(aliyunOssOptions);

    var dirContents = fileProvider.GetDirectoryContents("/");

    foreach (var fileInfo in dirContents)
    {
        //var fileStream = fileInfo.CreateReadStream();
        Console.WriteLine($"FileName:{fileInfo.Name}");
    }

    Console.ReadKey();
}

对应的appsettings.json配置文件如下

{
  "AliyunOss": {
    "AccessKeyId": "xxxxxxxxxxxxxx",
    "AccessKeySecret": "xxxxxxxxxxxxxxxxxxxxxxxx/Yw=",
    "Endpoint": "http://oss-cn-hangzhou.aliyuncs.com",
    "Bucket": "xxxxxxxxxx"
  }
}

运行效果

image

自定义Azure Blob存储文件提供程序

参考准备共享类库

创建一个.Net Standard的类库项目Microsoft.Extensions.FileProviders.AzureBlobStorage

依赖包

dotnet add package Microsoft.Extensions.FileProviders.Abstractions

定义Azure Blob授权和访问相关配置信息

public class AzureBlobOptions
{
    public string ConnectionString { get; set; }

    public string ContainerName { get; set; }
}

定义继承自IFileInfo的AzureBlobFileInfo

public class AzureBlobFileInfo : IFileInfo
{
    private readonly BlobContainerClient _blobContainerClient;
    public AzureBlobFileInfo(BlobContainerClient blobContainerClient)
    {
        _blobContainerClient = blobContainerClient;
    }

    public bool Exists => true;

    public long Length { get; set; }

    public string PhysicalPath { get; private set; }

    public string Name { get; set; }

    public DateTimeOffset LastModified { get; set; }

    public bool IsDirectory { get; set; }

    public Stream CreateReadStream()
    {
        var ms = new MemoryStream();
        var response = _blobContainerClient.GetBlobClient(Name).DownloadTo(ms);
        if(response.Status == 200)
        {
            ms.Position = 0;
        }
        return ms;
    }

    public void SetPhysicalPath(string containerRootPath)
    {
        PhysicalPath = $"{containerRootPath}/{Name}";
    }
}

定义继承自IDirectoryContents的AzureBlobDirectoryContents

public class AzureBlobDirectoryContents : IDirectoryContents
{
    private List<IFileInfo> _fileInfos;

    public AzureBlobDirectoryContents(List<IFileInfo> fileInfos)
    {
        this._fileInfos = fileInfos;
    }

    public bool Exists => true;

    public IEnumerator<IFileInfo> GetEnumerator()
    {
        return _fileInfos.GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return _fileInfos.GetEnumerator();
    }
}

引入Azure Blob官方SDK包

https://www.nuget.org/packages/Azure.Storage.Blobs/

依赖包

dotnet add package Azure.Storage.Blobs

定义继承自IFileProvider的AzureBlobFileProvider

public class AzureBlobFileProvider : IFileProvider
{
    readonly BlobContainerClient blobContainerClient;

    public AzureBlobFileProvider(AzureBlobOptions azureBlobOptions)
    {
        blobContainerClient = new BlobContainerClient(azureBlobOptions.ConnectionString, azureBlobOptions.ContainerName);
    }

    public IDirectoryContents GetDirectoryContents(string subpath)
    {
        Pageable<BlobItem> blobPageItems = blobContainerClient.GetBlobs();
        return FromAzureBlobs(blobPageItems);
    }

    public IFileInfo GetFileInfo(string subpath)
    {
        throw new NotImplementedException();
    }

    public IChangeToken Watch(string filter)
    {
        throw new NotImplementedException();
    }

    private IDirectoryContents FromAzureBlobs(Pageable<BlobItem> blobPageItems)
    {
        var files = new List<IFileInfo>();

        foreach (var blobItem in blobPageItems)
        {
            var azureBlobFileInfo = new AzureBlobFileInfo(blobContainerClient)
            {
                Name = blobItem.Name,
                Length = blobItem.Properties.ContentLength ?? 0,
                LastModified = blobItem.Properties.LastModified ?? default,
            };
            azureBlobFileInfo.SetPhysicalPath(blobContainerClient.Uri.OriginalString);
            files.Add(azureBlobFileInfo);
        }

        return new AzureBlobDirectoryContents(files);
    }
}

这里暂时只实现简单的文件列举。

使用Azure Blob文件提供程序

首先我们要读取相关配置,然后再使用这个文件提供程序。

依赖包

dotnet add package Microsoft.Extensions.Configuration
dotnet add package Microsoft.Extensions.Configuration.Json
dotnet add package Microsoft.Extensions.Configuration.Binder
static void Main(string[] args)
{
    IConfigurationBuilder builder = new ConfigurationBuilder();
    builder.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true);
    var config = builder.Build();
    var aliyunOssConfigSection = config.GetSection("AzureBlobConfig");
    var aliyunOssOptions = new AzureBlobOptions();
    aliyunOssConfigSection.Bind(aliyunOssOptions);

    IFileProvider fileProvider = new AzureBlobFileProvider(aliyunOssOptions);

    var dirContents = fileProvider.GetDirectoryContents("/");

    foreach (var fileInfo in dirContents)
    {
        var fileStream = fileInfo.CreateReadStream();
        Console.WriteLine($"FileName:{fileInfo.Name}, FileLength:{fileStream.Length}");
    }

    Console.ReadKey();
}

对应的appsettings.json配置文件如下

{
  "AzureBlobConfig": {
    "ConnectionString": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
    "ContainerName": "xxxxxx"
  }
}

运行效果

image

参考

posted @ 2022-10-13 03:01  TaylorShi  阅读(119)  评论(0编辑  收藏  举报