zoukankan      html  css  js  c++  java
  • ASP.NET Core中使用自定义路由

    上一篇文章《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, 0, 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

  • 相关阅读:
    2020春Contest
    HDU Count the string (KMP)
    P1757 通天之分组背包
    L1-050 倒数第N个字符串
    3月份目标
    Division UVa725
    数三角
    luogu P2051 [AHOI2009]中国象棋 dp 状态压缩+容斥
    Codeforces Round #654 (Div. 2) E
    Codeforces Round #654 (Div. 2) D
  • 原文地址:https://www.cnblogs.com/shenba/p/6376245.html
Copyright © 2011-2022 走看看