上一篇文章《ASP.NET Core中使用默认MVC路由》提到了如何使用默认的MVC路由配置,通过这个配置,我们就可以把请求路由到Controller和Action,通常情况下我们使用默认的路由器就可以了。

但是有些情况下,我们需要创建自己的路由规则,不是简单的修改MVC路由模板这么简单,比如我们需要针对一些特定的URL做特殊处理,这种情况通常是我们需要兼容一些旧的URL,但是升级之后总不能不管吧,要们做跳转或者给用户一个友好提示等等。如果旧的URL是MVC的那种格式Controller/Action格式还好办,如果是webform格式的URL,比如xxx.aspx或者静态文件URL,比如bbb.html。这时候我们不能使用默认的MVC路由器来处理我们的请求,我们需要提供一个特定的Router,也可以为理解为路由处理器,如果请求的URL匹配得上,那么就交给一个特定的handler处理。

这里我们提到了Handler,实际上可以理解为Httphandler,没错就是它。在Webform里面,之前版本的MVC都一直存在,web请求最终都给交给一个特定的Handler处理。在Webform里面的handler是aspx的code behind文件,在MVC里面是Routehandler,之后各自实现自己的process方法。

好了不深入去看,我们接着上一篇的例子,创建自己的一个Router,这Router可以实现以下功能

1.这个Router的作用是兼容不存在的Url,对于这些不存在的Url,我们给出友好提示

2.我们还可以将某些特定URL的请求转交给MVC框架去处理,这里说的是转交,而不是直接让MVC路由去处理。

实现过程

1.在项目根目录下创建一个类,名字为LegacyRoute,实现IRouter接口,实现代码如下

public class LegacyRoute : IRouter
{
private readonly string[] _urls; public LegacyRoute(params string[] urls)
{
_urls = urls;
} public Task RouteAsync(RouteContext context)
{
var requestedUrl = context.HttpContext.Request.Path.Value.TrimEnd('/');
if (_urls.Contains(requestedUrl, StringComparer.OrdinalIgnoreCase))
{
context.Handler = async ctx => {
var response = ctx.Response;
byte[] bytes = Encoding.ASCII.GetBytes($"This URL: {requestedUrl} is not available now");
await response.Body.WriteAsync(bytes, , bytes.Length);
};
}
return Task.CompletedTask;
} public VirtualPathData GetVirtualPath(VirtualPathContext context)
{
return null;
}
}

上述代码实现了IRouter的两个接口,这两个接口方法的简单介绍一下

RouteAsync:这个处理请求的关键方法,有请求过来的时候,并且URL能匹配的上,系统会调用这个Router进行匹配,看能否处理这个请求,如果能处理则给出响应。

比如这个例子,Router保存了一些需要兼容的旧的Url,如果请求过来的Url包含在里面,那么直接Repsonse输出结果。

处理的方式是创建一个Handler的实例给RouteContext,这个Handler的类型是RequestDelegate,是一个委托,这个实例可以理解为一个中间件实例,参考《ASP.NET Core中Middleware的使用》里面的说明。

GetVirtualPath:这个方法是返回用户能看到的URL路径,比如在cshtml里面调用Url.Action得到的Url,会调用这个方法获取对应的URL,这个方法一会再讲讲如何实现。

2.定义好了Router之后,然后就是应用到特定的Url,路由配置当然也是在Startup里面完成。

由于我们要把自定义的Router加入到当前的Routes集合,所以之前使用的简化版UseMvcWithDefaultRoute需要改成如下方式

app.UseMvc(routes =>
{
routes.Routes.Add(new LegacyRoute(
"/articles/aspwinform.html",
"/old/mvc3"));
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});

这里自定义的LegacyRoute要放在MVC的路由之前,否则会被MVC的路由提前拦截。

LegacyRoute的构造方法里提供了两个需要兼容的旧的Url,这时候启动项目,并且输入这两个URL会给出一段文字提示。

如果输入其他不存在的路径,那么还是返回404错误的

3 这时这个Router只是简单的修改Response内容,那如果需要返回更多的信息咋办,不能在这方寸方法里写大堆html代码吧,自己定义一套模板模式,

那几乎又是重写了一套逻辑。MVC的模板已经很强大了,那么我们完全可以把这个路由接收到请求再次转交给MVC去处理,这样就能用到高达上的Razor模板了。

我们先新建一个Controller,名字为LegacyController,增加一个简单的Action

public class LegacyController : Controller
{
public ViewResult GetLegacyUrl(string legacyUrl)
=> View("GetLegacyUrl", legacyUrl);
}

然后在Views文件夹创建Legacy目录,在Legacy目录创建GetLegacyUrl.cshtml文件,然后给一些内容文字什么的,具体不给截图了,都很简。

@model string
@{ Layout = null; }
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Routing</title>
</head>
<body class="panel-body">
<h2>GetLegacyURL</h2>
This URL: @Model is not available now
</body>
</html>

