Razor View Engine 是否缓存呈现的 HTML?

Does Razor ViewEngine cache the rendered HTMLs?

我有一个 2 级菜单项:我有一个部门列表,每个部门都有一个商店列表。

我有一个菜单 PartialView,它遍历 Model(部门)并构建菜单:

@model IEnumerable<Department>

<ul>
    @foreach (var department in Model)
    {
        <li>
            <a href="#">@Model.DepartmentName</a>
            <ul>
                @foreach (var store in department.Stores)
                {
                    <li><a href="some-url">@store.StoreName</a></li>
                }
            </ul>
        </li>
    }
</ul>

这就是我在 _layout.cshtml 中调用 Menu PartialView 的方式:

@Html.Partial("Shared/_Menu", MyApplicationCache.departments) 

如您所见,我将相同的模型(从缓存中)传递给所有请求的 PartialView。

Razor ViewEngine 是否有内部缓存系统来识别已经为该模型构建了该视图(符合 HTML 字符串)?还是它会根据每个请求重新呈现(重新编译)PartialView?

PartialView 会在每次请求时重新呈现,假设您没有在 Controller 或其涉及的操作方法上应用任何 OutputCacheAttribute

如果您需要输出缓存,您需要通过 OutputCacheAttribute 明确设置,请参阅 documentation

您可以通过输出 DateTime 轻松检查这一点,例如。通过如下所示的菜单项。
在每次请求时,它都会显示一个新值,证明它已重新呈现。

<li><a href="#">@DateTime.Now</a></li>

完整菜单:

@model IEnumerable<Department>

<ul>
    @foreach (var department in Model)
    {
        <li>
            <a href="#">@Model.DepartmentName</a>
            <ul>
                <li><a href="#">@DateTime.Now</a></li>
                @foreach (var store in department.Stores)
                {
                    <li><a href="some-url">@store.StoreName</a></li>
                }                
            </ul>
        </li>
    }
</ul>

这个问题有一个很好的答案,证明 PartialViews 没有被缓存,评论中建议的 this link 解释了如何将 [OutputCache] 用于 partialView - 这可以与 Html.Action() / Html.RenderAction() 并将 PartialViews 呈现为 [ChildAction].

将 PartialView 缓存为子操作很有意义,但我不想将我的菜单呈现为 [ChildAction],因为我不想单独调用来显示菜单,所以这就是我最终做了:

我使用 RazorEngine 在应用程序启动时将我的 PartialView 呈现为 HtmlString,并将 HtmlString 保存在静态变量(缓存)中。

public static class MenuCache
{
    private static readonly MvcHtmlString _menuMvcHtmlString;

    static MenuCache()
    {
        using (var context = ApplicationDbContext.Create())
        using (var razorEngine = RazorEngineService.Create(new TemplateServiceConfiguration()))
        {
            var repository = new MyRepository(context);
            var departments = repository.GetDepartments();

            // use razorEngine to render menu partial view into html string 
            // keep the htmlString in cache: _menuMvcHtmlString
            string menuPartialView = File.ReadAllText(HostingEnvironment.MapPath("~/Views/Shared/_Menu.cshtml"));
            string menuHtmlString = razorEngine.RunCompile(menuPartialView, "menuKey", null, departments);
            _menuMvcHtmlString = new MvcHtmlString(menuHtmlString);
        }
    }

    public static MvcHtmlString GetMenuHtmlString()
    {
        return _menuMvcHtmlString;
    }
}

我还创建了一个自定义的 HtmlHelper 方法,它将 return 菜单的 HtmlString:

public static class HtmlHelperExtensions
{
    public static MvcHtmlString MyMenu(this HtmlHelper html)
    {
        return MenuCache.GetMenuHtmlString();
    }
}

现在在我的 _Layout 页面中,我可以使用自定义 HtmlHelper 来显示菜单:

@Html.MyMenu()

重新渲染和重新编译在 ASP.Net MVC 中非常不同。虽然这里的大部分答案都是正确的,但 View 只编译了一次(调试模式除外,每次都会编译它,因此您可以更改视图,点击刷新并查看更改,或者如果生产中文件的时间戳发生变化)。它被编译成一个运行时 class,派生自 WebViewpage(no ViewModel) or WebViewPage<T>(具有 T 类型的 ViewModel)。

class 为每个需要的视图实例化(所以如果你多次使用相同的部分,你必须每次都实例化),填充模型,并调用 execute() 方法到 create/stream 到 HTML 到客户端。视图永远不能按模型缓存,因为这样做太复杂了,相反,MVC 团队选择允许按控制器方法配置缓存,而不是按 WebViewPage。

@ErikPhilips, thanks a lot for this - So the View is only compiled once (no matter if we use or not use OutputCache)? It's the execute method which renders the runtime class into HtmlString, and it is the rendering which would benefit from caching?

有点像,但它比那个更高级、更容易和更复杂。

高级 - 输出缓存基于控制器方法。因此,如果输出缓存配置确定调用可以使用缓存版本,则永远不会调用控制器方法。这就是巨大的性能提升所在。想象一下没有调用 DB / 外部 API 调用,没有必要。您可以配置缓存,以便在它看到 id=1 时将其缓存 30 分钟。现在,任何通过授权调用该方法并 id=1 的人都会获得缓存的 string/html.

更简单 - 您将 OuputCacheAttribute 放在一个方法上,对其进行配置,然后就完成了。配置非常简单。

复杂 - 缓存可以 more complicated because you can render other controller methods using Html.Action() (Html.Partial() if you don't need a layout for your partial) or the preferred Html.RenderAction(); (Html.RenderPartial() if you don't need a layout). There use to be a Donut Hole Caching Issue(推荐阅读),但已经修复了很长时间。