Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

How to deal with a Controller containing two constructors in ASP.NET Core?

I have an API upon which I mean to do Unit Testing via xUnit and Moq. It had one constructor actually, but I had to add another one for the specific purpose of Testing as per a suggestion by Visual Studio. It worked perfect for Testing, but I encountered another error once I tried to test the API via Swagger.
"System.InvalidOperationException: Multiple constructors accepting all given argument types have been found in type ‘Product.API.Controllers.ProductController’. There should only be one applicable constructor".
below is the contents of the controller

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);
    }

Below is the code for Unit Testing

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>>();
}

I came across a possible solution; to add a [FromServices] to the second constructor. But I got the same error!

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

>Solution :

Move all dependencies to a single ctor:

public ProductController(IConfiguration configuration, 
    UserManager<User> userManager, 
    ProductDbContext productDbContext, 
    SignInManager<User> signInManager, 
    IUserService userService, 
    IMediator mediator)
{
    // ...
}

And then pass all values to it in test:

public ProductControllerTest()
{
    _mediatorMock = new Mock<IMediator>();
    // mock and pass everything:
    _controller = new ProductController(..., _mediatorMock.Object); 
}

Alternatively you can use the ASP.NET Core ability to use method injection (Action injection with FromServices) though you still will need to pass all common parameters to ProductController ctor, but you can reduce the overall number of them and manage non-shared ones only where they are needed:

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);
}

Note that in general injecting both IMediator and things like db contexts and separate services, and looks like a smell, common approach is either use IMediator for (almost) everything if using it at all.

Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading