news 2026/10/31 11:01:16

.NET Core 文件系统

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
.NET Core 文件系统

1. ASP.NET Core 静态文件

在 ASP.NET Core 中,静态文件(如 HTML、CSS、图像和 JavaScript)默认存储在项目的 wwwroot 文件夹中。为了安全起见,框架不会自动提供这些文件,必须显式配置中间件。

1.1 启用静态文件服务

默认情况下,ASP.NET Core 不提供服务静态文件的功能。即使文件存在于 wwwroot 目录中,直接访问也会返回 404。需要在请求管道中调用 UseStaticFiles() 中间件。

步骤:
确保项目根目录下存在 wwwroot 文件夹。
在 Program.cs 中注册中间件。

var builder = WebApplication.CreateBuilder(args); var app = builder.Build(); // 启用默认 wwwroot 目录的静态文件服务 // 必须放在 UseRouting 之前或之后,但在 MapControllers/MapRazorPages 之前 app.UseStaticFiles(); app.MapGet("/", () => "Hello World!"); app.Run();

1.2 新增静态文件目录

如果希望从 wwwroot 以外的目录提供静态文件(例如 MyStaticFiles),需要配置 StaticFileOptions 并指定 FileProvider。

步骤:

1.创建一个新的文件夹,例如 MyStaticFiles。
2. 在 Program.cs 中添加额外的 UseStaticFiles 调用。

using Microsoft.Extensions.FileProviders; using System.IO; var builder = WebApplication.CreateBuilder(args); var app = builder.Build(); // 默认 wwwroot app.UseStaticFiles(); // 自定义目录:MyStaticFiles // 访问路径: /static/xxx.png var myStaticFilesPath = Path.Combine(builder.Environment.ContentRootPath, "MyStaticFiles"); if (Directory.Exists(myStaticFilesPath)) { app.UseStaticFiles(new StaticFileOptions { FileProvider = new PhysicalFileProvider(myStaticFilesPath), RequestPath = "/static" }); } app.Run();

1.3 自定义 HTTP 响应头

可以通过 OnPrepareResponse 回调为静态文件设置自定义缓存策略或其他 HTTP 头。

步骤:
在 StaticFileOptions 中配置 OnPrepareResponse。

using Microsoft.AspNetCore.StaticFiles; using Microsoft.Extensions.FileProviders; // ... 在 Program.cs 中 app.UseStaticFiles(new StaticFileOptions { OnPrepareResponse = ctx => { // 设置缓存控制头,例如缓存 1 年 const int durationInSeconds = 60 * 60 * 24 * 365; ctx.Context.Response.Headers.Append( "Cache-Control", $"public, max-age={durationInSeconds}" ); // 添加自定义头 ctx.Context.Response.Headers.Append("X-Custom-Header", "MyValue"); } });

1.4 静态文件的授权管理

默认情况下,UseStaticFiles 不提供授权检查。如果需要保护某些静态文件(如用户私有图片),可以使用 MapWhen 或自定义中间件,或者将文件放在受控区域并通过控制器动作流式传输。但更简单的方式是使用 UseStaticFiles 结合授权中间件的顺序,或者使用专门的端点映射。

对于简单的基于角色的访问,通常建议通过 Controller 行动作来提供受保护的文件,因为 UseStaticFiles 是管道早期的中间件,很难直接集成复杂的授权逻辑。但如果必须使用中间件方式,可以这样处理:

// 注意:这种方式较为粗糙,推荐对于敏感文件使用 Controller + Authorize Attribute app.Use(async (context, next) => { if (context.Request.Path.StartsWithSegments("/private")) { if (!context.User.Identity.IsAuthenticated) { context.Response.StatusCode = 401; return; } } await next(); }); // 然后配置 /private 路径的静态文件 app.UseStaticFiles(new StaticFileOptions { FileProvider = new PhysicalFileProvider(Path.Combine(builder.Environment.ContentRootPath, "PrivateFiles")), RequestPath = "/private" });

1.5 开启目录浏览服务

默认情况下,ASP.NET Core 禁止目录浏览以保障安全。如果需要列出目录内容,需启用 UseDirectoryBrowser。

步骤:
添加 UseDirectoryBrowser 中间件。
确保对应的 UseStaticFiles 也配置了相同的 FileProvider。

using Microsoft.AspNetCore.StaticFiles; using Microsoft.Extensions.FileProviders; // ... var dirPath = Path.Combine(builder.Environment.ContentRootPath, "MyStaticFiles"); if (Directory.Exists(dirPath)) { var fileProvider = new PhysicalFileProvider(dirPath); // 启用目录浏览 app.UseDirectoryBrowser(new DirectoryBrowserOptions { FileProvider = fileProvider, RequestPath = "/static" }); // 必须同时启用静态文件服务,否则点击文件无法下载/查看 app.UseStaticFiles(new StaticFileOptions { FileProvider = fileProvider, RequestPath = "/static" }); }

1.6 自定义文件扩展名

ASP.NET Core 默认只识别常见的 MIME 类型。如果需要提供未知扩展名的文件(如 .xyz),需要配置 ContentTypeProvider。

