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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM