☰
ASP.NET Core面试核心:中间件、依赖注入、JWT与EF Core实战解析
2026/10/14 7:52:02 网站建设 项目流程

1. 为什么面试官总爱问这些“八股”

做了快五年面试官,每年春秋招我都要过几百份简历、面几十个候选人。感触最深的一件事:很多人不是挂在算法题上,而是挂在ASP.NET Core最基础的概念上。你说AsyncLocal讲不清楚、表达式树写不明白,这些高级货我反而不强求,但中间件管线、依赖注入生命周期、JWT认证流程这种天天都在用的东西,如果支支吾吾讲不透,基本就告别offer了。

这篇文章把我在一线面试里反复听到的问题做了一个精简汇总,每道题下面的答案都按我自己的理解和项目实操经验整理过,不是背一段教科书原文就完事。适合准备跳槽、参加春招秋招的.NET开发者,也适合刚入行想快速建立框架知识体系的同学。说白了,这里面的每一道题,都是你入职后每天都会碰到的代码,只不过面试官换了个问法而已。

2. 框架核心:中间件管线与请求处理

2.1 什么是中间件,为什么必须说清楚

先看一道开场高频题:请描述一次HTTP请求在ASP.NET Core中是如何被处理的。

标准答案的骨架是这样的:请求到达服务器后,Kestrel(或其他服务器实现)接收原始HTTP请求,交给ASP.NET Core的托管层;托管层创建HttpContext上下文,然后请求进入中间件管线——这是一串按注册顺序排列的组件,每个组件可以处理请求、调用下一个组件、再处理响应。请求依次穿过每一个中间件,最后到达Endpoint(路由到的Controller Action或Minimal API的Handler),执行完业务代码后,响应按原路返回。

面试官问这道题,真正想看的是你能不能说出“管线”两个字。很多人只记得Startup里注册了一堆app.UseXXX(),但不理解它们是怎么串起来的。打个比方,中间件管线像火车站的一排安检口,每个口只负责一件事——查身份证、查行李、验票,全过了才能上车;响应出来时还要再过一遍这些口,只不过方向相反。

2.2 高频题:Use和Run的区别

Use与Run有什么区别?精简答案:Use是短接型中间件,可以调用next()把请求传给下一个;Run是终端型中间件,不调用next(),请求到这里就终止了。所以Run永远放在管线的最后,而Use可以在Run之前串联无限多个。

面试官喜欢在这个基础上追一句:UseWhen又是干什么的?UseWhen是条件式的短接,比如app.UseWhen(context => context.Request.Query.ContainsKey("admin"), app2 => app2.UseMiddleware ()),满足条件才走分支管线。这个考点主要是看你有没有做过条件化处理的需求,比如不同租户走不同的中间件逻辑。

还有个高频追问:中间件和过滤器(Filter)有什么区别?很多候选人直接懵了。核心区别是:中间件在Endpoint执行之前运行,管的是整条HTTP管线;Filter在Action执行前后运行,属于MVC框架层面,只能在Controller/Action的上下文中生效。中间件看不到Controller里定义的Action参数,Filter则能直接在参数绑定前后拦截。简单记:中间件是大门口的保安,Filter是进到公司之后的楼层门禁。

2.3 实战分支:自定义中间件的正确写法

面试里经常会出现“你写过自定义中间件吗”这种追问。我给一个能直接用的精简模板:

public class RequestLoggingMiddleware { private readonly RequestDelegate _next; private readonly ILogger<RequestLoggingMiddleware> _logger; public RequestLoggingMiddleware(RequestDelegate next, ILogger<RequestLoggingMiddleware> logger) { _next = next; _logger = logger; } public async Task InvokeAsync(HttpContext context) { var start = Stopwatch.GetTimestamp(); try { await _next(context); } finally { var elapsed = Stopwatch.GetElapsedTime(start); _logger.LogInformation("Request {Method} {Path} took {Elapsed}ms", context.Request.Method, context.Request.Path, elapsed.TotalMilliseconds); } } }

注册方式有两种:app.UseMiddleware (),或者用工厂扩展app.UseRequestLogging()。要注意的是,中间件构造函数里只能注入单例服务(比如ILogger),因为中间件实例在首次请求时创建一次、之后复用;如果想用Scoped服务(比如DbContext),必须从InvokeAsync的context参数里拿,比如context.RequestServices.GetService ()。这个细节面试里考过多次,翻车率很高。

