I have a Razor Pages project (no Controller) with structure like this:
From the main Index.cshtml
, I would render a partial view for its content depend on the Theme name, for example:
@* Default will be replaced with theme name *@
<partial name="Themes\Default\HomeContent" />
In HomeContent.cshtml
, I would like to render many other partial views within its folder. However this wouldn't work:
<p>Content</p>
<partial name="_DefaultThemePartial" />
The engine only searches these locations (correct according to the documentation):
InvalidOperationException: The partial view '_DefaultThemePartial' was not found. The following locations were searched:
/Pages/_DefaultThemePartial.cshtml
/Pages/Shared/_DefaultThemePartial.cshtml
/Views/Shared/_DefaultThemePartial.cshtml
I have also tried <partial name="./_DefaultThemePartial" />
or <partial name=".\_DefaultThemePartial" />
or try putting them in a subfolder named Shared
(within the Default folder). None of them works, only the above 3 locations are searched.
Is there anyway to render those partials without specifying the full path?
CodePudding user response:
I posted a proposal here. In the meantime, I found out you can expand the discovery mechanism by using IViewLocationExpander
but I barely found any useful documentation on it.
public class PartialViewLocationExpander : IViewLocationExpander
{
public IEnumerable<string> ExpandViewLocations(ViewLocationExpanderContext context, IEnumerable<string> viewLocations)
{
if (!context.Values.TryGetValue("FromView", out var fromView))
{
return viewLocations;
}
var folder = Path.GetDirectoryName(fromView) ?? "/";
var name = context.ViewName;
if (!name.EndsWith(".cshtml", StringComparison.OrdinalIgnoreCase))
{
name = ".cshtml";
}
var path = Path.Combine(folder, name)
.Replace('\\', '/');
return viewLocations.Concat(new[] { path });
}
public void PopulateValues(ViewLocationExpanderContext context)
{
var ctx = context.ActionContext as ViewContext;
if (ctx == null) { return; }
var path = ctx.ExecutingFilePath;
if (!string.IsNullOrEmpty(path))
{
context.Values["FromView"] = path;
context.Values["ViewName"] = context.ViewName;
}
}
}
// Register
services.Configure<RazorViewEngineOptions>(options =>
{
options.ViewLocationExpanders.Add(new PartialViewLocationExpander());
});
CodePudding user response:
From a response from my GitHub issue, there's a simpler solution without any extra code by adding the .cshtml
at the end:
[...] This is by design. Partial view lookup is done in two ways:
By name
By file path
You've done the lookup by name (without file extension), if you want to use a relative path, make sure you specify the file extension.
<partial name="_DefaultThemePartial.cshtml" />