What is IViewLocationExpander.PopulateValues() for in Asp.Net Core MVC
Asked Answered
P

3

10

I'm using ASP.NET MVC CORE. I have implemented my own ViewLocationExpander so that I can structure my project the way I want and place my views where I like.

This is accomplished by implementing a class that inherits from IViewLocationExpander and most of the work occurs in the following method:

ExpandViewLocations(ViewLocationExpanderContext context, IEnumerable<string> viewLocations)

Everything is working pretty sweet but the interface defines a 2nd method that I don't know how to properly implement:

PopulateValues(ViewLocationExpanderContext context)

I've read articles all over the internet about this interface but no one has really provided much info on what exactly this method is for other than saying vague things about how it helps with caching.

I'd really appreciate it if someone could explain how this method is used by the framework and how I can use it appropriately to aid caching if that is indeed what it is for.

Placet answered 22/4, 2016 at 20:19 Comment(0)
O
8

Maybe the following additional info taken directly from a GitHub MVC issue can answer your question:

Caching includes the Values dictionary in its lookup. Unless the PopulateValues() method adds distinct information to ViewLocationExpanderContext.Values, the ExpandViewLocations() method will be called just once per original file name i.e. the initial information is cached from then on.

On top of that, the particular example posed by OP can help understand even better, at least that's what happened to me:

  • His project has views with the same name under two different directory trees (say Foo and Bar)
  • Depending on some data extracted by current action context, the view to locate should be under either one of those trees

Without any code in PopulateValues(), view engine will ask once to locate the view, then use view "standard" data (e.g. ControllerName, ActionName, Area, etc.) in order to cache the actual location where view is found.

So, in OP case, once a view location is cached (say e.g. from Foo directory tree) everytime a view with same name is needed it will always be from that tree, there'll be no way to detect if the one in the other Bar tree should have been actually picked up.

The only way for OP is to customize PopulateValues() by adding specific, distinctive view details to Values dictionary: in current scenario, those are the info extracted from current action context.

That additional info are used two-fold: ExpandViewLocations() might use them when invoked in order to determine proper location, while view engine will use them to cache view location once found.

Dec. 2021 update

Official doc page is more descriptive. From Remarks section:

Individual IViewLocationExpanders are invoked in two steps:
(1) PopulateValues(ViewLocationExpanderContext) is invoked and each expander adds values that it would later consume as part of ExpandViewLocations(ViewLocationExpanderContext, IEnumerable<String>). The populated values are used to determine a cache key - if all values are identical to the last time PopulateValues(ViewLocationExpanderContext) was invoked, the cached result is used as the view location. (2) If no result was found in the cache or if a view was not found at the cached location, ExpandViewLocations(ViewLocationExpanderContext, IEnumerable<String>) is invoked to determine all potential paths for a view.

Odom answered 3/1, 2017 at 1:9 Comment(5)
Thank you! That's exactly the information I have wanted to learn for 6+ months! This info needs to be better publicized on the web. Nice Job!Placet
Thanks to you, as I also got the opportunity to get this clear. BTW I agree on the need to improve that bit of docsOdom
Update: I actually noticed that official doc page is now more descriptive: The populated values are used to determine a cache key - if all values are identical to the last time PopulateValues(ViewLocationExpanderContext) was invoked, the cached result is used as the view location.Odom
Building a multi-tenant website, and this right here saved my bacon.Sterrett
Just taking the chance of this comment to update link to doc page (the one above is broken as of today)Odom
H
3

Haven't messed around with it enough to be able to give you a concrete answer, but have a look at IViewLocationExpander.PopulateValues(ViewLocationExpanderContext context) on the ASP.NET MVC GitHub repo:

public interface IViewLocationExpander
{
    /// <summary>
    /// Invoked by a <see cref="RazorViewEngine"/> to determine the values that would be consumed by this instance
    /// of <see cref="IViewLocationExpander"/>. The calculated values are used to determine if the view location
    /// has changed since the last time it was located.
    /// </summary>
    /// <param name="context">The <see cref="ViewLocationExpanderContext"/> for the current view location
    /// expansion operation.</param>
    void PopulateValues(ViewLocationExpanderContext context);

    // ...other method declarations omitted for brevity
}

Readability format:

"Invoked by a RazorViewEngine to determine the values that would be consumed by this instance of IViewLocationExpander. The calculated values are used to determine if the view location has changed since the last time it was located.

Parameters:

context: The ViewLocationExpanderContext for the current view location expansion operation."

I've had a look at some classes which implement this interface - some declare the method but leave it empty, others implement it.

NonMainPageViewLocationExpander.cs:

public void PopulateValues(ViewLocationExpanderContext context)
{
}

LanguageViewLocationExpander.cs:

private const string ValueKey = "language";

public void PopulateValues(ViewLocationExpanderContext context)
{
    if (context == null)
    {
        throw new ArgumentNullException(nameof(context));
    }

    // Using CurrentUICulture so it loads the locale specific resources for the views.
#if NET451
    context.Values[ValueKey] = Thread.CurrentThread.CurrentUICulture.Name;
#else
    context.Values[ValueKey] = CultureInfo.CurrentUICulture.Name;
#endif
}

The article "View Location Expander in ASP.NET Core and MVC 6" provides an example. Here's an excerpt of the explanation:

You can add as many view location expanders as you want. IViewLocationExpander interface has 2 methods, PopulateValues and ExpandViewLocations. PopulateValues method allows you to add values that can be later consumed by ExpandViewLocations method. The values you put in PopulateValues method will be used to find cache key. ExpandViewLocations method will be only invoked if there is no cache result for the cache key or when framework is unable to find the view at the cached result. In the ExpandViewLocations method, you can return your dynamic view locations. Now you can register this view location expander in Startup.cs file,

services.Configure<RazorViewEngineOptions>(options =>
{
    options.ViewLocationExpanders.Add(new MyViewLocationExpander());
});
Hepatic answered 16/12, 2016 at 16:1 Comment(5)
That's helpful but I still don't understand what I should implement in PopulateValues if I have implemented ExpandViewLocations to provide custom locations to search for views. Thoughts?Placet
I haven't messed around with it enough to be able to give you a definite answer. But, I have provided you with examples of classes which both implement and don't implement the method and attached an excerpt from a weblogs.asp.net article. I think putting all of the information together gives you a (rough) overview of how things work.Hepatic
I appreciate the info you provided and hence the upvote but ultimately I need someone to answer this part: I'd really appreciate it if someone could explain how this method is used by the framework and how I can use it appropriately to aid caching if that is indeed what it is for.Placet
I think this is somewhat answered in "The values you put in PopulateValues method will be used to find cache key. ExpandViewLocations method will be only invoked if there is no cache result for the cache key or when framework is unable to find the view at the cached result" from the attached excerpt—thorougher examples would be useful.Hepatic
It does elude to an answer but after studying extensively the article you cited and the code samples it's still not clear how PopulateValues can be leveraged to aid the framework to use a cached value rather than needing to call ExpandViewLocations again.Placet
C
2

Basically the method can populate values into context.Values that will later be used to determine if a cached list should be used or if the ExpandViewLocations will be called....

Calia answered 13/6, 2016 at 19:6 Comment(1)
can you explain this a bit more? When you say "that will later be used to determine if a cached list should be used" how does that work? What key would I need to use in context.Values[key] to get it to used a cached value? And what format would the value need to be in?Placet

© 2022 - 2024 — McMap. All rights reserved.