3. 依赖注入:必考中的必考

3.1 三个生命周期

ASP.NET Core的依赖注入(DI)是内置的,这道题几乎场场必现:Transient、Scoped、Singleton区别是什么?

  • Transient:每次请求容器都新建一个实例。适合轻量、无状态的服务,比如日志、配置读取。
  • Scoped:每个作用域内同一个实例。在Web应用里,一次HTTP请求就是一个作用域,所以同一个请求里多次注入是同一个对象。
  • Singleton:进程内只创建一个实例,所有请求共享。适合无状态的重量级对象,或者需要全局唯一的服务。

面试官喜欢立刻追问:DbContext应该用哪个生命周期?答案是Scoped。因为DbContext内部有变更跟踪器状态,如果做成Singleton,会面临多请求并发写同一个实例的问题,轻则数据错乱,重则直接抛异常;做成Transient虽然能用,但每次操作都新建连接上下文,性能浪费很大。Scoped正好匹配“一个请求一次数据库上下文”的天然边界。

3.2 高频题:为什么构造函数注入是主流

紧接着的追问往往是:属性注入和构造函数注入怎么选?精简答案是:优先构造函数注入。因为构造函数注入能让依赖关系显式化——new一个服务必须把依赖全传进来,少一个都编译不过,容器的注册也能被编译器检查到。属性注入在ASP.NET Core内置容器里干脆就不支持,只有第三方容器(Autofac等)才有这个能力。

还有个进阶考点:同一个请求里,Scoped服务在其他地方拿,会不会拿到不同的实例?很多人不知道,DefaultDI容器解析服务有两条路径:一种是构造函数注入,走的是请求作用域;另一种是直接从IServiceProvider解析,如果这个Provider是从根容器CreateScope()手动创建的,那就另算作用域。所以你自己手动new Scope再手动Dispose时,务必记得释放,否则Scoped服务会变成“不会过期的垃圾”,内存泄漏就是这么来的。

3.3 容易翻车的Captive Dependency

这个问题实际项目中踩过坑,面试聊起来也很加分:Captive Dependency(被囚禁依赖)是什么意思?

当一个单例服务依赖了一个Scoped服务,情况就变成了“单例在第一次创建时把Scoped实例抓进去,之后一直复用这个过期实例”。典型例子:一个Singleton的缓存服务注入了Scoped的DbContext,第一次请求一切正常,第二次请求继续用同一个DbContext——上下文状态混乱、并发写冲突全来了。面试时能主动说出这个坑,绝对比背一百个概念更能打动面试官。

4. 鉴权与授权:JWT几乎跑不掉

4.1 JWT认证流程一分钟讲清楚

问“你做过用户鉴权吗”是普通题,问“JWT认证是怎么工作的”才是高频题。精简版答案分四步:

  1. 用户提交用户名密码,服务端验证通过后,用私钥签发一个JWT返回给客户端;
  2. JWT包含三部分:Header(算法)、Payload(用户信息、过期时间等)、Signature(签名);
  3. 客户端后续请求把JWT放在Authorization: Bearer 头里;
  4. 服务端用公钥或共享密钥验证签名,通过后解析Payload,把用户身份塞进HttpContext.User。

面试官爱追问:为什么是JWT而不是Session?核心原因是扩展性。Session默认存在服务器端内存,多实例部署时要配分布式缓存做Session共享,复杂度直接拉高;JWT本身无状态,服务端只需验证签名,任何一台机器都能直接认,API网关、微服务场景下特别方便。但JWT也引入新问题:不能主动注销——服务端踢不掉一个还没过期的token,只能等它自然过期。所以很多项目会再加一层短有效期token加refresh token的方案。

4.2 面试官爱问的过期与刷新

这个问题再往上钻就是:Access Token过期了怎么办?标准做法是Refresh Token。客户端拿短时效的Access Token(比如15分钟)调接口,过期后用Refresh Token换新的。Refresh Token本身时效长一点(比如7天),并且可以存库、可以吊销。

面试里再深入一层就是“双token机制”的权衡:Access Token太短,用户体验差;太长,安全风险高。我推荐的做法是Access Token设15~30分钟,Refresh Token设7天,超过7天强制重新登录。这个阈值要结合业务调整,不是固定的。

4.3 授权策略与自定义Requirement

