• ASP.NET Core – Razor Pages Routing


    前言

    之前有提过, MVC 和 Razor Pages 最大的区别就在 Routing 上.

    Razor Pages 的结构是 route, page, model

    route match to page, page declare model (route > page > model) 

    route 是怎样 match to page 的呢? ASP.NET Core 有一套机制. 这篇主要就是介绍这个和如果修改它.

    参考:

    Razor Pages Routing

    默认 route 匹配

    创建一个 Razor Pages 项目

    dotnet new webapp -o TestRazorPagesRouting
    dotnet watch run

    打开游览器访问

    https://localhost:7194 (首页)

    https://localhost:7194/Privacy (Privacy 页面)

    它们对应的 Page 是

    看的出来, 匹配方式是 file name, 然后 Index 可以忽略

    Privary.cshtml = /Privary

    Index.cshtml = /

    那么 https://localhost:7194/Index 也可以访问首页面? 

    答案是可以的.

    multiple layer

    按照它的思路, 应该就是把 path 加长. 没错!

    Mac/Compare.cshtml = /Mac/Compare

    结论

    Razor Page Routing 的机制是

    先去 root folder "Pages"

    找到所有带有 @page 的 .cshtml

    依据 file 的所在位置 (folder) 和它的 file name 作为 URL path.

    而 Index 可以被忽略 (要写也是可以)

    Index.cshtml = /

    Index.cshtml = /Index

    Privary.cshtml = /Privary

    Mac/Compare.cshtml = /Mac/Compare

    默认 route 匹配的问题

    这个默认匹配方式不是很直观, 它有一下问题:

    1. Case Style, URL 通常是 kebab-case. 但 folder 和 file name 却是 PascalCase. 而它竟然没有做任何转换.

    2. Index 不应该被访问. 有见过 /index.html 这样访问的, 但没有见过 /Index 这样访问的...

    3. 整体 Structure 没有顾虑到 CSS 和 JS

    下面这个才是正常网站的 folder structure

    html, js, css 一定是在同一个 folder 里的, 表示一个页面. 嵌套也是一样.

    虽然它默认的匹配不太理想, 但是一个好的框架不是评价它的默认, 而是它是否允许自定义.

    幸好, 在自定义方面, ASP.NET Core 还是提供了接口.

    Area

    在讲解修改默认匹配之前, 先看看 Area (因为我没有用, 所以大概记入一下就好)

    当有很多 pages 以后, 想给它们分类就可以用 Area

    结构长这样

    匹配的 URL 是

    /Adminstration/Reports

    /Production

    Extend Route Match

    默认匹配只能 cover 简单的场景, 真实项目中, 通常会需要 extend.

    Extend Path

    extend product name

    花括弧是 dynamic value, 问号是可有可无的意思.

    RouteData 是用来获取 dynamic value 的

    它匹配

    /ProductDetail

    /ProductDetail/whatever

    这样就可以做 dynamic page 了.

    Add extra route match

    builder.Services.AddRazorPages(options =>
    {
        options.Conventions.AddPageRoute("/ProductDetail", "/product-detail");
        options.Conventions.AddPageRoute("/ProductDetail", "/product-information");
    });

    注: 它是 extend 不是 override 哦

    它可以 match URL:

    /ProductDetail

    /product-detail

    /product-information

    有时候 URL 换了, 可以用这招来做 301 redirect.

    Override Route Match

    这个结构默认匹配是

    /Product/iPhone

    /Product/iPhone/Index

    通过 @page "/YourRootPath" 就可以完全覆盖默认匹配了

    修改后的匹配是 /iPhone

    注: 开头是 "/" 才表示 override 哦, 不然就是 extend 了

    Modify Default Route Match Pattern

    虽然可以 extend 和 override, 但是底层的问题还是得通过修改底层去解决.

    modify root folder

    默认是 /Pages

    builder.Services.AddRazorPages(options =>
    {
        options.RootDirectory = "/Web";
    });

    to kebab-case

    参考: stackoverflow – Automatically generate lowercase dashed routes in ASP.NET Core

    builder.Services.AddRazorPages(options =>
    {
        options.RootDirectory = "/Web";
        options.Conventions.Add(new PageRouteTransformerConvention(new KebabCaseTransformer()));
    });

    KebabCaseTrasformer

    public class KebabCaseTransformer : IOutboundParameterTransformer
    {
        public string? TransformOutbound(object? value)
        {
            if (value == null) return null;
            if (value.ToString()! == "") return "";
    
            return ToKebabCase(value.ToString()!);
    
            string ToKebabCase(string value)
            {
                return value == "AboutUs" ? "about-us" : value;
            }
        }
    }

    ASP.NET Core 在创建 route mapping 时会遍历所有的 @page .cshtml

    然后依据 folder file name 做 route, 这时拦截它就可以替换掉它的机制.

    Conventions 就是拦截点.

    Transformer 是一个比较具体的替换方式, 如果想换的更复杂一点就要直接使用 IPageRouteModelConvention

    PageRouteTransformerConvention 就是继承了 IPageRouteModelConvention.

    Fully Customize

    添加 Convention, Convention 是可以多个的, 类似 middleware 那样.

    builder.Services.AddRazorPages(options =>
    {
        options.RootDirectory = "/Web";
        options.Conventions.Add(new MyPageRouteModelConvention());
    });

    MyPageRouteModelConvention

    public class MyPageRouteModelConvention : IPageRouteModelConvention
    {
        public void Apply(PageRouteModel model)
        {
            var path = model.ViewEnginePath; // /FolderName/FileName e.g. /Mac/Compare/Index
            if (path.Contains("AboutUs"))
            {
                model.RouteParameterTransformer = new KebabCaseTransformer(); // 就是这里换 transform 的
                model.Selectors.Clear(); // selectors 就是关键. 把原本的 clear 到完
                model.Selectors.Add(new SelectorModel
                {
                    AttributeRouteModel = new AttributeRouteModel
                    {
                        Template = "/about-us"
                    }
                });
            }
        }
    }

    从 model 里面获取信息, 然后修改 Selectors (它就是最终的匹配)

    我们来看看原本的 Selectors, 这样就大概知道要怎么改了

     

    默认它生成了 2 个 Selectors

     

    第一个是 AboutUs/Index, 第 2 个是 AboutUs

    Index suppress link generation = true, 所有 link generation 不会 generate 出这样的 path.

    IsAbsoluteTemplate 指的是 template 是否 start with slash "/"

    我们加上 extend 和 override path 看看

    EndpointMetadata 可以获取到 RouteTemplate, 如果没有就是 null, 但 EndpointMetadata 是一定会有的

    总结

    要想完全控制最终的 route mapping 就需要添加 Convention

    通过 model.ViewEnginePath 获取到 folder 和 file name

    通过 model.Selectors 获取原本的 Selectors info, 最重要的是 EndpointMetadata 可以获取到 RouteTemplate

    最后通过 add/edit/delete Selectors 来达成最终的 mapping override.

    我自己的做法是:

    1. kebab-case

    2. remove 掉 Index 访问

    3. 支持多语言 /zh-hans/about-us, /jp/about-us

    关于 Link Generation

    tag helper 可以通过 PageName (folder file name) 自动生成 URL

    <a class="nav-link text-dark" asp-area="" asp-page="/Privacy">Privacy</a>

    也可以在代码中调用

    当 URL 换了以后, 不需要全场找 anchor 替换. 这个出发点是好的. 但是目前的 Link Generation 不是很给力.

    几年前我就有提过 issue, 但最后不了了之. 而现阶段也还有很多待加强的地方 Link generation improvements issue

    所以呢, 如果你有修改很多 default route 机制的话, 不建议使用 link generation 来管理. hardcode URL 就可以了. 毕竟 URL 也不是随随便便可以换的丫, SEO 不用管吗 ?不需要 301 redirect ?

  • 相关阅读:
    xapian的使用
    Andriod 环境配置以及第一个Android Application Project
    2013Esri全球用户大会之ArcGIS for Server&Portal for ArcGIS
    window server 2012 更改密钥 更改系统序列号
    持续集成之路——数据访问层的单元测试(续)
    多项式相乘与相加演示
    hdu 1847 博弈基础题 SG函数 或者规律2种方法
    solaris之cpu
    Android音效SoundPool问题:soundpool 1 not retry
    poj1845-Sumdiv
  • 原文地址:https://www.cnblogs.com/keatkeat/p/16037716.html
Copyright © 2020-2023  润新知