grimdog_john
grimdog_john

Reputation: 735

IViewLocationExpander not working for Partial Views (MVC Core)

I have defined a custom IViewLocationExpander to check for site-specific Views in a multi-tenant web application.

Imagine there are two tenants WebsiteA and WebsiteB and the following file structure for a HomeController and Index View

My IViewLocationExpander will render Views/Home/WebSiteA/Index.cshtml for WebsiteA and Views/Home/Index.cshtml for WebsiteB - as there is no Index view specific to WebsiteB so it uses the default one.

I also have setup a folder in Views named "Common" to hold any partial views - the idea being that I can render a custom Partial View in the same manner (Say for instance a Header).

Here is the code for my IViewLocationExpander

public sealed class TenantViewLocationExpander : IViewLocationExpander
{
    private ITenantService _tenantService;
    private string _tenant;

    public IEnumerable<string> ExpandViewLocations(ViewLocationExpanderContext context, IEnumerable<string> viewLocations)
    {
        string[] locations =
        {
            "/Views/{1}/" + _tenant + "/{0}.cshtml",
            "/Views/Common/" + _tenant + "/{0}.cshtml",
            "/Views/Shared/" + _tenant + "/{0}.cshtml",
            "/Pages/Shared/" + _tenant + "/{0}.cshtml",
            "/Views/{1}/{0}.cshtml",
            "/Views/Common/{0}.cshtml",
            "/Views/Shared/{0}.cshtml",
            "/Pages/Shared/{0}.cshtml"
        };
        return locations;
    }

    public void PopulateValues(ViewLocationExpanderContext context)
    {
        _tenantService = context.ActionContext.HttpContext.RequestServices.GetRequiredService<ITenantService>();
        _tenant = _tenantService.GetCurrentTenant();
    }
}

Everything works perfectly for standard views, however when I attempt to render a partial view, I always get the default returned (e.g. Views/Common/_Header.cshtml from the example above)

I'm rendering the partial in my Layout like so ...

<partial name="_Header.cshtml" />

If I remove the Views/Common/_Header.cshtml file -leaving only the Site specific one- I get an exception stating that the view could not be found

InvalidOperationException: The partial view '_Header.cshtml' was not found. The following locations were searched:
/Views/Shared/_Header.cshtml

It seems as if the expander hasn't added in the extra locations for partial views. So my question is, how can I configure IViewLocationExpander to work with Partials?

In older versions of MVC I see you could define them specifically by setting ViewLocationFormats and PartialViewLocationFormats, but in MVC Core I can't see that option anywhere?

Apologies if this has been covered off elsewhere - I couldn't find an answer anywhere.

Thanks in advance!

Upvotes: 2

Views: 979

Answers (1)

grimdog_john
grimdog_john

Reputation: 735

To anyone reading this who might be having the same problem - It seems you need to omit the '.cshtml' from the partial tag.

<!--- Works with all ViewLocations defined in IViewLocationExpander --->
<partial name="_Header" />

Whereas if you contain '.cshtml' like in the following, it seems to only search the default Partial View Location - 'Views/Shared'

<!--- Only looks in 'Views/Shared' --->
<partial name="_Header.cshtml" />

Not sure if this is intentional, but it definitely seems like a strange bug in MVC to me.

Upvotes: 6

Related Questions