鉴权通过之后,面试官会问授权:Authorize属性和Policy是什么关系?[Authorize]只是开关,真正复杂的规则要通过AddAuthorizationBuilder注册Policy。比如公司后台要满足“角色为Admin且地区在上海”才能访问,不能光靠Role列加字符串判断,得写成自定义Requirement:

public class RegionRequirement : IAuthorizationRequirement { public string Region { get; } public RegionRequirement(string region) => Region = region; } public class RegionHandler : AuthorizationHandler<RegionRequirement> { protected override Task HandleRequirementAsync( AuthorizationHandlerContext context, RegionRequirement requirement) { if (context.User.HasClaim("region", requirement.Region)) context.Succeed(requirement); return Task.CompletedTask; } }

面试官一般不会要求你写到第四层,但你要能说出“Policy由多个Requirement组成,每个Requirement对应一个Handler”这条链,说明你不仅会写[Authorize],还理解授权是怎么决策的。

5. EF Core:数据访问的送分题

5.1 跟踪查询与非跟踪查询

EF Core的题只要面试官是.NET方向的,基本都会带一题:AsNoTracking()是干什么的?跟踪查询(默认)会让EF Core把查出来的实体放进变更跟踪器,后续修改实体再SaveChanges时,EF Core自动生成UPDATE语句。非跟踪查询(AsNoTracking())不记录实体状态,实体就是普通的只读数据,性能更好、内存占用更少。

精简结论:只读展示用AsNoTracking(),需要修改的用默认跟踪。还有一个冷门考点:同一个DbContext里同时对同一个主键实体做跟踪和做非跟踪查询,会发生什么?答案是一般不会冲突,EF Core对同主键的已跟踪实体会做身份解析,返回同一实例;但如果开了AsNoTracking,再对同一主键查询又跟踪实体,就可能遇到“已存在相同主键的跟踪实体”异常。这种边边角角的坑,能踩到说明你真写过。

5.2 性能优化三板斧

再加一道程序:EF Core性能优化你知道哪些?我的精简答案是三板斧:

  • 第一斧:只查需要的字段,用Select投影,别整表拖回来;
  • 第二斧:分页用Skip/Take,但数据量大时要考虑键集分页(WHERE id > lastId ORDER BY id);
  • 第三斧:涉及大量更新时别循环SaveChanges,用ExecuteUpdate/ExecuteDelete批量生成一条SQL。

面试官会追问:Include和Join的区别?Include是告诉EF Core加载导航属性,让它自己构造JOIN或额外查询;Join是你手动指定两个集合的关联。大多数场景用Include更符合EF Core的模型表达方式,但涉及复杂筛选时,Join或直接写FROM xxx JOIN yyy更可控。

5.3 面试里常见的EF Core坑

这个环节几乎是送分题,但翻车率极高。典型的坑包括:在循环里执行查询(N+1问题,每次迭代都发一条SQL);把整个实体返回给API(暴露了不该暴露的字段,还浪费带宽);不配置索引导致查询慢到被DBA投诉。回答时如果能主动说“我会在循环外先查询好数据,用内存里做字典匹配”,面试官立刻知道你有真实项目经验。

6. 异步编程与并发

6.1 async/await为什么重要

为什么ASP.NET Core中的Action都推荐写成async?这道题很基础,但能过滤掉很多不会看源码的候选人。核心原因是线程效率:同步接口在等待数据库返回时,当前线程被占着不动;异步接口在await时把线程释放回线程池,让线程池去处理其他请求。同样的服务器配置,异步接口支持的并发请求数可能差一个数量级。

精简答案是:async/await不是把操作变快,而是让等待期间不占线程。我用生活类比:同步就像你排队办事时一直占着窗口直到办完;异步是你把资料交上去、留个电话,窗口可以先服务别人。

6.2 高频题:同步阻塞的后果

追问来了:如果我在async方法里用Task.Wait()或.Result,会发生什么?在ASP.NET Core的SynchronizationContext下,很可能直接死锁:同步阻塞在等待异步完成,而异步里的延续需要在原同步上下文上继续,这个上下文又被阻塞占用,两边互相等待。虽然ASP.NET Core的SynchronizationContext是线程池上下文,不一定每台机器都死锁,但这种写法一定降低吞吐。面试时被问到,直接说“绝不在异步代码里用.Result和.Wait()”就是满分回答。

