如何在ASP.NET Core中自定义Azure Storage File Provider

时间:2023-03-08 23:19:47
如何在ASP.NET Core中自定义Azure Storage File Provider

文章标题:如何在ASP.NET Core中自定义Azure Storage File Provider

作者:Lamond Lu

地址:https://www.cnblogs.com/lwqlun/p/10406566.html

项目源代码: https://github.com/lamondlu/AzureFileProvider

如何在ASP.NET Core中自定义Azure Storage File Provider

背景

ASP.NET Core是一个扩展性非常高的框架,开发人员可以根据自己的需求扩展出想要的功能。File Provider是ASP.NET Core中的一个重要组件,通过这个组件,开发人员可以暴露一组文件,并允许应用程序像访问静态文件一样访问暴露的文件。

ASP.NET Core中内置了3种File Provider

  • PhysicalFileProvider - 用来访问和应用程序部署在一起的静态文件
  • ManifestEmbeddedFileProvider - 用来访问程序集中的内嵌文件
  • CompositeFileProvider - 将多个File Provider合并使用

那么如何自定义一个File Provider呢?比如如何将Azure Files Storage中的文件暴露给ASP.NET Core应用程序。今天我们来演示一下,如果通过实现IFileProvider 接口来实现一个Azure Files Storage Provider。

本文中只针对Azure Files Storage, Azure Blob Storage的实现可以参见Filip w的博文

创建.NET Core Library项目

首先我们使用Visual Studio 2017,创建一个Class Library项目, 命名为AzureFileProvider

如何在ASP.NET Core中自定义Azure Storage File Provider

为了使用IFileProvider接口和Azure Storage服务,这里我们需要使用Nuget引入2个库

  • Microsoft.AspNetCore.App
  • WindowsAzure.Storage

创建AzureFileProvider

为了创建一个ASP.NET Core支持的File Provider, 我们就需要自己创建一个类,并让它实现IFileProvider接口。

这里首先我们创建一个类AzureFileProvider, 它实现了IFileProvider接口

	public class AzureFileProvider : IFileProvider
{
public IDirectoryContents GetDirectoryContents(string subpath)
{
throw new NotImplementedException();
} public IFileInfo GetFileInfo(string subpath)
{
throw new NotImplementedException();
} public IChangeToken Watch(string filter)
{
throw new NotImplementedException();
}
}

从以上代码中,我们可以了解到,IFileProvider接口定义了3个需要实现方法

  • GetDirectoryContents - 这个方法是用来获取指定目录下的内容的
  • GetFileInfo - 这个方法使用来获取指定文件内容的
  • Watch - 这个方法是用来监听文件变更的,这个暂时不需要实现它

实现GetDirectoryContents方法

为了实现GetDirectoryContents方法,我们需要首先创建一个IDirectoryContents接口的实现类, 因为它是这个方法的返回类型。

我们创建一个类AzureStorageDirectoryContents, 它实现了IDirectoryContents接口。

	public class AzureStorageDirectoryContents : IDirectoryContents
{
private List<IFileInfo> _listInfos; public AzureStorageDirectoryContents(List<IFileInfo> listInfos)
{
_listInfos = listInfos;
} public bool Exists
{
get
{
return true;
}
} public IEnumerator<IFileInfo> GetEnumerator()
{
return _listInfos.GetEnumerator();
} IEnumerator IEnumerable.GetEnumerator()
{
return _listInfos.GetEnumerator();
}
}

代码解释:

  • 这里IDirectoryContents其实就是为了显示指定目录中的文件结构
  • IFileInfo接口对象既可以表示文件也可以表示子目录,这个接口的2个实现我会在后面说明
  • 这里我们通过构造函数,将指定文件夹内的文件结构注入到了AzureStorageDirectoryContents雷中。

下面我们就可以来添加GetDirectoryContents方法的实现了

	private AzureStorageSetting _setting = null;

    public AzureFileProvider(AzureStorageSetting setting)
{
_setting = setting;
} public IDirectoryContents GetDirectoryContents(string subpath)
{
var rootDirectory = GetRootDirectory(); var folderName = subpath.Substring(1);
CloudFileDirectory folder = null; if (string.IsNullOrWhiteSpace(folderName))
{
folder = rootDirectory;
}
else
{
folder = rootDirectory.GetDirectoryReference(folderName);
} var files = new List<IFileInfo>();
foreach (var item in folder
.ListFilesAndDirectoriesSegmentedAsync(new FileContinuationToken())
.Result
.Results)
{
if (item is CloudFile)
{
var file = item as CloudFile;
files.Add(new AzureFileInfo(file));
}
else if (item is CloudFileDirectory)
{
var directory = item as CloudFileDirectory;
files.Add(new AzureDirectoryInfo(directory));
}
} return new AzureStorageDirectoryContents(files);
} private CloudFileDirectory GetRootDirectory()
{
var shareName = _setting.ShareName;
var storageAccount = CloudStorageAccount.Parse(_setting.ConnectionString);
var fileClient = storageAccount.CreateCloudFileClient();
var share = fileClient.GetShareReference(shareName);
var rootDir = share.GetRootDirectoryReference(); return rootDir;
}

