外观模式:ASP.NET Core 里那些让你“无痛”写代码的秘密武器
打开一个 ASP.NET Core 项目,你会看到什么?
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
var app = builder.Build();
app.MapControllers();
app.Run();五句话,一个 Web 应用就搭起来了。
但你想过没有——背后那成千上万行的依赖注入容器、中间件管道、宿主构建、请求上下文……这些复杂得要命的东西,谁帮你搞定的?
答案是一个设计模式:外观模式(Facade)。
它就像你家里的智能遥控器。按下“观影模式”,窗帘自动拉上、灯光调暗、电视打开——你完全不用管背后的红外信号、电路逻辑、设备协议。你只管按按钮,剩下的交给遥控器。
ASP.NET Core 里到处都是这种“遥控器”。今天我们就来拆开看看,它们到底是怎么工作的。
外观模式是啥?一个前台接待员
简单说,外观模式就是给一群复杂的“幕后人员”配一个前台接待。你只需要找前台,前台帮你搞定一切。
GoF 的定义里有三个角色:
- 外观:统一接口,就像前台小姐姐
- 子系统:真正干活的部门,比如财务、人事、技术
- 客户端:你,只跟前台说话
核心思想就四个字:封装复杂,暴露简单。
ASP.NET Core 深谙此道,几乎把整个框架都“包装”成了一个个外观。你感觉不到复杂,是因为有人替你扛了。
IServiceCollection:点菜一样注册服务
如果没有外观,注册一个服务要怎么做?
你得手动创建 ServiceDescriptor 列表,操心生命周期怎么存,还要自己构建 ServiceProvider……光是想想就头大。
但现在呢?
var services = new ServiceCollection();
services.AddLogging();
services.AddControllers();
services.AddScoped<IUserService, UserService>();
var provider = services.BuildServiceProvider();这几行代码背后藏着什么?ServiceDescriptor 的集合管理、ServiceProvider 的表达式树编译、作用域验证、循环依赖检测……但作为开发者的你,完全不用管。
IServiceCollection 就是那个前台。你告诉她“我要加日志,加 MVC,加一个 UserService”,她扭头就帮你办妥了。
IApplicationBuilder:搭中间件管道,像搭积木
ASP.NET Core 的请求处理管道,本质上是一串 Func<RequestDelegate, RequestDelegate> 委托链。你要直接去拼这些委托,眼睛都能看花。
但 IApplicationBuilder 给了你一套亲切的 API:
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints => endpoints.MapControllers());每个 UseXXX 都是外观方法。内部帮你把中间件包装成委托,挂到管道上。你不用操心作用域、依赖注入怎么传递——外观全包了。
就像搭积木,每一块放上去,管道就自动成型。
WebApplication:从“古典”到“极简”的飞跃
如果你经历过 .NET Core 3.x 时代,应该还记得那个“启动三件套”:
public class Program
{
public static void Main(string[] args) =>
CreateHostBuilder(args).Build().Run();
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}繁琐得像写八股文。
而到了 .NET 6+,WebApplication 横空出世,把宿主、配置、日志、中间件、服务器全部糅合成一个外观:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
var app = builder.Build();
app.MapControllers();
app.Run();五句话,搞定一切。你看到的是 5 行代码,实际上是 IHostBuilder + IApplicationBuilder + IConfiguration + 日志 + Kestrel 的“全家桶”。
WebApplication 就是那个万能遥控器,一键启动所有子系统。而且它没有锁死扩展性——你想动底层,依然可以通过 builder.Services 或 app 钻进去调细节。
HttpContext:一个对象,装下整个 HTTP 世界
在中间件或控制器里,你要拿请求、写响应、看用户身份、读写 Session……如果每个都去单独找底层对象,代码会变得无比丑陋。
HttpContext 就是为你准备好的“百宝箱”:
app.Run(async context =>
{
var request = context.Request;
var response = context.Response;
var user = context.User;
var session = context.Session;
await response.WriteAsync($"Hello {user.Identity.Name}");
});你不需要知道 IFeatureCollection 里藏着什么 IHttpRequestFeature,也不用管协议解析细节。HttpContext 把这些子系统打包成几个直观的属性,让你用起来像在翻自己的背包。
ControllerBase:把 MVC 的脏活累活全包了
在 MVC 里,你写一个 ProductsController:
[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
[HttpGet("{id}")]
public async Task<ActionResult<ProductDto>> Get(int id)
{
var product = await _productService.GetByIdAsync(id);
if (product == null) return NotFound();
return Ok(product);
}
}你有没有想过——谁帮你做的路由匹配?谁把 URL 里的 id 绑定到参数?谁验证 [Required] 特性?谁把 Ok() 转成 200 响应?
全是 MVC 内部子系统干的事。但 ControllerBase 这个外观把它们全挡在了外面。你只需要专注于业务逻辑,剩下的交给“遥控器”。
IWebHostEnvironment:不用自己查字典了
以前要判断是不是开发环境,你可能得读环境变量 ASPNETCORE_ENVIRONMENT,还要处理不同操作系统的路径分隔符。
现在注入一个 IWebHostEnvironment,世界就清净了:
if (_env.IsDevelopment())
{
Console.WriteLine($"Content root: {_env.ContentRootPath}");
}它帮你缓存了环境变量、统一了路径抽象,还给你一堆好用的扩展方法。这就是外观的温柔。
外观 vs 适配器 vs 中介者,别搞混了
有朋友可能问:这不就是适配器吗?
区别很大:
| 模式 | 目标 | 比喻 |
|---|---|---|
| 外观 | 简化接口 | 翻译官——让你听懂复杂术语 |
| 适配器 | 转换接口 | 转换插头——让英标插头插进国标插座 |
| 中介者 | 协调依赖 | 居委会——协调邻里纠纷 |
目标不同,别用岔了。
外观模式在 ASP.NET Core 中的全景
回顾一下,框架里的外观无处不在:
| 外观对象 | 帮你搞定了什么? |
|---|---|
IServiceCollection | 服务注册的繁琐管理 |
IApplicationBuilder | 中间件委托链的构建 |
WebApplication | 宿主 + 配置 + 日志 + 服务器,一键启动 |
HttpContext | 请求 / 响应 / 用户 / 会话等子对象的聚合 |
ControllerBase | 模型绑定、验证、路由、结果执行 |
IWebHostEnvironment | 环境变量和路径的读取与缓存 |
这些外观加在一起,构成了 ASP.NET Core 的“易用性底座”。
写给自己的思考
理解外观模式,你不仅能更深刻地领会框架的设计哲学,更能在自己的代码里借鉴这种思路。
当你觉得某个模块用起来很“拧巴”时,试着给它套一层外观。
把复杂性藏到背后,把简单的 API 留给调用者。这正是 ASP.NET Core 成功的秘诀之一:强大,但不折磨人。
下次你再用 app.MapControllers() 的时候,记得在心里默默感谢一下那个替你在背后扛事的外观。然后想想——你自己的代码里,有没有哪个复杂子系统,也值得配一个这样的“前台接待”?