7. 其他高频考点

7.1 Controller、Minimal API怎么选

近两年的新题是:Controller风格和Minimal API风格怎么选?其实没有绝对答案,我的经验是:传统Web API项目、和MVC模式打交道多、需要大量过滤器(ActionFilter/ExceptionFilter)的,用Controller更顺手;小而轻的HTTP接口、追求极低启动开销、愿意把代码结构控制得很小很清晰的,用Minimal API。不一定非黑即白,一个项目里两种风格同时存在也很正常,关键是团队统一。

面试里还会问:Controller是不是越轻越好?是。Controller只做参数校验、调用业务服务、组装返回结果,真正的业务逻辑放独立Service层。那种Controller里写了一千行SQL和业务判断的代码,面试官一看到心里已经扣分了。

7.2 配置系统与IOptions

appsettings.json的配置是怎么被读取的?精简答案:ASP.NET Core把配置系统做成一个kv的层次结构,appsettings.json只是其中一个配置源,还可以叠加环境变量、命令行参数、用户机密等;读取时通过IConfiguration.GetSection("Database:ConnectionString")按路径访问,或者绑定到强类型类再注入IOptions 。

面试官会追问:IOptions、IOptionsSnapshot、IOptionsMonitor区别?精简版:IOptions是单例,启动时读一次配置后不再更新;IOptionsSnapshot是Scoped,每次请求读一次,适合配置可能变、且你想每次拿到新值的场景;IOptionsMonitor是单例,但支持配置变更回调,适合缓存配置又要热更新的情况。三个选型记不住的话,记住“要热更新用Monitor,要每次请求最新用Snapshot,其他用Options”就够了。

7.3 CORS与常见实践

前端调API必写CORS,面试题也常带一个:跨域是什么?服务端怎么配CORS?跨域是浏览器的安全机制,默认禁止一个源访问另一个源的资源。服务端在Program.cs里写:

builder.Services.AddCors(options => { options.AddPolicy("AllowFrontend", policy => { policy.WithOrigins("https://myapp.example.com") .AllowAnyHeader() .AllowAnyMethod(); }); }); app.UseCors("AllowFrontend");

注意两个细节:一是AllowAnyOrigin不能用AllowCredentials,浏览器和安全策略都不允许“携带凭据+任意来源”同时出现;二是CORS是浏览器行为,直接HTTP请求或客户端工具(比如Postman)根本走不到CORS拦截,别指望用它做服务器安全防线。第三个细节很多人不知道:OPTIONS预检请求如果没有被正确响应,前端控制台会报CORS错误,实际去后端一看日志,路由都正常,要排查的是中间件注册顺序。

8. 面试实战中的避坑经验

8.1 面试官追问时怎么应对

把这些题目背完只是第一步,真正拉开差距的是表达方式。我在面试中观察到的现象是:候选人能把答案背得很顺,但一问“你项目里为什么这么配”就卡住。所以我的建议是,每道题都准备一个真实的项目场景来配对。比如面试官问Scoped,你说“我们项目里DbContext就是Scoped的,因为一个请求一个工作单元,用Singleton会并发爆炸”,效果比干背定义好十倍。

另一个经验:遇到不会的题,别僵住乱编。坦诚说“这个我没实际用过,但按我对XX的理解,大概是……”既诚实,又展示学习能力。反之,为了圆一个谎把话题越扯越远,面试官几周后面试下一批人,根本记不住你。

8.2 自己当面试官以后才知道的事

说点平时不好公开发的东西。当了面试官以后我发现,评分标准其实比想象中宽容:八股题不要求字字精准,面试官重点看两件事——第一,你的知识是“串起来的”还是“孤立背出来的”;第二,回答中能不能表现出真实项目的痕迹。比如提到JWT,能顺带说一句“我们当时还配了Redis存Refresh Token实现踢人”,这就属于加分项,因为它在展示你完整做过,而不是看过几篇博客。

所以我强烈建议准备面试的朋友,按“概念定义 -> 为什么 -> 项目实例 -> 有什么坑”四层结构去准备每一道题。前两层是八股,第三层是经验,第四层是亮点。把这四个层次补全,上面整理的所有题目,你都能聊得比标准答案更出彩。我自己从候选人变成面试官以后,最大的体会就是:八股只是入场券,深挖下去的那几步,才是真正决定你去留的东西。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询