代码解释:

  • 这里我们通过构造函数为AzureFileProvider类注入了一个Azure Files Storage强类型配置类AzureStorageSetting, 它的数据源是appSettings.json, 后续我们会通过强类型配置将其注入
  • GetRootDirectory方法是通过Azure Files Storage配置,获得Azure Files Storage中文件集合的根目录
  • GetDirectoryContentssubpath.Substring(1)代码的作用是去除subpath带的第一个“/”。如果不去除,会读取不到文件
  • 这里我们使用了ListFilesAndDirectoriesSegmentedAsync方法获取了指定目录中所有的文件和目录
  • 如果是文件,我们会实例化一个AzureFileInfo对象,如果是一个目录,我们会实例化一个AzureDirectoryInfo对象
  • 最终我们将读取到的所有文件和目录信息通过AzureStorageDirectoryContents类的构造函数注入。

创建AzureFileInfoAzureDirectoryInfo

为了区分文件和目录,我们创建2个新类AzureFileInfoAzureDirectoryInfo。 他们都实现了IFileInfo接口。

AzureFileInfo

	public class AzureFileInfo : IFileInfo
{
private CloudFile _file = null;
private MemoryStream _stream = new MemoryStream(); public AzureFileInfo(CloudFile file)
{
_file = file;
_file.DownloadRangeToStreamAsync(_stream, null, null).Wait();
_stream.Position = 0;
} public bool Exists
{
get
{
return true;
}
} public long Length
{
get
{
return _stream.Length;
}
} public string PhysicalPath
{
get
{
return _file.Uri.AbsolutePath;
}
} public string Name
{
get
{
return _file.Name;
}
} public DateTimeOffset LastModified
{
get
{
return _file.Properties.LastModified.GetValueOrDefault();
}
} public bool IsDirectory
{
get
{
return false;
}
} public Stream CreateReadStream()
{
return _stream;
}
}

代码解释

  • 这里我们通过AzureFileInfo的构造函数传入了一个CloudFile对象, 这个对象将作为Name, PhysicalPath, LastModified等属性的数据源。
  • 我们使用CloudFile对象DownloadRangeToStreamAsync, 将其对应的文件流下载。注意这里加载文件流之后,需要将文件流的Position置0(即流的头部)
  • 文件的长度即文件流的长度
  • 强制设置IsDirectory属性为false, 因为当前处理的是文件

AzureDirectoryInfo

	public class AzureDirectoryInfo : IFileInfo
{
private CloudFileDirectory _directory = null; public AzureDirectoryInfo(CloudFileDirectory directory)
{
_directory = directory;
} public bool Exists
{
get
{
return true;
}
} public long Length => throw new NotImplementedException(); public string PhysicalPath
{
get
{
return _directory.Uri.AbsolutePath;
}
} public string Name
{
get
{
return _directory.Name;
}
} public DateTimeOffset LastModified
{
get
{
return _directory.Properties.LastModified.GetValueOrDefault();
}
} public bool IsDirectory
{
get
{
return true;
}
} public Stream CreateReadStream()
{
throw new NotImplementedException();
}
}

代码解释

  • 这里我们通过AzureDirectoryInfo的构造函数传入了一个CloudFileDirectory对象, 这个对象将作为Name, PhysicalPath, LastModified等属性的数据源。
  • 强制设置IsDirectory属性为true, 因为当前处理的是目录
  • 这里我们没有实现Length属性和CreateReadStream, 因为我们处理的是目录, 这2个属性没有必要实现。

实现GetFileInfo方法

相对于GetDirectoryContents方法的实现,GetFileInfo方法就简单多了,我们只需要根据当前指定的subpath, 将文件信息返回即可。

	public IFileInfo GetFileInfo(string subpath)
{
var rootDirectory = GetRootDirectory();
var file = rootDirectory
.GetFileReference(subpath.Substring(1)); return new AzureFileInfo(file);
}

如何启用AzureFileProvider

下面我们来试验一下我们编写的AzureFileProvider是否能运行成功。

首先我们创建一个默认ASP.NET Core Api项目,并引用上一步中编译好的程序集AzureFileProvider.dll。

appSettings.json中, 我们需要定义Azure Files Storage的配置

例:

{
"Logging": {
"LogLevel": {
"Default": "Warning"
}
},
"AzureStorage": {
"ConnectionString": "DefaultEndpointsProtocol=https;AccountName=fdsffsdf;AccountKey=fdsfsdfs;EndpointSuffix=core.windows.net",
"ShareName": "testShare"
}
}

第二步,我们需要修改Startup.cs文件的Configure方法。

	public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
AzureStorageSetting o = new AzureStorageSetting();
Configuration.Bind("AzureStorage", o); app.UseStaticFiles(new StaticFileOptions
{
FileProvider = new AzureFileProvider(o),
RequestPath = "/files"
}); app.UseDirectoryBrowser(new DirectoryBrowserOptions
{
FileProvider = new AzureFileProvider(o),
RequestPath = "/files"
}); app.UseMvc();
}

代码解释

  • 这里我们使用强类型配置绑定,获取了appSettings.json中的Azure Files Storage的配置
  • 在配置静态文件中间件部分,我们通过StaticFileOptions配置对象,指定了当前应用使用AzureFileProvider。
  • 为了演示效果,我这里也启用了DirectoryBrowser中间件,即可以使用网页查看目录结构。这个功能比较危险,在正式项目很少使用。所以正式使用时,最好将这段代码删掉。

最终效果

现在我们启动当前项目, 访问"/files", 即可查看到当前指定Azure Files Storage中的所有文件和目录

如何在ASP.NET Core中自定义Azure Storage File Provider

项目源代码

https://github.com/lamondlu/AzureFileProvider

Nuget程序集

以上类库,我已经发布到了Nuget上, 如果你不想每次都把前面的代码写一遍,可以直接安装这个程序集来使用。

Install-Package LamondLu.AzureFileProvider