You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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(比如MockITokenProvider返回固定测试Token,MockINozzleService返回模拟数据)
  • 单一职责:每个类只做自己的事——Token管理由ITokenProvider负责,NozzleAdapter只负责发送请求,控制器只处理请求响应
  • 符合Asp.Net Core最佳实践:所有服务都通过DI容器注册,没有硬编码依赖

内容的提问来源于stack exchange,提问作者Sibeesh Venu

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 07:06:39