在 ASP.NET Core Razor 页面中更改 cshtml 文件的名称

Posted

技术标签:

【中文标题】在 ASP.NET Core Razor 页面中更改 cshtml 文件的名称【英文标题】:Change name of cshtml file in ASP.NET Core RazorPages 【发布时间】:2021-05-19 12:58:10 【问题描述】:

我的环境:带有 RazorPages 的 ASP.NET Core 5、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

【问题讨论】:

问题不在于编译器或 Razor Pages 所期望的名称。它是由您用来生成额外文件的任何内容生成的名称。 那是需要改变的。你是如何生成这些文件的?带有cshtml 扩展名的文件是页面本身,而不仅仅是页面的一部分。生成的文件包含什么?您能否将它们转换为部分页面并将它们包含在实际页面中? @PanagiotisKanavos 同意 - 你指的是我上面提到的解决方法,它有效。但我更喜欢使用IPageRouteModelConvention 以某种方式更改约定,我只是不知道如何。从旧的 T4 时代开始,一个名为“foo.generated.bar”的文件总是被认为是在源代码控制之外生成的——所以我想在这里复制这个范例。我敢肯定这是有可能的。 【参考方案1】:

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

您可以创建一个自定义IPageRouteModelConvention 来定位每个PageRouteModel。但是,这样您无法确保路由不被复制(因为修改AttributeRouteModel.Template 后,它可能会与其他一些现有路由重复)。除非您必须管理一组共享的路线模板。相反,您可以创建自定义 IPageRouteModelProvider。它在一个地方提供所有PageRouteModels,以便您可以修改和添加或删除任何。这种方式非常方便,您可以支持 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"));

【讨论】:

谢谢!我得到一个错误:AmbiguousMatchException: The request matched multiple endpoints. Matches: /Foo/Index.generated /Index。它对你有用吗? 另外,也许做类似to this的事情更容易? 是的——你确实是国王^2! @Ionix 实际上我应该感谢您,您的回复帮助我更好地改进了代码,这实际上可能对其他人和我自己也有帮助。像这样高级的东西需要一个问题来解决,我一个人无法想到你遇到的问题,所以我需要解决问题,这个过程将帮助我更深入地挖掘知识并更多地了解框架。如果您对代码有任何可能的问题,请随时在此处告诉我,以便我提供帮助并使其变得更好。关于我的名字,King 是我的英文名(翻译自越南语):) 你太谦虚了——这需要对netcore平台有深入的了解,这是大多数人所没有的。我希望有一天我能拥有这样的技能。是的,你是所有语言的编码之王。太感谢了! :)

以上是关于在 ASP.NET Core Razor 页面中更改 cshtml 文件的名称的主要内容,如果未能解决你的问题,请参考以下文章

如何在 ASP.Net Core Razor 页面上重定向

ASP .NET Core Razor 页面中的授权

Asp.net core razor pages 加载部分页面

Asp.Net Core Razor 页面中的远程验证

ASP.NET Core Razor 页面与完整 MVC Core [关闭]

Asp .NET Core 2.2 Razor 页面布局和局部