ASP.NET Core 中的SEO优化(3):自定义路由匹配和生成
前言
前两篇文章主要总结了CMS系统两个技术点在ASP.NET Core中的应用:
而本篇文章,继续介绍另一个技术点:自定义路由匹配和生成。
背景
在MVC5时代,默认的路由可能就是简单的约定/{controller}/{action}/{id}
,第一节对应控制器(Controller)名,第二节对应操作(Action)名,第三节是参数名。
在WebApi和ASP.NET Core时代,有了Route
特性来指定相应操作的路由指向,可以很灵活地配置RESTful Api。
但是,路由灵活性在注重SEO的CMS系统中有更苛刻的要求。例如:
- 栏目的列表 ->
/{父栏目名}/{子栏目名}-{页码}/
- 文章详情页 ->
/{栏目名}/{文章名}.html
- 标签页 ->
/{标签名}
这些友好的url链接使用默认的路由约定是很难实现的,当然是可以配置路由规则去传递参数:
app.UseMvc(routes =>
{
routes.MapRoute(
name: "article_list",
template: "{parentCategory}/{category}-{page}/",
defaults: new { controller = "Article", action = "Index" });
routes.MapRoute(
name: "article_detail",
template: "{category}/{article}.html",
defaults: new { controller = "Article", action = "Detail" });
routes.MapRoute(
name: "tags",
template: "{tag}/",
defaults: new { controller = "Article", action = "Tag" });
});
但是,这样配置很繁琐,也不灵活,如果还要传入更多规则,比如不向下匹配,就显得捉襟见肘了。那有没有更灵活的方案呢?当然有,就是本文接下来要介绍的IRouter接口。
原理
上一节最后介绍的一般路由配置方法,其实是MapRoute
方法创建一个个IRouter
的实例,添加到IRouteBuilder
实例中,具体方法可以看看源码。
所以我们可以实现一个自定义的IRouter
,就能不用默认的约定,去实现我们的苛刻需求。
首先要看看IRouter
这个接口的定义,源码在aspnet/Routing/src/Microsoft.AspNetCore.Routing.Abstractions/IRouter.cs
namespace Microsoft.AspNetCore.Routing
{
public interface IRouter
{
Task RouteAsync(RouteContext context);
VirtualPathData GetVirtualPath(VirtualPathContext context);
}
}
实现
IRouter
接口只有两个方法,本文分别给出代码示例来介绍。
RouteAsync
这个方法中实现路由匹配,从路由上下文中获取所需要的数据,存入RouteData字典中,再从控制器取出做相应的处理。
public async Task RouteAsync(RouteContext context)
{
var requestedUrl = context.HttpContext.Request.Path.Value.TrimStart('/').ToLower();
var split = requestedUrl.Split('/');
if (secoend != null && secoend.EndsWith(".html") && split.Length == 2)
{
var title = secoend.Replace(".html", "");
context.RouteData.Values["controller"] = "Article";
context.RouteData.Values["action"] = "Detail";
context.RouteData.Values["category"] = first;
context.RouteData.Values["title"] = title;
}
//...对请求路径进行一系列的判断
//最后注入`MvcRouteHandler`示例执行`RouteAsync`方法,表示匹配成功
await context.HttpContext.RequestServices.GetService<MvcRouteHandler>().RouteAsync(context);
}
这样,当请求路由为/news/asp.net.html
时,就会匹配到上面的规则,请求进入Article
控制器中的Detail
操作被处理,并且可以从控制器中的RouteData.Values["category"]?.ToString();
方法拿到所需的数据。
GetVirtualPath
这个方法实现路由生成,可以从路由上下文中获取RouteData字典中的数据,进行虚拟路径(区别与真实目录)的生成。
public VirtualPathData GetVirtualPath(VirtualPathContext context)
{
var path = string.Empty;
var hasController = context.Values.TryGetValue("controller", out var controller);
var hasAction = context.Values.TryGetValue("action", out var action);
var hasCategory = context.Values.TryGetValue("category", out var category);
var hasTitle = context.Values.TryGetValue("title", out var title);
if (hasController && hasAction && hasCategory && hasTitle)
{
path = $"/{category/{title}.html";
}
return path != string.Empty ? new VirtualPathData(this, path) : null;
}
这样,当调用路径生成方法@Url.Action("Detail","Article",new { title="asp.net", category="news" })
,就会生成"/news/asp.net.html"这样的路径来。
IRouter的设置生效
自定义实现的IRouter
如何设置到原有的MVC项目中呢?方法很简单,上面已经简单说道了,其实app.UseMvc
这个方法就有添加IRouter
的方法:
app.UseMvc(routes =>
{
//添加 自定义路由匹配与url生成组件
routes.Routes.Add(new RouteProvider());
});
这里RouteProvider
是IRouter
的实现,这样自定义的路由提供对象就生效啦!
相关小技巧
SEO中会有一条铁规则,就是不是有效链接的情况下只能返回404,比如,手动输入了一个路径,能匹配到文章详情的操作(action),如果数据库中查不出文章,本来应该不能匹配的,但是,如果在匹配的时候查询数据库确认是否存在的话,会加大系统的压力,所以,可以放在操作(action)中查询,查不到再返回
NotFound
,让上一篇文章《ASP.NET Core 中的SEO优化(2):中间件中渲染Razor视图》中介绍的中间件一并处理输出404页面。站内链接如果使用带域名的绝对路径,能够提高优化效果,我们可以自己写一个
UrlHelper
的扩展方法:
public static class UrlHelperExtensions
{
public static string AbsoluteAction(
this IUrlHelper helper,
string actionName,
string controllerName,
object routeValues = null)
{
string scheme = helper.ActionContext.HttpContext.Request.Scheme;
return helper.Action(actionName, controllerName, routeValues, scheme);
}
public static string AbsoluteContent(
this IUrlHelper helper,
string contentPath)
{
return new Uri(helper.ActionContext.HttpContext.Request.GetUri(), helper.Content(contentPath)).ToString();
}
public static string AbsoluteRouteUrl(
this IUrlHelper helper,
string routeName,
object routeValues = null)
{
string scheme = helper.ActionContext.HttpContext.Request.Scheme;
return helper.RouteUrl(routeName, routeValues, scheme);
}
}
总结
本文主要介绍了自定义路由匹配和生成的解决方案,把路由相关的处理集中到一个类中,避免分散在各个视图进行维护。下篇文章,将会介绍自定义视图搜索目录及主题切换。
ASP.NET Core 中的SEO优化(3):自定义路由匹配和生成的更多相关文章
- ASP.NET Core 中的SEO优化(4):自定义视图路径及主题切换
系列回顾 <ASP.NET Core 中的SEO优化(1):中间件实现服务端静态化缓存> <ASP.NET Core 中的SEO优化(2):中间件中渲染Razor视图> < ...
- ASP.NET Core 中的SEO优化(2):中间件中渲染Razor视图
前言 上一篇文章<ASP.NET Core 中的SEO优化(1):中间件实现服务端静态化缓存>中介绍了中间件的使用方法.以及使用中间件实现服务端静态化缓存的功能.本系列文章的这些技巧都是我 ...
- ASP.NET Core 中的SEO优化(1):中间件实现服务端静态化缓存
分享 最近在公司成功落地了一个用ASP.NET Core 开发前台的CMS项目,虽然对于表层的开发是兼容MVC5的,但是作为爱好者当然要用尽量多的ASP.NET Core新功能了. 背景 在项目开发的 ...
- Asp.Net Core 中IdentityServer4 授权中心之自定义授权模式
一.前言 上一篇我分享了一篇关于 Asp.Net Core 中IdentityServer4 授权中心之应用实战 的文章,其中有不少博友给我提了问题,其中有一个博友问我的一个场景,我给他解答的还不够完 ...
- ASP.NET Core中使用Cache缓存
ASP.NET Core中使用Cache缓存 缓存介绍: 通过减少生成内容所需的工作,缓存可以显著提高应用的性能和可伸缩性. 缓存对不经常更改的数据效果最佳. 缓存生成的数据副本的返回速度可以比从原始 ...
- Asp.Net Core 中IdentityServer4 授权原理及刷新Token的应用
一.前言 上面分享了IdentityServer4 两篇系列文章,核心主题主要是密码授权模式及自定义授权模式,但是仅仅是分享了这两种模式的使用,这篇文章进一步来分享IdentityServer4的授权 ...
- Asp.Net Core 中IdentityServer4 实战之 Claim详解
一.前言 由于疫情原因,让我开始了以博客的方式来学习和分享技术(持续分享的过程也是自己学习成长的过程),同时也让更多的初学者学习到相关知识,如果我的文章中有分析不到位的地方,还请大家多多指教:以后我会 ...
- Asp.Net Core 中IdentityServer4 实战之角色授权详解
一.前言 前几篇文章分享了IdentityServer4密码模式的基本授权及自定义授权等方式,最近由于改造一个网关服务,用到了IdentityServer4的授权,改造过程中发现比较适合基于Role角 ...
- 在ASP.NET Core中构建路由的5种方法
原文链接 :https://stormpath.com/blog/routing-in-asp-net-core 在ASP.NET Core中构建路由的5种方法 原文链接 :https://storm ...
随机推荐
- shell基本认识
shell基本认识 bash # echo $BASH /bin/bash 第一个shell脚本first_shell.sh #!/bin/bash echo "Hello world!&q ...
- Python实现单链表
定义链表结构: class ListNode: def __init__(self, x): self.val = x self.next = None 输出该链表l1的元素: while l1: p ...
- JQuery获取指定元素中的checkbox选中状态的一些属性
项目中用户上传病例数据,每一次上传自动生成一个病例文件夹,数据保存到后台,前端显示文件夹,现在的需求是勾选想要删除的文件夹的chenckbox,点击删除后,数据库和前端都相应的更新. 如果是静态页面, ...
- iOS 可变字符串NSMutableString的使用
.创建一个可变字符串 NSMutableString * ms1 = [[NSMutableString alloc]init]; .可以通过类方法来创建 NSMutableString * ms2 ...
- [PostgreSQL]PostgreSQL数据类型格式化函数——字符串和数值间的转换
详情见官网:http://www.postgres.cn/docs/10/functions-formatting.html PostgreSQL中有以下格式化函数: 函数 返回类型 描述 例子 to ...
- 【BZOJ3597】方伯伯运椰子(分数规划,网络流)
[BZOJ3597]方伯伯运椰子(分数规划,网络流) 题解 给定了一个满流的费用流模型 如果要修改一条边,那么就必须满足流量平衡 也就是会修改一条某两点之间的路径上的所有边 同时还有另外一条路径会进行 ...
- Java中Collection 的基本认识
集合Conllection的基本概念:1.集合的基本认识:如StringBuffer&StringBuilder是集合(存储的对象类型是String).数组.对象与其很相似,但是还有区别.2. ...
- c++复习要点
自增和自减运算符有前缀和后缀两种形式,都会改变对象,所以不能对常量对象操作. 前缀形式返回改变后的对象,返回*this. 后缀形式返回改变之前的值,所以必须创建一个代表这个值的独立对象并返回它,是通过 ...
- MySQL学习(一): MySQL的初步操作与命令
启动与关闭: 用管理员身份运行cmd 启动:net start mysql57 其中mysql57是我的MySQL名称,版本不同名称可能不一样 关闭:net stop mysql57 与上面同理 登录 ...
- Shell学习笔记——算数运算与条件测试
算数运算 1. 使用let命令 #!/sbin/bash var1=2 var2=3 let sum=var1+var2 echo $sum 使用let命令式,变量前不需要加$号 只用于整数运算,不适 ...