.NET Core开发日志——简述路由
有过asp.net或其它现代web框架开发经历的开发者对路由这一名字应该不陌生。如果要用一句话解释什么是路由,可以这样形容:通过对url的解析,指定相应的处理程序。
回忆下在web forms应用程序中使用路由的方式:
public static void registerroutes(routecollection routes) { routes.mappageroute("", "category/{action}/{categoryname}", "~/categoriespage.aspx"); }
然后是mvc应用程序:
public static void registerroutes(routecollection routes) { routes.ignoreroute("{resource}.axd/{*pathinfo}"); routes.maproute( "default", "{controller}/{action}/{id}", new { controller = "home", action = "index", id = "" } ); }
再到了asp.net core:
public void configure(iapplicationbuilder app) { app.usemvc(routes => { routes.maproute( name: "default", template: "{controller=home}/{action=index}/{id?}"); }); }
还可以用更简单的写法:
public void configure(iapplicationbuilder app) { app.usemvcwithdefaultroute(); }
从源码上看这两个方法的实现是一样的。
public static iapplicationbuilder usemvcwithdefaultroute(this iapplicationbuilder app) { if (app == null) { throw new argumentnullexception(nameof(app)); } return app.usemvc(routes => { routes.maproute( name: "default", template: "{controller=home}/{action=index}/{id?}"); }); }
关键是内部usemvc方法的内容:
public static iapplicationbuilder usemvc( this iapplicationbuilder app, action<iroutebuilder> configureroutes) { ... var routes = new routebuilder(app) { defaulthandler = app.applicationservices.getrequiredservice<mvcroutehandler>(), }; configureroutes(routes); routes.routes.insert(0, attributerouting.createattributemegaroute(app.applicationservices)); return app.userouter(routes.build()); }
其中的处理过程,首先实例化了一个routebuilder对象,并对它的defaulthandler属性赋值为mvcroutehandler。接着以其为参数,执行routes.maproute方法。
maproute的处理过程就是为routebuilder里的routes集合新增一个route对象。
public static iroutebuilder maproute( this iroutebuilder routebuilder, string name, string template, object defaults, object constraints, object datatokens) { ... var inlineconstraintresolver = routebuilder .serviceprovider .getrequiredservice<iinlineconstraintresolver>(); routebuilder.routes.add(new route( routebuilder.defaulthandler, name, template, new routevaluedictionary(defaults), new routevaluedictionary(constraints), new routevaluedictionary(datatokens), inlineconstraintresolver)); return routebuilder; }
有此一个route对象仍不夠,程序里又插入了一个attributeroute。
随后执行routes.build(),返回routecollection集合。该集合实现了irouter接口。
public irouter build() { var routecollection = new routecollection(); foreach (var route in routes) { routecollection.add(route); } return routecollection; }
最终使用已完成配置的路由。
public static iapplicationbuilder userouter(this iapplicationbuilder builder, irouter router) { ... return builder.usemiddleware<routermiddleware>(router); }
于是又看到了熟悉的middleware。它的核心方法里先调用了routecollection的routeasync处理。
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); } }
其内部又依次执行各个route的routeasync方法。
public async virtual task routeasync(routecontext context) { ... for (var i = 0; i < count; i++) { var route = this[i]; context.routedata.routers.add(route); try { await route.routeasync(context); if (context.handler != null) { break; } } ... } }
之前的逻辑中分别在routecollection里加入了attributeroute与route。
*循环中会判断handler是否被赋值,这是为了避免在路由已被匹配的情况下,继续进行其它的匹配。从执行顺序来看,很容易明白attributeroute比一般route优先级高的道理。
先执行attributeroute里的routeasync方法:
public task routeasync(routecontext context) { var router = gettreerouter(); return router.routeasync(context); }
里面调用了treerouter的routeasync方法:
public async task routeasync(routecontext context) { foreach (var tree in _trees) { var tokenizer = new pathtokenizer(context.httpcontext.request.path); var root = tree.root; var treeenumerator = new treeenumerator(root, tokenizer); ... while (treeenumerator.movenext()) { var node = treeenumerator.current; foreach (var item in node.matches) { var entry = item.entry; var matcher = item.templatematcher; try { if (!matcher.trymatch(context.httpcontext.request.path, context.routedata.values)) { continue; } if (!routeconstraintmatcher.match( entry.constraints, context.routedata.values, context.httpcontext, this, routedirection.incomingrequest, _constraintlogger)) { continue; } _logger.matchedroute(entry.routename, entry.routetemplate.templatetext); context.routedata.routers.add(entry.handler); await entry.handler.routeasync(context); if (context.handler != null) { return; } } ... } } } }
如果所有attributeroute路由都不能匹配,则不会进一步作处理。否则的话,将继续执行handler中的routeasync方法。这里的handler是mvcattributeroutehandler。
public task routeasync(routecontext context) { ... var actiondescriptor = _actionselector.selectbestcandidate(context, actions); if (actiondescriptor == null) { _logger.noactionsmatched(context.routedata.values); return task.completedtask; } foreach (var kvp in actiondescriptor.routevalues) { if (!string.isnullorempty(kvp.value)) { context.routedata.values[kvp.key] = kvp.value; } } context.handler = (c) => { var routedata = c.getroutedata(); var actioncontext = new actioncontext(context.httpcontext, routedata, actiondescriptor); if (_actioncontextaccessor != null) { _actioncontextaccessor.actioncontext = actioncontext; } var invoker = _actioninvokerfactory.createinvoker(actioncontext); if (invoker == null) { throw new invalidoperationexception( resources.formatactioninvokerfactory_couldnotcreateinvoker( actiondescriptor.displayname)); } return invoker.invokeasync(); }; return task.completedtask; }
该方法内部的处理仅是为routecontext的handler属性赋值。实际的操作则是要到routermiddleware中invoke方法的context.handler(context.httpcontext)
这一步才被执行的。
至于route里的routeasync方法:
public virtual task routeasync(routecontext context) { ... ensurematcher(); ensureloggers(context.httpcontext); var requestpath = context.httpcontext.request.path; if (!_matcher.trymatch(requestpath, context.routedata.values)) { // if we got back a null value set, that means the uri did not match return task.completedtask; } // perf: avoid accessing dictionaries if you don't need to write to them, these dictionaries are all // created lazily. if (datatokens.count > 0) { mergevalues(context.routedata.datatokens, datatokens); } if (!routeconstraintmatcher.match( constraints, context.routedata.values, context.httpcontext, this, routedirection.incomingrequest, _constraintlogger)) { return task.completedtask; } _logger.matchedroute(name, parsedtemplate.templatetext); return onroutematched(context); }
只有路由被匹配的时候才在onroutematched里调用target的routeasync方法。
protected override task onroutematched(routecontext context) { context.routedata.routers.add(_target); return _target.routeasync(context); }
此处的target即是最初创建routebuilder时传入的mvcroutehandler。
public task routeasync(routecontext context) { ... var candidates = _actionselector.selectcandidates(context); if (candidates == null || candidates.count == 0) { _logger.noactionsmatched(context.routedata.values); return task.completedtask; } var actiondescriptor = _actionselector.selectbestcandidate(context, candidates); if (actiondescriptor == null) { _logger.noactionsmatched(context.routedata.values); return task.completedtask; } context.handler = (c) => { var routedata = c.getroutedata(); var actioncontext = new actioncontext(context.httpcontext, routedata, actiondescriptor); if (_actioncontextaccessor != null) { _actioncontextaccessor.actioncontext = actioncontext; } var invoker = _actioninvokerfactory.createinvoker(actioncontext); if (invoker == null) { throw new invalidoperationexception( resources.formatactioninvokerfactory_couldnotcreateinvoker( actiondescriptor.displayname)); } return invoker.invokeasync(); }; return task.completedtask; }
处理过程与mvcattributeroutehandler相似,一样是要在routermiddleware的invoke里才执行handler的方法。
以一张思维导图可以简单概括上述的过程。
或者用三句话也可以描述整个流程。
- 添加路由
- 匹配地址
- 处理请求