创建这两个文件是为了后面操作做铺垫。

紧接着修改LegacyRoute为如下代码

public class LegacyRoute : IRouter
{
private readonly string[] _urls;
private readonly IRouter _mvcRoute; public LegacyRoute(IServiceProvider services, params string[] urls)
{
_urls = urls;
_mvcRoute = services.GetRequiredService<MvcRouteHandler>();
} public async Task RouteAsync(RouteContext context)
{
var requestedUrl = context.HttpContext.Request.Path.Value.TrimEnd('/');
if (_urls.Contains(requestedUrl, StringComparer.OrdinalIgnoreCase))
{
context.RouteData.Values["controller"] = "Legacy";
context.RouteData.Values["action"] = "GetLegacyUrl";
context.RouteData.Values["legacyUrl"] = requestedUrl;
await _mvcRoute.RouteAsync(context);
}
} public VirtualPathData GetVirtualPath(VirtualPathContext context)
{
return null;
}
}

主要改动是注入了IServiceProvider,这是Core里面用于查找服务的Provider,用它实现service locate功能,查找我们需要的服务组件,这是IOC的知识点,先不详述。

引入IServiceProvider主要是为了得到MvcRouteHandler这个服务组件。

设置contexnt的RouteData的数据,然后将整个context实例交给MvcRouteHandler的实例去处理,这样就顺利将某些特定旧Url的请求转交给了Mvc去处理。

LegacyRoute的构造方法改了,那么在Startup里面也要调整,要提供IServiceProvider的实例,还好通过IApplicationBuilder的实例就可以轻松获得

routes.Routes.Add(new LegacyRoute(
app.ApplicationServices,
"/articles/aspwinform.html",
"/old/mvc3"));

4 到这一步的时候,看起来已经可以处理某些特定的URL的请求。但是LegacyRoute里面还有一个方法没有实现,那就是GetVirtualPath,这是干嘛的呢,实际上就是用于生成对外显示的URL路径。

使用如下代码完善GetVirtualPath方法

public VirtualPathData GetVirtualPath(VirtualPathContext context)
{
if (context.Values.ContainsKey("legacyUrl"))
{
var url = context.Values["legacyUrl"] as string;
if (_urls.Contains(url))
{
return new VirtualPathData(this, url);
}
}
return null;
}

这个代码是判断是否在路由参数里提供了legacyUrl参数,如果有则进一步处理,并返回一个VirtualPathData实例。

说这么多可能不太好理解,实际上这个用在cshtml里面就明白了

在页面里创建一个a标记,使用如下代码

<a asp-route-legacyurl="/old/mvc3">/old/mvc3</a> (由于这里用到了TagHelper,所以还必须做一些处理,具体参考源代码的配置)

那么生成的的Html代码如下

<a href="/old/mvc3">/old/mvc3</a>

看起来比较的多余,跟直接写死/old/mvc3路径一样效果,但是走的路径不同,如果修改了路由规则,那么通过asp-route-*方式设置的路径也会跟着修改。

到这里基本实现了自定义的路由,基本的思路就是这样,当然能做跟多复杂的事情,甚至定义一个完整的路由规则。

完整代码示例可以从以下路径下载

https://github.com/shenba2014/AspDotNetCoreMvcExamples/tree/master/CustomRouter

最新文章

  1. ManagementClass类解析和C#如何获取硬件的相关信息
  2. adb shell
  3. 跟随标准与Webkit源码探究DOM -- 获取元素之getElementsByClassName
  4. cvWaitKey
  5. 也谈SSO,一个简单实用的单点登录Demo
  6. noi1816 画家问题(技巧搜索Dfs)
  7. BZOJ1674: [Usaco2005]Part Acquisition
  8. Android开发中在一个Activity中关闭另一个Activity
  9. IOS设计模式学习(7)单例
  10. Flex 数据绑定
  11. postgresql语句
  12. mysql 查询近7天数据,缺失补0
  13. chapter4 module and port
  14. HTML中使用&lt;input&gt;添加的按钮打开一个链接
  15. MySQL与Oracle集群主从复制工具
  16. BZOJ3289 Mato的文件管理(莫队+树状数组)
  17. 20155217 2016-2017-2《java程序设计》第一周学习总结
  18. Switf与OC混合开发流程
  19. SQL大全(1)
  20. cf299C Weird Game

热门文章

  1. Replication--发布属性immediate_sync
  2. 在.net Core中使用StackExchange.Redis 2.0
  3. 「HNOI 2015」落忆枫音
  4. coffee主题美化内容概要
  5. go iris xorm包使用(sqlite3数据库增删查改)
  6. 2018OCP最新题库052新加考题及答案整理-27
  7. PL/SQL数据开发那点事
  8. 使用bluebird解决promise兼容性问题
  9. jupyter notebook不小心点了退出,怎么找到密码
  10. Angular material mat-icon 资源参考_Places