我目前正在尝试使用dotnetcore 2.1中的Razor Pages实现一个可主题化的网站,但是为什么网页无法加载会遇到一些麻烦/混淆。
对网站的每个请求都会导致根据访问的域设置主题值,默认情况下主题为“默认”,存储在每个请求的RouteData中。
我已实施以下ThemeViewLocationExpander
public class ThemeViewLocationExpander : IViewLocationExpander
{
private const string ValueKey = "Theme";
public void PopulateValues(ViewLocationExpanderContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
context.Values[ValueKey] = (context.ActionContext.RouteData.Values["tenant"] as Tenant)?.Theme;
}
public IEnumerable<string> ExpandViewLocations(ViewLocationExpanderContext context, IEnumerable<string> viewLocations)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
if (viewLocations is null)
{
throw new ArgumentNullException(nameof(viewLocations));
}
context.Values.TryGetValue(ValueKey, out string theme);
if (!string.IsNullOrEmpty(theme))
{
viewLocations = new[] {
$"/Pages/Themes/{theme}//Pages/Themes/{THEME_NAME}
/{{0}}.cshtml",
$"/Pages/Themes/{theme}/Shared/{{0}}.cshtml",
$"/Pages/Shared/{{0}}.cshtml",
};
}
return viewLocations;
}
正如您所看到的,我正试图从/Pages/
...而不是/Pages/Shared
加载页面(因为每个主题可能有完全不同的布局而不仅仅是不同的CSS),但例外情况顶级共享文件夹/Pages
的内容,例如可能存在于所有主题中的验证脚本的部分内容。
我不希望直接在/Pages/Index.cshtml
下面有任何其他页面,例如Startup.cs
,因为每个请求都应该在主题文件夹中结束,因为主题是强制性的。
我已将以下内容添加到 services.Configure<RazorViewEngineOptions>(options =>
{
options.ViewLocationExpanders.Add(new ThemeViewLocationExpander());
});
https://localhost:44352/
我可以在第一次加载时断开此代码,因此我知道它已注册,但是如果我运行该网站并且/Pages/Index.cshtml
它应该加载/Pages/Themes/Default/Index.cshtml
时加载/Pages/Index.cshtml
如果我删除This localhost page can’t be found
No webpage was found for the web address: https://localhost:44352/
HTTP ERROR 404
并运行我获得的网站
ThemeViewLocationExpander.ExpandViewLocations()
有人能为我提供任何见解或帮助吗?
也许我需要修改一些路由以合并主题,或者我错过了一些重要的东西,似乎我在/Pages/
中设置的视图位置从未被使用过。
我已经阅读了很多关于DNC 2.1的多租户内容,但它要么是MVC而不是RazorPages,要么只在主题文件夹中实现CSS / _Layout.cshtml页面,并使用{{1下的默认页面用于布局(我试图在每个主题文件夹中包含所有页面的单独实现)
修改
我已经决定将/Pages/Index.cshtml
和其他页面保留在顶级文件夹中更有意义,并将其用作默认的“主题”,然后任何新主题都将使用这些页面,除非在其内部特别重写/Pages/Themes/{THEME_NAME}
文件夹。
我已经实现了这一点,但即使设置了主题值并且/Pages/Index.cshtml
添加了主题文件夹,我仍然不知道为什么页面从viewLocations
加载。
编辑2:
测试过它并使用主题设置它肯定在主题文件夹中使用了正确的布局页面。例如,当我加载https://localhost:44352/
时,主题为“新鲜”,则会加载布局页面/Pages/Themes/Fresh/Shared/_Layout.cshtml
,但是Index.cshtml
是从/Pages/Index.cshtml
而不是/Pages/Themes/Fresh/Index.cshtml
加载的。
此时viewLocations
是:
/Pages/Themes/Fresh/{1}/{0}.cshtml
/Pages/Themes/Fresh/Shared/{0}.cshtml
/Pages/Shared/{0}.cshtml
/Pages/{1}/{0}.cshtml
/Pages/Shared/{0}.cshtml
/Views/Shared/{0}.cshtml
编辑3:
这是一个github链接,用于简化版本的项目,演示了我遇到的问题。 https://github.com/BenMaxfield/ThemeTest
为了测试它,只需运行应用程序,您应该看到它加载了DARK主题布局,但是加载了DEFAULT索引内容。
要切换到DEFAULT主题,请在PopulateValues
中查找ThemePageViewLocationExpander
方法并设置context.Values[ThemeKey] = string.Empty;
编辑4
我注意到ThemePageViewLocationExpander.ExpandViewLocations
只是每次调用部分文件,而且从不会出现代码背后的实际页面。
这解释了为什么为主题加载了正确的_Layout.cshtml
页面,但没有正确的Index.cshtml
文件(因为/Pages/Index.cshtml
是一个代码隐藏的页面,并且@page
声明 - 即它不是部分的)
为了解决这个问题,我已经创建了一个新的文件夹/Pages/Views/
,并将.cshtml
部分@page
与/Pages/
页面中的每个/Pages/Index.cshtml
页面对应1}},
例如我现在有/Pages/Views/Index.cshtml
(非部分)和/Pages/Index.cshtml
(部分)。
在<partial name="Index" model="@Model"/>
内部我已添加/Pages/Views/
以从新的ThemePageViewLocationExpander.ExpandViewLocations
文件夹加载相应的部分内容。
由于现在正在加载,因此调用了部分/Index
,然后我可以根据给定的主题键(如果存在)覆盖它的视图位置。
这意味着,OnGet()
的所有请求都会从/Pages/Index.cshtml
加载Index.cshtml
,后者会在其视图中加载部分内容,我可以通过在其中添加另一个/Themes/{THEME_NAME}/Views/
部分来覆盖ThemePageViewLocationExpander.ExpandViewLocations
。
以下是 viewLocations = new[] {
$"/Themes/{theme}/{{0}}.cshtml",
$"/Themes/{theme}/Views/{{0}}.cshtml",
$"/Pages/Views/{{0}}.cshtml",
}.Concat(viewLocations);
.
├── Pages
| ├── Index.cshtml (with code behind, called on localhost:45635/)
| └── Views
| └── Index.cshtml (partial)
└── Themes
| ├── Dark
| └── Views
| └── Index.cshtml (partial)
示例文件夹结构:
{{1}}