Does Razor ViewEngine cache the rendered HTMLs?
Asked Answered
Y

3

7

I have a 2-level menu item: I have a list of department and each department has a list of stores.

I have a Menu, PartialView which iterates through the Model (departments) and builds the menu:

@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>

And this is how I call the Menu PartialView in my _layout.cshtml:

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

As you can see I am passing the same model (from the cache) to the PartialView on all the requests.

Does Razor ViewEngine have an internal caching system to recognize that this view has already been built (complied to HTML string) for this model? Or does it re-render (recompile) the PartialView on every single request?

Yardage answered 27/11, 2018 at 7:3 Comment(4)
Seems possible techfunda.com/howto/275/cache-partial-view-outputApropos
@DaleBurrell: thanks a lot. So reading your link, I can see that RazorView does not cache anything...unless using [OutputCache].Yardage
Re-Rendering and Re-Compiling are very very different in ASP.Net MVC. While most of the answers here are correct, the View is only compiled once. It is compiled into a runtime class. The class is instantiated for each view needed, the model is populated, and the execute() method is called to create/stream the HTML to the client. The view could never be cached per model as it is to complicated to do that and instead the MVC team chose to allow configuring caching per controller method.Botchy
@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?Yardage
G
5

The PartialView gets re-rendered at every single request, assuming you don't have any OutputCacheAttribute applied on the Controller or its action method involved.

If you need output caching, you need to set this up explicitly via OutputCacheAttribute, see the documentation.

You can easily check this by outputting a DateTime, eg. via a menu-item as shown here below.
At every request, it will show a new value, proving it got re-rendered.

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

Full menu:

@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>
Googly answered 29/11, 2018 at 20:9 Comment(0)
B
3

Re-Rendering and Re-Compiling are very very different in ASP.Net MVC. While most of the answers here are correct, the View is only compiled once (except in debug mode, where it's compiled each time so you can change the view, hit refresh and see the change, or if the timestamp changes on the file in production). It is compiled into a runtime class that derives from WebViewpage(no ViewModel) or WebViewPage<T>(has ViewModel of type T).

The class is instantiated for each view needed (so if you used the same partial multiple times, you have to instantiate the each time), the model is populated, and the execute() method is called to create/stream the HTML to the client. The view could never be cached per model as it is to complicated to do that and instead the MVC team chose to allow configuring caching per controller method, not per 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?

Sorta of, but it's much more advanced, easier and complicated then that.

Advanced - The output cache is based on the controller method. So if the output cache configuration determines that the call can use a cached version, the controller method is never called. That's where the huge performance gain is. Imagine no call to the DB / external API call, there isn't a need. You could configure the cache so if it sees id=1 cache it for 30 minutes. Now anyone who calls that method with authorization and id=1 gets the cached string/html.

Easier - You put your OuputCacheAttribute on a method, configure it and you're done. Pretty darn easy to configure.

Complicated - Caching can be 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 (recommended reading) but that's been fixed for a long time.

Botchy answered 6/12, 2018 at 3:32 Comment(0)
Y
1

This question has a great answer, which proves PartialViews are not cached, and this link which is suggested in the comments explains how to use [OutputCache] for a partialView - this could be used with Html.Action() / Html.RenderAction() and renders PartialViews as a [ChildAction].

Caching PartialView as a child action makes sense, but I did not want to render my menu as a [ChildAction], because I did not want to make a separate call for displaying the menu, so this is what I ended up doing:

I used RazorEngine to render my PartialView into HtmlString on application Startup, and will keep the HtmlString in a static variable (cache).

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;
    }
}

I also created a Customized HtmlHelper method which would return the HtmlString for the menu:

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

Now in my _Layout page, I can use the customized HtmlHelper to display the menu:

@Html.MyMenu()
Yardage answered 5/12, 2018 at 20:14 Comment(3)
This seems really complicated compared to creating a controller with a method that has the ChildOnlyActionAttribute and OutputCacheAttribute.Botchy
@ErikPhilips - thanks again. But if I create an OutputCache on Partial view, then every time the View is rendered, I need to make a second call to get the menu, right? Considering menu appears on top of all of my views, I thought it might be a good idea to cache it individually, to avoid the second call: Html.RenderAction()... am I missing something here?Yardage
Who cares about a second call when it's cached? All that's being returned is the HTML if it's cached.Botchy

© 2022 - 2024 — McMap. All rights reserved.