dotnet core 開發體驗之Routing


開始

回顧上一篇文章:dotnet core開發體驗之開始MVC 里面體驗了一把mvc,然后我們知道了aspnet mvc是靠Routing來驅動起來的,所以感覺需要研究一下Routing是什么鬼。

Routing簡單使用體驗

首先我們用命令yo aspnet創建一個新的空web項目。(Yeoman的使用自己研究,參考:https://docs.asp.net/en/latest/client-side/yeoman.html?#building-projects-with-yeoman)

創建完項目后,在project.json里面添加Routing依賴。

"dependencies": {
    ...
    "Microsoft.AspNetCore.Routing": "1.0.0-*"
},

添加完依賴后,修改Startup里面的Configure,和ConfigureServices里面添加Routing的使用依賴
修改前:

public void Configure(IApplicationBuilder app)
{
        app.Run(async (context) =>
        {
            await context.Response.WriteAsync("Hello World!");
        });
}

修改后:

public void ConfigureServices(IServiceCollection services)
{
        services.AddRouting();
}

public void Configure(IApplicationBuilder app)
{
        var endpoint = new RouteHandler((c) => c.Response.WriteAsync("Hello, I am Routing!"));

        app.UseRouter(endpoint);
}

dotnet run 然后瀏覽器訪問http://localhost:5000/ 顯示為Hello, I am Routing! 接下來我們在http://localhost:5000/ 后面加入一些其他的東西來訪問,發現其實還是一樣打印Hello, I am Routing! 這讓我們感覺好像並沒有什么卵用的樣子。不着急我們先來看看Routing是怎么運行起來的。在開始這話題之前需要先了解到什么是中間件,參考:https://docs.asp.net/en/latest/fundamentals/middleware.html

Routing的最基本運行原理

Routing的驅動入口就是基於middleware的。可以先看看 app.UseRouter(endpoint)的內部實現,參考一個擴展方法類RoutingBuilderExtensions,可以看到最后有一句代碼return builder.UseMiddleware<RouterMiddleware>(router) 。這里可以很明顯看到,入口在RouterMiddleware的Invoke方法。

    public async Task Invoke(HttpContext httpContext)
    {
        var context = new RouteContext(httpContext);
        context.RouteData.Routers.Add(_router);

        await _router.RouteAsync(context);

        if (context.Handler == null)
        {
            _logger.RequestDidNotMatchRoutes();
            await _next.Invoke(httpContext);
        }
        else
        {
            httpContext.Features[typeof(IRoutingFeature)] = new RoutingFeature()
            {
                RouteData = context.RouteData,
            };

            await context.Handler(context.HttpContext);
        }
    }

這個入口的實現是這樣的:

Invoke入口 ===>>> 實例化一個 RouteContext ===>>> 把我們傳進來的 IRouter 存到 RouteContext里面的 RouteData =>>> 再執行IRouter的RouteAsync方法把RouteContext對象傳進去提供給具體實現使用。=>>> 如果context.Handler沒有東西,就執行下一個RequestDelegate,如果有的話就把RouteData保存起來然后執行這個RequestDelegate。

看到了這里我們已經可以明白下面這個代碼運行起來的原理。

public void Configure(IApplicationBuilder app)
{
        var endpoint = new RouteHandler((c) => c.Response.WriteAsync("Hello, I am Routing!"));

        app.UseRouter(endpoint);
}

可能還會有人不明白RouteHandler是個什么鬼,既然我們知道了代碼的實現運行原理,那么肯定可以猜到RouteHandler是有實現接口IRouter的。我們可以看RouteHandler代碼

    public Task RouteAsync(RouteContext context)
    {
        context.Handler = _requestDelegate;
        return TaskCache.CompletedTask;
    }

這里可以看到把我們的(c) => c.Response.WriteAsync("Hello, I am Routing!")賦值給context.Handler,然后由RouterMiddleware來執行我們這個事件方法。於是我們就可以在瀏覽器上面看到輸出 Hello, I am Routing!這么一句話了。

Routing的一些使用

文章到現在,我們雖然知道了Routing運行起來的一個大概原理,但是我們一直打印出相同內容,確實也沒有什么卵用呀。我們要改一下讓打印內容能有點改變。這個時候可以使用到Routing提供的Route類來使用。代碼修改如下:

    public void Configure(IApplicationBuilder app)
    {
        var endpoint = new RouteHandler((c) => c.Response.WriteAsync($"Hello, I am Routing! your item is {c.GetRouteValue("item")}"));
        var resolver = app.ApplicationServices.GetRequiredService<IInlineConstraintResolver>();
        var runRoute = new Route(endpoint,"{item}",resolver);

        app.UseRouter(runRoute);
    }

修改完代碼后,我們再次編譯運行,然后輸入http://localhost:5000/ 我們發現一片空白,然后再輸入http://localhost:5000/abc 發現打印出來的是Hello, I am Routing! your item is abc。然后再輸入其他的 http://localhost:5000/abc/cc 發現也是一片空白。這是因為我們給路由添加的匹配是主機地址/+{item}那其他的路徑都是匹配不到,那么肯定就是不會顯示任何東西啦。假設我們要給一個默認值,那么可以改成這樣

var runRoute = new Route(endpoint,"{item=home}",resolver);

OK,這個時候我們再輸入http://localhost:5000/ 看到的就是Hello, I am Routing! your item is home。

匹配原理相對比較復雜點,想要了解的話可以參考 RouteBase的源碼,然后看相關的類,看看我們設置的模板是如何解析的,然后如何和url進行匹配的。如果要要來解釋完整個過程的話,這個文章肯定是不夠的,所以各位可以自己了解一下。

假如要配置多個路由支持的話,可以使用RouteCollection

    public void Configure(IApplicationBuilder app)
    {
        var endpoint = new RouteHandler((c) => c.Response.WriteAsync($"Hello, I am Routing! your item is {c.GetRouteValue("item")}"));
        var resolver = app.ApplicationServices.GetRequiredService<IInlineConstraintResolver>();
        var runRoute = new Route(endpoint,"{item=home}",resolver);
        var otherRoute = new Route(endpoint,"other/{item=other_home}",resolver);

        var routeCollection = new RouteCollection();
        routeCollection.Add(runRoute);
        routeCollection.Add(otherRoute);

        app.UseRouter(routeCollection);
    }

修改成上面的代碼后就支持兩個路由,假如輸入的url是 http://localhost:5000/other 那么就是使用runRoute,如果輸入的是http://localhost:5000/other/myother 那么使用的就是otherRoute。

這樣書寫暴露了很多細節東西,我們可以用 Routing提供的RouteBuilder類來編寫相同的東西。代碼修改一下如下:

public void Configure(IApplicationBuilder app)
    {
        var endpoint = new RouteHandler((c) => c.Response.WriteAsync($"Hello, I am Routing! your item is {c.GetRouteValue("item")}"));
        
        var routeBuilder = new RouteBuilder(app)
        {
            DefaultHandler = endpoint,
        };
        
        routeBuilder.MapRoute("default","{item=home}");
        routeBuilder.MapRoute("other","other/{item=other_home}");


        app.UseRouter(routeBuilder.Build());
    }

如果有一些特殊的的路由配置,我們也可以使用routeBuilder.Routes.Add(route);這代碼來添加。至於能配置的模板都有些什么,可以看 Routing 的 Template 的測試類:https://github.com/aspnet/Routing/tree/dev/test/Microsoft.AspNetCore.Routing.Tests/Template 看完基本就知道都有些什么樣的模板格式可以使用了。

實現自己的 RouteHandler

到現在,我們已經知道了Routing大概是怎么運行起來,知道了如何簡單的使用。那么接下來可以來創建一個自己的RouteHandler,來加深一下對Routing的使用體驗。

創建一個類MyRouteHandler,實現接口IRoute:

public class MyRouteHandler : IRouter
{
    public VirtualPathData GetVirtualPath(VirtualPathContext context)
    {
        return null;
    }

    public Task RouteAsync(RouteContext context)
    {

        context.Handler = (c) =>
        {
            var printStr = $"controller:{c.GetRouteValue("controller")}," +
            $"action:{c.GetRouteValue("action")},id:{c.GetRouteValue("id")}";

            return c.Response.WriteAsync(printStr);
        };
        return TaskCache.CompletedTask;
    }
}

然后我們的路由配置改成這樣:

    public void Configure(IApplicationBuilder app)
    {
        var endpoint = new MyRouteHandler();
        
        var routeBuilder = new RouteBuilder(app)
        {
            DefaultHandler = endpoint,
        };
        
        routeBuilder.MapRoute("default","{controller=Home}/{action=Index}/{id?}");

        app.UseRouter(routeBuilder.Build());
    }

然后打開瀏覽器http://localhost:5000/ 打印出來的內容是 controller:Home,action:Index,id: 。這樣是不是很像我們去調用mvc的控制器和控制器的行為呢?Routing的體驗文章到這來就結束了,謝謝觀看。


由於本人水平有限,知識有限,文章難免會有錯誤,歡迎大家指正。如果有什么問題也歡迎大家回復交流。要是你覺得本文還可以,那么點擊一下推薦。


免責聲明!

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



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