在ASP.NET Core应用开发中,默认的文件提供程序基于物理文件系统,只能访问服务器磁盘上的文件。但很多场景下我们需要提供动态生成的文件、内存中临时存储的文件或者存储在数据库中的文件,这时候就需要自定义虚拟文件提供程序来实现需求。

核心接口说明
ASP.NET Core的文件系统基于IFileProvider接口,自定义虚拟文件提供程序需要实现该接口以及相关的辅助接口:
IFileProvider:核心接口,负责提供文件目录和文件信息IFileInfo:表示单个文件的信息,包含是否存在、长度、读取流等属性IDirectoryContents:表示目录下的文件集合,用于遍历目录内容
实现内存虚拟文件提供程序
第一步:实现内存文件信息类
首先创建InMemoryFileInfo类实现IFileInfo接口,用于描述内存中的文件信息:
using Microsoft.Extensions.FileProviders;
using System.IO;
public class InMemoryFileInfo : IFileInfo
{
private readonly byte[] _fileContent;
public InMemoryFileInfo(string name, byte[] content)
{
Name = name;
_fileContent = content;
Exists = true;
Length = content.Length;
LastModified = DateTimeOffset.UtcNow;
PhysicalPath = null;
IsDirectory = false;
}
public bool Exists { get; }
public bool IsDirectory { get; }
public DateTimeOffset LastModified { get; }
public long Length { get; }
public string Name { get; }
public string PhysicalPath { get; }
public Stream CreateReadStream()
{
return new MemoryStream(_fileContent);
}
}
第二步:实现内存目录内容类
创建InMemoryDirectoryContents类实现IDirectoryContents接口,用于返回目录下的所有内存文件:
using Microsoft.Extensions.FileProviders;
using System.Collections;
using System.Collections.Generic;
public class InMemoryDirectoryContents : IDirectoryContents
{
private readonly IEnumerable<IFileInfo> _fileInfos;
public InMemoryDirectoryContents(IEnumerable<IFileInfo> fileInfos)
{
_fileInfos = fileInfos;
Exists = true;
}
public bool Exists { get; }
public IEnumerator<IFileInfo> GetEnumerator()
{
return _fileInfos.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
第三步:实现内存文件提供程序
创建InMemoryFileProvider类实现IFileProvider接口,管理内存中的文件集合:
using Microsoft.Extensions.FileProviders;
using System;
using System.Collections.Generic;
using System.Linq;
public class InMemoryFileProvider : IFileProvider
{
private readonly Dictionary<string, IFileInfo> _files = new Dictionary<string, IFileInfo>(StringComparer.OrdinalIgnoreCase);
public void AddFile(string path, byte[] content)
{
var fileName = Path.GetFileName(path);
_files[path] = new InMemoryFileInfo(fileName, content);
}
public IFileInfo GetFileInfo(string subpath)
{
if (_files.TryGetValue(subpath, out var fileInfo))
{
return fileInfo;
}
return new NotFoundFileInfo(subpath);
}
public IDirectoryContents GetDirectoryContents(string subpath)
{
var files = _files.Values.Where(f => Path.GetDirectoryName(subpath) == Path.GetDirectoryName(f.Name));
return new InMemoryDirectoryContents(files);
}
public IChangeToken Watch(string filter)
{
return NullChangeToken.Singleton;
}
private class NotFoundFileInfo : IFileInfo
{
public NotFoundFileInfo(string path)
{
Name = path;
}
public bool Exists => false;
public bool IsDirectory => false;
public DateTimeOffset LastModified => DateTimeOffset.MinValue;
public long Length => -1;
public string Name { get; }
public string PhysicalPath => null;
public Stream CreateReadStream()
{
throw new FileNotFoundException($"文件 {Name} 不存在");
}
}
}
第四步:在ASP.NET Core中注册使用
在Program.cs中注册内存文件提供程序,并添加静态文件中间件:
var builder = WebApplication.CreateBuilder(args);
// 创建内存文件提供程序并添加测试文件
var inMemoryFileProvider = new InMemoryFileProvider();
inMemoryFileProvider.AddFile("/test.txt", System.Text.Encoding.UTF8.GetBytes("这是内存中的测试文件内容"));
// 将内存文件提供程序添加到文件提供程序集合
builder.Services.AddSingleton<IFileProvider>(inMemoryFileProvider);
var app = builder.Build();
// 配置静态文件中间件使用自定义文件提供程序
app.UseStaticFiles(new StaticFileOptions
{
FileProvider = inMemoryFileProvider
});
app.MapGet("/", () => "虚拟文件提供程序示例,访问 /test.txt 可查看内存中的文件");
app.Run();
实现数据库虚拟文件提供程序
第一步:创建数据库文件实体
假设使用EF Core存储文件,首先创建DbFile实体类:
public class DbFile
{
public int Id { get; set; }
public string FilePath { get; set; }
public string FileName { get; set; }
public byte[] FileContent { get; set; }
public DateTimeOffset UploadTime { get; set; }
}
第二步:实现数据库文件信息类
创建DbFileInfo类实现IFileInfo接口:
using Microsoft.Extensions.FileProviders;
using System.IO;
public class DbFileInfo : IFileInfo
{
private readonly byte[] _fileContent;
public DbFileInfo(DbFile dbFile)
{
Name = dbFile.FileName;
_fileContent = dbFile.FileContent;
Exists = true;
Length = dbFile.FileContent.Length;
LastModified = dbFile.UploadTime;
PhysicalPath = null;
IsDirectory = false;
}
public bool Exists { get; }
public bool IsDirectory { get; }
public DateTimeOffset LastModified { get; }
public long Length { get; }
public string Name { get; }
public string PhysicalPath { get; }
public Stream CreateReadStream()
{
return new MemoryStream(_fileContent);
}
}
第三步:实现数据库文件提供程序
创建DbFileProvider类实现IFileProvider接口,从数据库查询文件信息:
using Microsoft.Extensions.FileProviders;
using Microsoft.EntityFrameworkCore;
using System;
using System.Linq;
public class DbFileProvider : IFileProvider
{
private readonly AppDbContext _dbContext;
public DbFileProvider(AppDbContext dbContext)
{
_dbContext = dbContext;
}
public IFileInfo GetFileInfo(string subpath)
{
var dbFile = _dbContext.DbFiles.FirstOrDefault(f => f.FilePath == subpath);
if (dbFile != null)
{
return new DbFileInfo(dbFile);
}
return new NotFoundFileInfo(subpath);
}
public IDirectoryContents GetDirectoryContents(string subpath)
{
var files = _dbContext.DbFiles
.Where(f => f.FilePath.StartsWith(subpath))
.Select(f => new DbFileInfo(f))
.ToList();
return new InMemoryDirectoryContents(files);
}
public IChangeToken Watch(string filter)
{
return NullChangeToken.Singleton;
}
private class NotFoundFileInfo : IFileInfo
{
public NotFoundFileInfo(string path)
{
Name = path;
}
public bool Exists => false;
public bool IsDirectory => false;
public DateTimeOffset LastModified => DateTimeOffset.MinValue;
public long Length => -1;
public string Name { get; }
public string PhysicalPath => null;
public Stream CreateReadStream()
{
throw new FileNotFoundException($"文件 {Name} 不存在");
}
}
}
第四步:注册数据库文件提供程序
在Program.cs中配置数据库上下文和文件提供程序:
var builder = WebApplication.CreateBuilder(args);
// 配置数据库上下文
builder.Services.AddDbContext<AppDbContext>(options =>
options.UseSqlite("Data Source=files.db"));
// 注册数据库文件提供程序
builder.Services.AddScoped<IFileProvider, DbFileProvider>();
var app = builder.Build();
// 初始化数据库并添加测试文件
using (var scope = app.Services.CreateScope())
{
var dbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
dbContext.Database.EnsureCreated();
if (!dbContext.DbFiles.Any())
{
dbContext.DbFiles.Add(new DbFile
{
FilePath = "/db_test.txt",
FileName = "db_test.txt",
FileContent = System.Text.Encoding.UTF8.GetBytes("这是数据库中的测试文件内容"),
UploadTime = DateTimeOffset.UtcNow
});
dbContext.SaveChanges();
}
}
app.UseStaticFiles(new StaticFileOptions
{
FileProvider = app.Services.GetRequiredService<IFileProvider>()
});
app.MapGet("/", () => "数据库虚拟文件提供程序示例,访问 /db_test.txt 可查看数据库中的文件");
app.Run();
注意事项
- 虚拟文件提供程序的路径匹配区分大小写,建议统一使用小写路径避免问题
- 数据库文件提供程序中,查询文件时建议添加索引提升查询效率
- 如果文件较大,需要注意内存占用,可考虑分块读取流的方式优化
- 生产环境中如果需要监听文件变化,可以实现
IChangeToken接口返回对应的变更令牌
ASP.NET_CoreVirtualFileProviderIFileProviderC#修改时间:2026-06-09 17:24:39