提问人:Aboliii 提问时间:11/15/2023 更新时间:11/15/2023 访问量:53
如何处理 ASP.NET Core 中包含两个构造函数的控制器?
How to deal with a Controller containing two constructors in ASP.NET Core?
问:
我有一个 API,我的意思是通过 xUnit 和 Moq 进行单元测试。它实际上有一个构造函数,但我必须根据 Visual Studio 的建议添加另一个构造函数以用于测试的特定目的。它非常适合测试,但是当我尝试通过 Swagger 测试 API 时,我遇到了另一个错误。“System.InvalidOperationException:在类型'Product.API.Controllers.ProductController'中找到了多个接受所有给定参数类型的构造函数。应该只有一个适用的构造函数”。 以下是控制器的内容
public class ProductController : ControllerBase
{
private readonly IMediator _mediator;
private readonly IConfiguration _configuration;
private readonly UserManager<User> _userManager;
private readonly SignInManager<User> _signInManager;
private readonly ProductDbContext _productDbContext;
private readonly IUserService _userService;
public ProductController(IConfiguration configuration, UserManager<User> userManager, ProductDbContext productDbContext
, SignInManager<User> signInManager, IUserService userService)
{
_configuration = configuration;
_userManager = userManager;
_productDbContext = productDbContext;
_signInManager = signInManager;
_userService = userService;
}
public ProductController(IMediator mediator)
{
_mediator = mediator;
}
[HttpGet]
[AllowAnonymous]
public async Task<IActionResult> GetAllProducts()
{
var products = await _mediator.Send(new GetAllProductsQuery());
return Ok(products);
}
下面是单元测试的代码
private readonly Mock<IMediator> _mediatorMock;
private readonly ProductController _controller;
public ProductControllerTest()
{
_mediatorMock = new Mock<IMediator>();
_controller = new ProductController(_mediatorMock.Object);
}
[Fact]
public async Task ProductController_GetAllProducts_ShouldReturnStatusCode200()
{
//arrange
var expected = new List<ProductModelDTO>();
_mediatorMock.Setup(m=> m.Send(It.IsAny<GetAllProductsQuery>(), default(CancellationToken))).ReturnsAsync(expected);
//act
var result = await _controller.GetAllProducts();
//assert
var okResult = result as OkObjectResult;
okResult?.StatusCode.Should().Be(200);
okResult?.Value.Should().BeOfType<List<ProductModelDTO>>();
}
我遇到了一个可能的解决方案;将 [FromServices] 添加到第二个构造函数。但是我遇到了同样的错误!
答:
在 C# 语言级别,使用多个构造函数没有错。但是,在框架级别(其中 ASP.NET 中使用的依赖注入器正在尝试自动创建控制器的实例),多个构造函数会混淆框架。
使用一个构造函数。
如果您的控制器具有以下依赖项:
private readonly IMediator _mediator;
private readonly IConfiguration _configuration;
private readonly UserManager<User> _userManager;
private readonly SignInManager<User> _signInManager;
private readonly ProductDbContext _productDbContext;
private readonly IUserService _userService;
然后把它们都放在构造函数中:
public ProductController(
IConfiguration configuration,
UserManager<User> userManager,
ProductDbContext productDbContext,
SignInManager<User> signInManager,
IUserService userService,
IMediator mediator)
{
_configuration = configuration;
_userManager = userManager;
_productDbContext = productDbContext;
_signInManager = signInManager;
_userService = userService;
_mediator = mediator;
}
这样,框架就会知道要使用哪个构造函数,因为只有一个构造函数。每当需要添加新的依赖项时,请将该依赖项添加到构造函数中,而不是仅为该依赖项添加全新的构造函数。
将所有依赖项移动到单个 ctor:
public ProductController(IConfiguration configuration,
UserManager<User> userManager,
ProductDbContext productDbContext,
SignInManager<User> signInManager,
IUserService userService,
IMediator mediator)
{
// ...
}
然后在测试中将所有值传递给它:
public ProductControllerTest()
{
_mediatorMock = new Mock<IMediator>();
// mock and pass everything:
_controller = new ProductController(..., _mediatorMock.Object);
}
或者,您可以使用 ASP.NET Core 功能来使用方法注入(使用 FromServices
进行操作注入),尽管您仍然需要将所有通用参数传递给 ctor,但您可以减少它们的总数,并仅在需要它们的地方管理非共享参数:ProductController
public ProductController(IConfiguration configuration, UserManager<User> userManager, ProductDbContext productDbContext
, SignInManager<User> signInManager, IUserService userService)
{
_configuration = configuration;
_userManager = userManager;
_productDbContext = productDbContext;
_signInManager = signInManager;
_userService = userService;
}
[HttpGet]
[AllowAnonymous]
public async Task<IActionResult> GetAllProducts([FromServices] IMediator mediator)
{
var products = await _mediator.Send(new GetAllProductsQuery());
return Ok(products);
}
请注意,一般来说,注入数据库上下文和单独的服务之类的东西,并且看起来像一种气味,常见的方法是(几乎)使用它来使用所有东西。IMediator
IMediator
评论
...
new ProductController(_configMock.Object, userManagerMock.Object, ..., _mediatorMock.Object)
评论
IMediator