在 ASP.NET Core RazorPages 中更改 cshtml 文件的名称

Change name of cshtml file in ASP.NET Core RazorPages

我的环境:ASP.NET Core 5 with RazorPages, Webpack 5.

在引用 svg 文件的剃刀页面 (.cshtml) 中,我想将它们内联。这是 Webpack 可以做的事情(通过插件),但我不确定如何整合这两个技术堆栈。

我可以编写模板化的 cshtml 文件,并通过 webpack 填充它们:

ContactUs.cshtml.cs
ContactUs.cshtml                     <------ read by webpack
ContactUs.generated.cshtml           <------ generated by webpack

但是我如何强制 msbuild / aspnet 在构建时使用生成的文件 (ContactUs.generated.cshtml) 而不是模板文件 (ContactUs.cshtml)?

我怀疑答案是使用 IPageRouteModelConvention 但我不确定如何使用。

(一个肮脏的解决方法是使用文件名 ContactUs.template.cshtmlContactUs.cshtml,但我更喜欢上面的名称,因为“生成”更清晰。)


更新

简化问题:

编译器查找 Foo.cshtml.csFoo.cshtml.

如何告诉它寻找 Foo.cshtml.csFoo.generated.cshtml

加载应用程序时,框架会为您加载一组 PageRouteModel,它们是从 razor 页面文件夹(按照惯例)自动生成的。每个这样的模型都包含一组 SelectorModel,每个模型都有一个 AttributeRouteModel。您需要做的只是通过从自动生成的值中删除后缀部分来修改 AttributeRouteModel.Template

您可以创建自定义 IPageRouteModelConvention 来定位每个 PageRouteModel。然而,这样你就不能确保路由被复制(因为在修改 AttributeRouteModel.Template 之后,它可能会与其他一些现有路由重复)。除非您必须管理一组共享的路线模板。相反,您可以创建自定义 IPageRouteModelProvider。它在一个地方提供了所有 PageRouteModel,以便您可以修改和添加或删除任何内容。这种方式非常方便,您可以支持 2 个剃须刀页面,其中一页的优先级高于另一页(例如:您有 Index.cshtmlIndex.generated.cshtml,并且您希望它选择 Index.generated.cshtml。如果生成的视图不存在,将使用默认 Index.cshtml

所以这里是详细的代码:

public class SuffixedNamePageRouteModelProvider : IPageRouteModelProvider
{
    public SuffixedNamePageRouteModelProvider(string pageNameSuffix, int order = 0)
    {
        _pageNameSuffixPattern = string.IsNullOrEmpty(pageNameSuffix) ? "" : $"\.{Regex.Escape(pageNameSuffix)}$";
        Order = order;
    }
    readonly string _pageNameSuffixPattern;
    public int Order { get; }

    public void OnProvidersExecuted(PageRouteModelProviderContext context)
    {

    }

    public void OnProvidersExecuting(PageRouteModelProviderContext context)
    {
        if(_pageNameSuffixPattern == "") return;
        var suffixedRoutes = context.RouteModels.Where(e => Regex.IsMatch(e.ViewEnginePath, _pageNameSuffixPattern)).ToList();
        var overriddenRoutes = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
        foreach (var route in suffixedRoutes)
        {
            //NOTE: this is not required to help it pick the right page we want.
            //But it's necessary for other related code to work properly (e.g: link generation, ...)
            //we need to update the "page" route data as well
            route.RouteValues["page"] = Regex.Replace(route.RouteValues["page"], _pageNameSuffixPattern, "");

            var overriddenRoute = Regex.Replace(route.ViewEnginePath, _pageNameSuffixPattern, "");
            var isIndexRoute = overriddenRoute.EndsWith("/index", StringComparison.OrdinalIgnoreCase);

            foreach (var selector in route.Selectors.Where(e => e.AttributeRouteModel?.Template != null))
            {
                var template = Regex.Replace(selector.AttributeRouteModel.Template, _pageNameSuffixPattern, "");
                if (template != selector.AttributeRouteModel.Template)
                {
                    selector.AttributeRouteModel.Template = template;
                    overriddenRoutes.Add($"/{template.TrimStart('/')}");
                    selector.AttributeRouteModel.SuppressLinkGeneration = isIndexRoute;
                }                                                                              
            }
            //Add another selector for routing to the same page from another path.
            //Here we add the root path to select the index page
            if (isIndexRoute)
            {
                var defaultTemplate = Regex.Replace(overriddenRoute, "/index$", "", RegexOptions.IgnoreCase);
                route.Selectors.Add(new SelectorModel()
                {
                    AttributeRouteModel = new AttributeRouteModel() { Template = defaultTemplate }
                });
            }
        }
        //remove the overridden routes to avoid exception of duplicate routes
        foreach (var route in context.RouteModels.Where(e => overriddenRoutes.Contains(e.ViewEnginePath)).ToList())
        {
            context.RouteModels.Remove(route);
        }
    }
}

Startup.ConfigureServices中注册IPageRouteModelProvider:

services.AddSingleton<IPageRouteModelProvider>(new SuffixedNamePageRouteModelProvider("generated"));