ASP.. NET MVC WebAPI从异步任务创建ViewModel

本文关键字:任务 创建 ViewModel 异步 NET MVC WebAPI ASP | 更新日期: 2023-09-27 18:02:28

我使用ASP编写web应用程序。. NET MVC WebAPI和我想将当前的同步代码转换为异步代码进行优化。问题是我用从存储库中获取的多个对象填充ViewModel。这些来自存储库的调用应该是异步的。

让我们假设我有关于这个接口的存储库调用的签名

public interface ICompanyRepository
{
    IEnumerable<Company> GetCompanies();
    IEnumerable<Address> GetAddresses();
}

视图模型定义
public class CompaniesFullViewModel
{
    public IEnumerable<Company> Companies { get; set; }
    public IEnumerable<Address> Addresses { get; set; }
}

和控制器:

public class CompanyController
{
    public readonly ICompanyRepository Repository { get; private set; }
    public CompanyController(IRepository repository)
    {
        Repository = repository;
    }
    [ResponseType(typeof(CompaniesFullViewModel))]
    public HttpResponseMessage Get()
    {
        var companies = Repository.GetCompanies();
        var addresses = Repository.GetAddresses();
        HttpStatusCode statusCode = companies.Any()
             ? HttpStatusCode.OK
             : HttpStatusCode.PartialContent;
        return
            Request.CreateResponse(
                statusCode,
                new CompaniesFullViewModel
                {
                    Companies = companies,
                    Addresses = addresses
                });
    }
}

此外,我还对控制器进行了测试:

[TestClass]
public sealed class CompanyTestController : BaseTestController
{
    #region Fields
    private static Mock<ICompanyRepository> _repositoryMock;
    private static CompanyController _controller;
    #endregion
    [ClassInitialize]
    public static void Initialize(TestContext testContext)
    {
        // Mock repository
        _repositoryMock = new Mock<ICompanyRepository>();
        DependencyResolver.Default.Container.RegisterInstance(_repositoryMock.Object);
        // Create controller
        _controller =
            DependencyResolver.Default.Container.Resolve<CompanyController>();
        // Init request
        _controller.Request = new HttpRequestMessage();
        _controller.Request.SetConfiguration(new HttpConfiguration());
    }
    [ClassCleanup]
    public static void Cleanup()
    {
        _controller.Dispose();
    }
    [TestMethod]
    public void Get_ActionExecutes_ReturnsEmptyCompaniesViewModel()
    {
        var companies = new List<Company>();
        var addresses = new List<Address>();
        // Setup fake method
        _repositoryMock
            .Setup(c => c.GetCompanies())
            .Returns(companies);
        _repositoryMock
            .Setup(c => c.GetAddresses())
            .Returns(addresses);
        // Execute action
        var response = _controller.Get();
        // Check the response
        Assert.AreEqual(HttpStatusCode.PartialContent, response.StatusCode);
    }
}

我如何将控制器转换为异步,如果存储库是异步的,签名看起来像这样:

public interface ICompanyRepository
{
    Task<IEnumerable<Company>> GetCompaniesAsync();
    Task<IEnumerable<Address>> GetAddressesAsync();
}

ASP.. NET MVC WebAPI从异步任务创建ViewModel

您需要做的是将Controller动作更改为async,并将返回类型更改为Task<>。然后,您可以等待异步存储库调用:

[ResponseType(typeof(CompaniesFullViewModel))]
public async Task<HttpResponseMessage> Get() // async keyword. 
{
    var companies = await Repository.GetCompaniesAsync(); // await
    var addresses = await Repository.GetAddressesAsync(); // await
    HttpStatusCode statusCode = companies.Any()
         ? HttpStatusCode.OK
         : HttpStatusCode.PartialContent;
    return
        Request.CreateResponse(
            statusCode,
            new CompaniesFullViewModel
            {
                Companies = companies,
                Addresses = addresses
            });
}

按照约定,你也可以改变控制器动作的名字以Async结尾,尽管如果你使用RESTful约定和/或路由属性,控制器动作的实际名字并不重要。

测试

我使用XUnitNUnit,但似乎MSTest也支持异步方法的测试,Moq也提供了Async版本的设置:

[Test]
public async Task Get_ActionExecutes_ReturnsEmptyCompaniesViewModel() // async Task
{
    var companies = new List<Company>();
    var addresses = new List<Address>();
    // Setup fake method
    _repositoryMock
        .Setup(c => c.GetCompaniesAsync())
        .ReturnsAsync(companies); // Async
    _repositoryMock
        .Setup(c => c.GetAddressesAsync())
        .ReturnsAsync(addresses); // Async
    // Execute action
    var response = await _controller.Get(); // Await
    // Check the response
    Assert.AreEqual(HttpStatusCode.PartialContent, response.StatusCode);
    _repositoryMock.Verify(m => m.GetAddressesAsync(), Times.Once);
    _repositoryMock.Verify(m => m.GetCompaniesAsync(), Times.Once);
}

作为题外话,似乎你正在使用Setter依赖注入。另一种选择是使用构造函数注入,它的好处是确保类始终处于有效状态(即,在等待依赖项设置时没有短暂状态)。这还允许将依赖项(在本例中是您的存储库)设置为readonly