步骤:
创建 FileExtensionContentTypeProvider 实例。
添加新的映射关系。

using Microsoft.AspNetCore.StaticFiles; // ... var provider = new FileExtensionContentTypeProvider(); // 添加自定义扩展名映射 provider.Mappings[".xyz"] = "application/x-custom-type"; provider.Mappings[".log"] = "text/plain"; app.UseStaticFiles(new StaticFileOptions { ContentTypeProvider = provider });

2. 自定义一个简单的文件系统

ASP.NET Core 使用 IFileProvider 接口抽象文件系统。我们可以实现自定义的 IFileProvider 来从非物理存储(如 Redis、数据库、内存)提供文件。

2.1 文件和目录接口核心概念

要实现自定义文件系统,主要涉及三个接口:

IFileProvider: 入口点,用于获取 IDirectoryContents 和 IFileInfo。
IFileInfo: 描述单个文件的元数据(名称、长度、最后修改时间等)和内容流。
IDirectoryContents: 描述目录内容的集合。

2.2 RedisFileProvider 实现

以下是一个简化的示例,展示如何从 Redis 中读取文件内容。将文件内容存储在 Redis 字符串中,键名为文件路径。

安装 StackExchange.Redis NuGet 包。

RedisFileProvider

using Microsoft.Extensions.FileProviders; using StackExchange.Redis; using System; using System.Collections.Generic; using System.IO; using System.Threading; public class RedisFileProvider : IFileProvider { private readonly IDatabase _redisDb; private readonly string _keyPrefix; public RedisFileProvider(IConnectionMultiplexer redis, string keyPrefix = "files:") { _redisDb = redis.GetDatabase(); _keyPrefix = keyPrefix; } public IFileInfo GetFileInfo(string subpath) { if (string.IsNullOrEmpty(subpath) || subpath.Contains("..")) { return new NotFoundFileInfo(subpath); } // 清理路径,移除前导斜杠 var cleanPath = subpath.TrimStart('/'); var key = $"{_keyPrefix}{cleanPath}"; // 检查键是否存在 var exists = _redisDb.KeyExists(key); if (!exists) { return new NotFoundFileInfo(subpath); } // 获取文件大小和最后修改时间(这里简化处理,实际可能需要额外存储元数据) var value = _redisDb.StringGet(key); if (value.IsNullOrEmpty) { return new NotFoundFileInfo(subpath); } return new RedisFileInfo(subpath, value.Length, DateTime.UtcNow); } public IDirectoryContents GetDirectoryContents(string subpath) { // Redis 不是层级文件系统,模拟目录浏览非常复杂且低效 // 这里仅返回空集合,表示不支持目录浏览 return new NotFoundDirectoryContents(); } public IChangeToken Watch(string filter) { // 简化实现,返回不触发的 Token return NullChangeToken.Singleton; } } public class RedisFileInfo : IFileInfo { private readonly string _name; private readonly long _length; private readonly DateTimeOffset _lastModified; public RedisFileInfo(string name, long length, DateTimeOffset lastModified) { _name = name; _length = length; _lastModified = lastModified; } public bool Exists => true; public long Length => _length; public string PhysicalPath => null; // 无物理路径 public string Name => Path.GetFileName(_name); public DateTimeOffset LastModified => _lastModified; public bool IsDirectory => false; public Stream CreateReadStream() { // 在实际生产中,应该从 Redis 重新读取数据流 // 这里为了演示简化,假设数据已在构造函数中获取或需要重新查询 // 注意:IFileInfo 应该是轻量级的,CreateReadStream 才真正加载数据 // 由于 RedisFileInfo 构造时没有持有 byte[],这里需要访问 Redis // 为了保持类纯净,通常 RedisFileProvider 会在 GetFileInfo 时不加载内容, // 而在 CreateReadStream 时加载。 // 修正:由于 IFileInfo 接口限制,我们需要在 Provider 中注入 Redis 连接或在 FileInfo 中持有引用 // 此处为简化代码结构,假设我们通过某种方式能读取到流 // 在实际实现中,RedisFileInfo 应持有 IDatabase 引用和 Key throw new NotImplementedException("在实际实现中,此处应从 Redis 读取 Stream"); } } // 更完善的 RedisFileInfo 实现 public class RedisFileInfoComplete : IFileInfo { private readonly IDatabase _redisDb; private readonly string _key; private readonly string _name; public RedisFileInfoComplete(IDatabase redisDb, string key, string name) { _redisDb = redisDb; _key = key; _name = name; } public bool Exists => _redisDb.KeyExists(_key); public long Length => Exists ? _redisDb.StringLength(_key) : 0; public string PhysicalPath => null; public string Name => Path.GetFileName(_name); public DateTimeOffset LastModified => DateTimeOffset.UtcNow; // 简化 public bool IsDirectory => false; public Stream CreateReadStream() { var bytes = _redisDb.StringGet(_key); if (bytes.IsNullOrEmpty) { return Stream.Null; } return new MemoryStream(bytes.ToArray()); } }

Program

using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using StackExchange.Redis; using System; using System.IO; var builder = WebApplication.CreateBuilder(args); // 配置 Redis 连接 (假设本地 Redis) var redis = ConnectionMultiplexer.Connect("localhost:6379"); builder.Services.AddSingleton<IConnectionMultiplexer>(redis); var app = builder.Build(); // 初始化一些测试数据到 Redis var db = redis.GetDatabase(); db.StringSet("files:test.txt", "Hello from Redis!"); db.StringSet("files/readme.md", "# Redis File Provider Demo"); // 使用自定义 RedisFileProvider // 注意:需要使用上面定义的 RedisFileInfoComplete 逻辑更新 RedisFileProvider // 这里为了代码可运行性,展示配置方式 /* * 由于上面的 RedisFileProvider 示例中 CreateReadStream 未完整实现, * 在实际项目中请确保 RedisFileInfo 能够访问 Redis 数据库以读取流。 */ // 假设我们已经完善了 RedisFileProvider 和 RedisFileInfoComplete // app.UseStaticFiles(new StaticFileOptions // { // FileProvider = new RedisFileProvider(redis), // RequestPath = "/redis-files" // }); app.MapGet("/", () => "Check /redis-files/test.txt (if configured correctly)"); app.Run(); using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using StackExchange.Redis; using System; using System.IO; var builder = WebApplication.CreateBuilder(args); // 配置 Redis 连接 (假设本地 Redis) var redis = ConnectionMultiplexer.Connect("localhost:6379"); builder.Services.AddSingleton<IConnectionMultiplexer>(redis); var app = builder.Build(); // 初始化一些测试数据到 Redis var db = redis.GetDatabase(); db.StringSet("files:test.txt", "Hello from Redis!"); db.StringSet("files/readme.md", "# Redis File Provider Demo"); // 使用自定义 RedisFileProvider // 注意:需要使用上面定义的 RedisFileInfoComplete 逻辑更新 RedisFileProvider // 这里为了代码可运行性,展示配置方式 /* * 由于上面的 RedisFileProvider 示例中 CreateReadStream 未完整实现, * 在实际项目中请确保 RedisFileInfo 能够访问 Redis 数据库以读取流。 */ // 假设我们已经完善了 RedisFileProvider 和 RedisFileInfoComplete // app.UseStaticFiles(new StaticFileOptions // { // FileProvider = new RedisFileProvider(redis), // RequestPath = "/redis-files" // }); app.MapGet("/", () => "Check /redis-files/test.txt (if configured correctly)"); app.Run();

2.3 使用自定义文件系统

在 Program.cs 中,将自定义的 IFileProvider 传递给 UseStaticFiles 中间件。

  • 依赖注入:可以将 IFileProvider 注册为服务,以便在应用的其他部分复用。
  • 混合使用:可以同时使用多个 UseStaticFiles,一个用于物理文件(wwwroot),一个用于 Redis 文件。
  • 性能考虑 :自定义 IFileProvider 中的 GetFileInfo 和 CreateReadStream 应尽量高效。对于 Redis,避免在 GetFileInfo 中加载大文件内容,仅在 CreateReadStream 中加载。

示例:

// 在 Program.cs 中 // 1. 获取 Redis 连接 var redis = ConnectionMultiplexer.Connect("localhost:6379"); // 2. 配置静态文件中间件使用 RedisProvider app.UseStaticFiles(new StaticFileOptions { FileProvider = new RedisFileProvider(redis), // 需使用包含完整逻辑的版本 RequestPath = "/cloud" }); // 现在访问 /cloud/test.txt 将从 Redis 读取 key "files:test.txt" 的内容
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/31 10:59:32

学术会议双向字幕的术语治理:从议程抽取到现场校验

一、背景与交付链路项目背景是一场口腔种植领域的国际学术会议&#xff0c;中英双向发言&#xff0c;舞台两侧各一块大屏&#xff0c;一块纯英文、一块中英对照&#xff0c;现场不设耳机与扫码入口&#xff0c;观众获取译文依靠屏上字幕。链路分四段&#xff1a;拾音、语音识别…

作者头像 李华
网站建设 2026/10/31 10:53:20

Kafka稀疏索引写入艺术:用内存映射把8字节条目写得又快又稳

摘要&#xff1a;上一篇 Log.append 六步流程中&#xff0c;第四步"更新稀疏索引"只提了一句。这篇把它彻底拆开&#xff1a;Kafka 每个 LogSegment 有 .index 和 .timeindex 两个稀疏索引&#xff0c;都通过 MappedByteBuffer 内存映射读写。为什么写 .log 用 FileC…

作者头像 李华
网站建设 2026/10/31 10:52:29

Loop 窗口管理完整指南:6 个高频操作一次跑通

Loop 窗口管理完整指南&#xff1a;6 个高频操作一次跑通 【免费下载链接】Loop Window management made elegant. 项目地址: https://gitcode.com/GitHub_Trending/lo/Loop Loop 是一款免费开源的 macOS 窗口管理工具&#xff0c;按住一个触发键&#xff0c;配合鼠标方…

作者头像 李华