如何在每个控制器动作中使用异步 ViewModel?

How to use asynchronous ViewModel in every controller action?

我是 asp.net mvc (5) 的新手,我的网站遇到了问题。

基本上,所有 aspnet_users 都通过 guid.

链接到我的特定 user table

我有一个 BaseController class 和一个 UnitOfWork 和一个 ViewModelBase :

public class BaseController : Controller
{
    protected UnitOfWork UnitOfWork { get; private set; }
    public ViewModelBase ViewModel { get; set; }
}

(Extract of) ViewModelBase class,包含布局页面中需要的信息:

public class ViewModelBase
{
    public User User { get; set; }
    public bool IsAuthentified { get; set; }
    public bool Loaded { get; set; }
}

以及使用它的布局:

@model Project.ViewModels.ViewModelBase
<html>
    <body>
        Some very cool layout stuff related to the User in the ViewModelBase
    </body>
</html>

这里有一个使用 HomeController 的例子:

public class HomeController : BaseController
{
    private new HomeViewModel ViewModel
    {
        get { return (HomeViewModel)base.ViewModel; }
    }

    public HomeController()
    {
        ViewModel = new HomeViewModel();
    }

    public ActionResult Index()
    {
        // I need to get the MembershipUser here to retrieve the related User and set it in the ViewModel
        return View(ViewModel);
    }
}

HomeViewModel :

public class HomeViewModel : ViewModelBase
{
    public string HomeSpecificProperty { get; set; }
}

和视图:

@model Project.ViewModels.HomeViewModel

@{
    ViewBag.Title = "Home";
    Layout = "~/Views/Shared/Layout.cshtml";
}

Welcome @Model.User.UserName !
<br />
@Model.HomeSpecificProperty

这是我的问题。问题是所有页面都有一个继承自 ViewModelBase 的视图模型,因为它用于布局,但我不知道在哪里以及如何在其中设置 User 属性。 由于 Membership 用于检索 User 它必须在 Action 中,我不能在 ViewModelBase 构造函数中执行此操作。

因此我在 BaseController 中添加了这段代码,它在第一个 get 上设置了 ViewModelBase 属性:

private ViewModelBase viewModel;

protected ViewModelBase ViewModel
{
    get
    {
        if (!viewModel.Loaded)
            LoadBaseContext();

        return viewModel;
    }
    set { viewModel = value; }
}

private void LoadBaseContext()
{
    viewModel.IsAuthentified = HttpContext.User.Identity.IsAuthenticated;

    if (viewModel.IsAuthentified)
    {
        MembershipUser user = Membership.GetUser();
        viewModel.Player = UnitOfWork.UserRepo.Get((Guid)user.ProviderUserKey);
    }

    viewModel.Loaded = true;
}

可能不是很漂亮,但是很管用。然而,由于其中有一些数据库访问(特别是获取 User),我想我应该把 LoadBaseContext 函数放在 async 中。 我试过了,因为所有的动作都使用 ViewModelBase 我也把每个动作都异步了。但是 @Html.ActionLink 不再起作用,因为调用的操作是 async.

最后,我的问题是:ViewModelBaseUser 属性 是正确的做法吗?如果是, LoadBaseContext 应该是 async 吗?那么,如何让它配合动作呢?

非常感谢。

更新

布局摘录:

@if (!Model.IsAuthentified)
{
    @Html.Action("Index", "Authentification", null) // display login partial view
}
else
{
    @Html.Action("Index", "UserInfos", null) // display userinfos partial view
}

认证控制器索引的动作:

public ActionResult Index()
{
    ViewModel = new AuthentificationViewModel();
    // loads the ViewModelBase properties here
    return PartialView("~/Views/Shared/Partials/Login.cshtml", ViewModel);
}

如果您在所有视图中都有您一直想要的东西,您可以确保每个视图都采用包含或扩展该核心数据集的视图模型, 你可以简单地将数据放在 ViewBag.

要执行后者,您可以覆盖基本控制器中的 OnActionExecuting,并在那里设置 ViewBag 内容。

protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
    base.OnActionExecuting(filterContext);
    this.ViewBag["IsAuthenticated"] = this.Request.IsAuthenticated;
    // ...
}

如果您想要 async 行为,那么您可以 this post 适应您的需要。

要在不覆盖 OnActionExecuting 的情况下执行此操作,您可以在控制器基础 class 中放置一个 async 方法来设置 ViewBag 中的数据:

protected async virtual Task PopulateViewBag()
{
    this.ViewBag["foo"] = await MyAsyncMethod();

    // ...
}

... 然后只需从您的每个控制器操作中调用它:

public async Task<ActionResult> MyAction()
{
    await this.PopulateViewBag();

    // ... more code
}

不过会有点乏味。

最后一句话: 取决于你希望有多少用户等等。获取用户信息可能更容易一次 ,然后缓存它(例如在 Session 中),而不是在每次请求时重复获取它。大概大多数用户信息不会在请求之间发生变化...