Asp.Net Core 2.0 MVC构造函数传参与单元测试方案咨询
我来帮你梳理这个问题,咱们一步步实现既符合依赖注入原则、又能优雅传递Token的方案,同时满足单元测试需求:
解决方案思路
核心是把Token的读写逻辑抽象成可注入服务,避免直接在构造函数里传字符串Token(没法DI),也避免控制器直接操作HttpContext(不利于单元测试)。
1. 抽象Token管理逻辑:创建ITokenProvider接口及实现
先把Token的读写从控制器里抽出来,做成独立的服务,这样既能通过DI注入,又能在测试时轻松Mock。
public interface ITokenProvider { // 从Cookie获取Token string GetToken(); // 将Token写入Cookie void AddToken(string token); } public class CookieTokenProvider : ITokenProvider { private readonly IHttpContextAccessor _httpContextAccessor; public CookieTokenProvider(IHttpContextAccessor httpContextAccessor) { _httpContextAccessor = httpContextAccessor; } public string GetToken() { _httpContextAccessor.HttpContext.Request.Cookies.TryGetValue("token", out var token); return token; } public void AddToken(string token) { var cookieOptions = new CookieOptions { // 注意:你原来的Expires设为AddDays(-1)会让Cookie立即过期,这里改成合理的有效期 Expires = DateTimeOffset.Now.AddDays(7), HttpOnly = true, // 提升安全性,防止前端JS读取 Secure = true // HTTPS环境下建议开启,仅通过HTTPS传输Cookie }; _httpContextAccessor.HttpContext.Response.Cookies.Append("token", token, cookieOptions); } }
2. 更新Startup注册服务
需要注册IHttpContextAccessor(因为我们的TokenProvider要访问HttpContext),以及新的ITokenProvider服务:
public void ConfigureServices(IServiceCollection services) { // 注册HttpContext访问器,TokenProvider需要它 services.AddHttpContextAccessor(); services.AddSingleton<ITempDataProvider, CookieTempDataProvider>(); services.AddDistributedMemoryCache(); services.AddSession(); services.AddTransient<IAccountService, AccountService>(); services.AddTransient<IAccountAdapter, AccountAdapter>(); services.AddTransient<INozzleService, NozzleService>(); // 注册TokenProvider服务 services.AddTransient<ITokenProvider, CookieTokenProvider>(); services.AddTransient<INozzleAdapter, NozzleAdapter>(); services.AddMvc(); }
3. 调整NozzleAdapter:依赖ITokenProvider而非直接传字符串
原来的NozzleAdapter构造函数接收string token,现在改成依赖ITokenProvider,让它自己去获取Token,这样DI可以自动注入,还能灵活应对Token变化:
public class NozzleAdapter : INozzleAdapter { private readonly ITokenProvider _tokenProvider; private readonly HttpClient _client; // 注入ITokenProvider和HttpClient(或者保持你原有的HttpClient创建方式) public NozzleAdapter(ITokenProvider tokenProvider, HttpClient client) { _tokenProvider = tokenProvider; _client = client; } public async Task<YourResponseType> SendRequestAsync(string apiUrl) { var token = _tokenProvider.GetToken(); if (!string.IsNullOrWhiteSpace(token)) { // 设置请求头 _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); } // 发送请求并处理响应 var response = await _client.GetAsync(apiUrl); response.EnsureSuccessStatusCode(); return await response.Content.ReadFromJsonAsync<YourResponseType>(); } }
4. 优化AccountController:用ITokenProvider管理Token
你已经更新了AccountController的构造函数,现在只需要把原来的AddCookie方法替换成ITokenProvider的AddToken即可:
public class AccountController : Controller { private readonly IAccountService _service; private readonly ITokenProvider _tokenProvider; public AccountController(IAccountService service, ITokenProvider tokenProvider) { _service = service; _tokenProvider = tokenProvider; } [HttpPost, AllowAnonymous, ValidateAntiForgeryToken] public IActionResult Login(LoginViewModel loginModel, string returnUrl = null) { returnUrl = string.IsNullOrWhiteSpace(returnUrl) ? ApiConstants.Dashboard : returnUrl; ViewData["ReturnUrl"] = returnUrl; if (!ModelState.IsValid) return View(loginModel); var token = _service.Login(loginModel); if (string.IsNullOrWhiteSpace(token)) return View(loginModel); TempData["token"] = token; // 用TokenProvider写入Cookie,替代原来的AddCookie方法 _tokenProvider.AddToken(token); return RedirectToAction("Index", "Dashboard"); } }
5. 简化NozzleController:移除直接读取Cookie的逻辑
现在NozzleService依赖的NozzleAdapter已经能自己获取Token了,控制器只需要注入INozzleService即可,不需要直接操作HttpContext:
public class NozzleController : Controller { private readonly INozzleService _nozzleService; // 构造函数只保留业务服务依赖,符合单一职责 public NozzleController(INozzleService nozzleService) { _nozzleService = nozzleService; } public async Task<IActionResult> Index() { var nozzleData = await _nozzleService.GetNozzleDataAsync(); return View(nozzleData); } }
方案优势
- 避免多构造函数:所有类都只有一个构造函数,依赖的都是抽象接口,完全符合DI原则
- 可测试性:所有依赖都是抽象接口,单元测试时可以轻松Mock(比如Mock
ITokenProvider返回固定测试Token,MockINozzleService返回模拟数据) - 单一职责:每个类只做自己的事——Token管理由
ITokenProvider负责,NozzleAdapter只负责发送请求,控制器只处理请求响应 - 符合Asp.Net Core最佳实践:所有服务都通过DI容器注册,没有硬编码依赖
内容的提问来源于stack exchange,提问作者Sibeesh Venu
相关产品推荐
相关产品推荐

