控制器构造函数中的MVC异步方法

7
我正在尝试制作动态菜单(存储在数据库中),该菜单显示在所有Web应用程序页面上。 使用Google,我发现将菜单视图制作为主视图(_Layout.cshtml)的一部分更好。因此,控制器的每个操作方法都必须包含带有菜单模型的数据。为了避免代码重复,我找到了创建基本控制器并使用其构造函数提供数据的解决方案:

https://learn.microsoft.com/en-us/aspnet/mvc/overview/older-versions-1/views/passing-data-to-view-master-pages-cs

此外,我正在尝试使用async/await的功能,我的PageService(菜单)正在使用ToListAsync()从数据库获取数据。所以现在我有一个问题,BaseController构造函数有一个async方法:
public class BaseController : AsyncController, IBaseController
{
    private readonly IPageService _pageService;

    public BaseController(IPageService pageService)
    {
        _pageService = pageService;
        SetBaseViewModelAsync();
    }

    private async Task SetBaseViewModelAsync()
    {
        ViewData["Pages"] = await _pageService.GetAllAsync();
    }
}

我知道这是 糟糕的代码,但我不知道如何正确地设计这种情况。也许有另一种更好的方法来创建动态菜单或以异步方式获取数据?
此外,我发现了这篇文章,但我不知道是否可以应用其解决方案,因为我不知道是否可以处理控制器实例的创建:

http://blog.stephencleary.com/2013/01/async-oop-2-constructors.html


那是糟糕的设计,你说得对。你应该考虑使用中间件或操作过滤器。 - Camilo Terevinto
谢谢您的回复! 您能否提供一些具有适当设计的文章链接?因为我已经谷歌搜索了1-2个小时,以找到如何实现动态菜单。 - Dima Glushko
2
这似乎是一个很好的 Child Action 的候选。还可以参考这个问题 https://dev59.com/xWEh5IYBdhLWcg3w12il - Jasen
@Jasen,RenderAction 无法调用异步操作。 - Dima Glushko
2个回答

6

不必从基本控制器中派生所有内容(这可能需要很多额外的工作和测试),您只需创建一个名为MenuController的控制器,创建一个名为Default的方法,然后从您的布局中调用它即可:

[ChildActionOnly]
public Default()
{
  var viewModel = _pageService.GetAllAsync();
  return Partial(viewModel);
}

在您的布局中:

@{Html.RenderAction("Default", "Menu");}

这真的是最简单和最干净的解决方案。最大的优点是您可以将菜单缓存与方法调用分开控制。对于asp.net-mvc(1-5)来说,没有好的解决方案以这种方式运行异步代码。(ActionFilters不能是异步的且(Render)Partials不能是异步的。您仍然可以调用异步方法,但它将同步运行。 Render vs Non-Render Performance

谢谢您的回复!我对MVC还不熟悉,所以我会阅读有关RenderAction的内容,并尝试您的解决方案。此外,缓存的可能性在这里非常棒! - Dima Glushko
应用了您的解决方案,但是它有一些细微之处:子操作不能是异步的: https://dev59.com/fmAf5IYBdhLWcg3w_W5j#47962963 因此,我决定不将操作设置为ChildAction。必须只将其设置为子操作吗? 您能否修改您的答案,以便接受是正确的?因为现在您的示例中包含异步代码在ChildAction中。 - Dima Glushko
异步代码使用async/await关键字,而我的代码没有使用这些关键字。它应该能够编译并显示警告信息。否则,你可以在同步方法中调用异步代码。具体请参考此链接:https://dev59.com/Dmox5IYBdhLWcg3wOx5i#25097498。 - Erik Philips
在同步方法中调用异步代码是一个不好的主意。我认为最好删除ChildAction属性并保留异步代码。 - Dima Glushko
我错了,那只是我的缓存问题,它给我返回了正确的结果。我已经清除了缓存,但我仍然遇到了与此处相同的错误: https://dev59.com/fmAf5IYBdhLWcg3w_W5j#47962963 所以我不能使用带有异步操作的RenderAction... 那么我将只调用同步方法。看来无法通过异步代码解决我的问题。 - Dima Glushko

-1

我按照Erik Philips的建议,在我的视图中更改了调用Html.RenderAction的功能:

@{
    Html.RenderAction("Index", "Pages");
}

和控制器:

public class PagesController : AsyncController, IPagesController
{
    private readonly IPagesService _pagesService;

    public PagesController(IPagesService pagesService)
    {
        _pagesService = pagesService;
    }

    [HttpGet]
    [Route("")]
    public async Task<ActionResult> IndexAsync()
    {
        var viewModel = await _pagesService.GetAllAsync();
        return PartialView("MenuPartial", viewModel);
    }
}

但是RenderAction与异步控制器操作不兼容:

异步PartialView导致“HttpServerUtility.Execute被阻止…”异常

因此,似乎只有同步调用在这里是可行的。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接