在 MVC 中从派生页面传达状态的惯用方式?

Idiomatic way to communicate state from derived page in MVC?

我的 _Layout.cshtml 中有导航栏元素,这取决于调用的控制器。在搜索页面上将没有导航,但为了保持站点的风格一致,导航栏本身将保留。我不确定执行这项工作最被接受和最惯用的方式是什么。

_Layout.cshtml

 (etc)
    <nav>
        <div class="container">
            <ul>
                @if (TempData.ContainsKey(KeyChain.ItemKeyTempDataKey))**
                {
                    var itemKey = TempData[KeyChain.ItemKeyTempDataKey] as ItemKey;
                    <li>@Html.ActionLink("Overview", "Index", "Overview", itemKey, new { })</li>
                    <li>@Html.ActionLink("Purchasing", "Index", "InvoiceMovementHistory", itemKey, new { })</li>
                    <li>@Html.ActionLink("Profit Trends", "Index", "SalesMovement", itemKey, new { })</li>
                    <li>@Html.ActionLink("Coupons", "Index", "Coupon", itemKey, new { })</li>
                    <li>@Html.ActionLink("Deals", "Index", "WebDeal", itemKey, new { })</li>
                    <li>@Html.ActionLink("Update Log", "Index", "UpdateLog", itemKey, new { })</li>

                }
            </ul>
        </div>
    </nav>
 (etc)

ItemKey.cs

public class ItemKey
{
    public string Upc { get; set; }
    public string VendorItemCode { get; set; }
    public int Vendor { get; set; }
}

UpdateLogViewModel.cs

public class UpdateLogViewModel
{
    public IEnumerable<UpdateLogEntryViewModel> UpdateLogEntries { get; set; }
}

UpdateLogController.cs

    public ActionResult Index(ItemKey itemKey)
    {
        TempData[KeyChain.ItemKeyTempDataKey] = itemKey;
        //etc uses itemkey to get data in order to generate updateLogViewModel
        return updateLogViewModel();
    }

我想到的事情

  1. 如果填充了 itemkey,则使用 TempData(如上所述)显示导航栏元素。然而,TempData 有点过时了,感觉有点老套。

  2. 向导航栏添加渲染部分,将导航栏元素放入渲染动作中,并将它们填充到使用它的每个视图(基本上是除搜索视图之外的每个视图)的部分中。这只是在超速时违反了 DRY,但在我看来是惯用的东西。

  3. 派生一个辅助子布局,它是一个 "itemlayout",它将被键入到 itemkey 并删除临时数据检查。只要开发人员将项目布局用于项目子屏幕,至少可以提供编译时检查。但是,说我疯了,这更糟,因为现在我所有派生视图的视图模型都必须依赖于 itemlayout 视图模型的类型。然而,这样做的好处是明确了依赖关系:如果你要使用这个布局,你必须从这个包含 itemkey 属性 的视图模型派生。这似乎是最惯用的方式,但我讨厌键入布局的想法。

  4. 将导航栏移至每个视图页面。我几乎肯定不会这样做,但应该指出存在这种可能性。

那么,有没有另一种方法可以让我在 MVC 中惯用地执行此操作,或者我在上面列出的选项之一是首选方法?

TempData 是一种在 ASP.NET MVC 应用程序中发送数据的糟糕方法。这是 Viewstate 时代的遗留问题。这是个坏主意。

您可以将 Navbar 设为 RenderAction 而不是 TempData,并从它出现的每个页面(从视图)向它传递信息。您还可以使用 HtmlHelper(如下所述)来呈现链接。将所有这些都放在 Layout.cshtml 中是没有意义的,因为它将包含不适用于它的代码。

实际上,您尝试做的是以不同的样式显示活动页面。

有很多方法可以做到这一点。

  • Highlighting current page ASP.NET MVC
  • Highlighting current page in navigation ASP.NET MVC

K. Scott Allen has a blog post about the various methods he uses.

所有这些技巧都有一个共同点:它们都建议使用仅查看当前页面的 HTMLHelper。

在 MVC 中执行此操作的最自然和规范的方法是覆盖部分。创建一个名为 SearchController 的特定控制器。

然后,在您的 "Views\Shared" 文件夹中创建一个名为 _Navigation.cshtml 的部分,如下所示:

<nav>
    <div class="container">
        <ul>
            <li>...</li>
            <li>...</li>
            <li>...</li>
        </ul>
    </div>
</nav>

然后,在 "Views\Search" 中创建 另一个 局部调用 _Navigation.cshtml,如下所示:

<nav>
    <div class="container">
        <p>Nothing to see here.</p>
    </div>
</nav>

然后,在您的布局中,当您执行此操作时:

@Html.Partial("_Navigation")

视图解析器的优先级将在搜索页面上选择后者,在其他任何地方选择前者。

编辑:根据我从您的评论和更新中收集到的信息,您有一个控制器操作在查询字符串中接收一些值,并且您希望将这些值保留在您的操作 link 中。答案很简单。

假设 URL /UpdateLog?Upc=xyz&VendorItemCode=abc&Vendor=1 触发了您的 UpdateLog.Index 操作,那么在您看来,您的 link 只需要是,例如:

@Html.ActionLink("Purchasing", "Index", "InvoiceMovementHistory")

如果InvoiceMovementHistory.Index也接受这些参数,MVC框架在生成link时会自动将current路由参数映射到目标路由.根本不需要您管理这些值。

如果它们不是查询字符串参数而是 URL 段,同样适用。

这种通过 GET 参数从一个请求到另一个请求的上下文无状态传递是最终的 "idiomatic" 在 MVC 和一般网络上做这类事情的方式。

将此与我上面描述的视图覆盖结合使用,您可以非常简单地执行此操作并关闭特定页面的导航。也就是说,鉴于出现的清晰度,我会放弃部分,只需检查布局中的 ViewBag.DisableNav 属性:

@if (!ViewBag.DisableNav)
{
    <nav>
        <div class="container">
            <ul>
                <li>@Html.ActionLink("Purchasing", "Index",
                                     "InvoiceMovementHistory")</li>
                <li>...</li>
                <li>...</li>
            </ul>
        </div>
    </nav>
}