乘风破浪,遇见最佳跨平台跨终端框架.Net Core/.Net生态 - 浅析ASP.NET Core文件提供程序,让你可以将文件存放在任何地方
什么是文件提供程序
ASP.NET Core通过文件提供程序来抽象化文件系统访问。在ASP.NET Core框架中使用文件提供程序。例如:
IWebHostEnvironment
将应用的内容根目录和Web根目录作为IFileProvider
类型公开。- 静态文件中间件使用文件提供程序来查找静态文件。
- Razor使用文件提供程序来查找页面和视图。
- .NET Core工具使用文件提供程序和glob模式来指定应该发布哪些文件。
动手实践
核心类型
- 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
准备一个自定义的小文件,让它复制到生成目录。
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();
}
使用嵌入式文件提供程序(EmbeddedFileProvider)
依赖包
dotnet add package Microsoft.Extensions.FileProviders.Abstractions
dotnet add package Microsoft.Extensions.FileProviders.Embedded
我们准备好一个Embedded.html
,然后将它的属性设置为嵌入的资源
。
接下来,我们使用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();
}
运行结果
使用组合文件提供程序(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();
}
输出效果:
自定义阿里云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包
依赖包
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"
}
}
运行效果
自定义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包
依赖包
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"
}
}
运行效果
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步