静态文件系统
通过ASP.NET Core 提供的静态文件模块和静态文件中间件,可以轻松的让应用程序拥有访问静态文件的功能,同时可以基于IFileProvider对象来自定义文件系统,如基于Redis做扩展文件系统
启动静态文件服务
在Program.cs 类中,通过WebApplication的UseStaticFiles扩展方法启动。
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.UseStaticFiles();
app.Run();
默认存储目录(wwwroot)
默认情况下,静态文件存储在项目的wwwroot目录下。
- 读取静态文件(以favicon.ico文件为例):https:// localhost:6379/favicon.ico
- 读取静态文件(以README.md文件为例):https://localhost:6379/css/open-iconic/README.md
增加自定义静态目录文件
- 调用UseStaticFiles方法时传递StaticFileOptions配置参数。
- StaticFileOptions中FileProvider为指定的文件夹路径
- StaticFileOptions中RequestPath为请求路径的前缀
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
//自定义静态文件目录
StaticFileOptions fileOpt = new()
{
FileProvider = new PhysicalFileProvider(Path.Combine(builder.Environment.ContentRootPath, "MyStaticFiles")),//指定文件夹目录
RequestPath = "/StaticFiles"//自定义前缀
};
app.UseStaticFiles(fileOpt);
app.Run();
- 指定了在项目文件目录下的MyStaticFiles文件夹
- 通过路径来获取想要的文件(以用户手册.pdf为例):https://localhost:6379/StaticFiles/用户手册.pdf
自定义一个简单的文件系统
在ASP.NET Core中,允许开发人员自定义文件系统,可以利用IFileProvider接口来构建文件系统。
文件信息类(RedisFileInfo)
public class RedisFileInfo : IFileInfo
{
/// <summary>
/// 判断目录或文件是否真的存在
/// </summary>
public bool Exists { get; set; } = true;
/// <summary>
/// 表示是目录还是文件
/// </summary>
public bool IsDirectory { get; set; }
/// <summary>
/// 文件或目录最后一次修改的时间
/// </summary>
public DateTimeOffset LastModified { get; set; }
/// <summary>
/// 表示文件内容的字节长度
/// </summary>
public long Length => _fileContent.Length;
/// <summary>
/// 表示文件或目录的名字
/// </summary>
public string Name { get; set; }
/// <summary>
/// 表示文件或目录的物理路径
/// </summary>
public string PhysicalPath { get; set; }
private readonly byte[] _fileContent;
public Stream CreateReadStream()
{
var stream = new MemoryStream(_fileContent);
stream.Position = 0;
return stream;
}
public RedisFileInfo() { }
public RedisFileInfo(string name, string content)
{
Name = name;
LastModified = DateTimeOffset.Now;
_fileContent = Convert.FromBase64String(content);
}
public RedisFileInfo(string name,bool isDirectory)
{
Name = name;
LastModified = DateTimeOffset.Now;
IsDirectory = isDirectory;
}
}
文件目录类(EnumerableDirectoryContents)
public class EnumerableDirectoryContents : IDirectoryContents
{
private readonly IEnumerable<IFileInfo> _entries;
public bool Exists => true;
public EnumerableDirectoryContents(IEnumerable<IFileInfo> entries)
{
_entries = entries;
}
public IEnumerator<IFileInfo> GetEnumerator()
{
return _entries.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
Redis配置文件(RedisFileOptions)
public class RedisFileOptions
{
/// <summary>
/// 配置Redius连接信息
/// </summary>
public string HostAndPort { get; set; }
}
文件系统逻辑处理类(RedisFileProvider)
/// <summary>
/// Redis文件解析器,只要用于通过指定的名称从Redis中读取存储的图片内容
/// </summary>
public class RedisFileProvider : IFileProvider
{
private readonly RedisFileOptions _options;
private readonly ConnectionMultiplexer _redis;
private static string NormalizePath(string path) => path.TrimStart('/').Replace('/', ':');
/// <summary>
/// 参数为Ioptions的好处是可以使用Options.Create()方法来直接生成
/// </summary>
/// <param name="options"></param>
public RedisFileProvider(IOptions<RedisFileOptions> options)
{
_options = options.Value;
_redis = ConnectionMultiplexer.Connect(new ConfigurationOptions
{
EndPoints = { _options.HostAndPort }
});
}
/// <summary>
/// 获得指定的目录
///
/// 通过
/// </summary>
/// <param name="subpath"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
public IDirectoryContents GetDirectoryContents(string subpath)
{
var db = _redis.GetDatabase();
var server = _redis.GetServer(_options.HostAndPort);
var list = new List<IFileInfo>();
subpath = NormalizePath(subpath);
foreach (var key in server.Keys(0, $"{subpath}*"))
{
var k = "";
if (subpath != "") k = key.ToString().Replace(subpath, "").Split(":")[0];
else k = key.ToString().Split(":")[0];
if (list.Find(f => f.Name == k) == null)
{
//判断是否存在.
if (k.IndexOf('.', StringComparison.OrdinalIgnoreCase) >= 0)
{
list.Add(new RedisFileInfo(k, db.StringGet(k)));
}
else
{
list.Add(new RedisFileInfo(k, true));
}
}
}
if (list.Count == 0)
{
return NotFoundDirectoryContents.Singleton;
}
return new EnumerableDirectoryContents(list);
}
/// <summary>
/// 得到指定目录或文件的IFileInfo对象
/// 通过subpath参数值再Redis客户端读取文件信息。
/// </summary>
/// <param name="subpath"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
public IFileInfo GetFileInfo(string subpath)
{
subpath = NormalizePath(subpath);
var db = _redis.GetDatabase();
var redisValue = db.StringGet(subpath);
return !redisValue.HasValue ? new NotFoundFileInfo(subpath) : new RedisFileInfo(subpath, redisValue.ToString());
}
public IChangeToken Watch(string filter)
{
throw new NotImplementedException();
}
}
注入服务
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
//使用自定义文件系统
StaticFileOptions fileOpt = new()
{
FileProvider = new RedisFileProvider(Options.Create(new RedisFileOptions
{
HostAndPort = "localhost:6379",
}))
};
app.UseStaticFiles(fileOpt);