欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

.NET Core开发日志——简述路由

程序员文章站 2022-12-29 17:51:23
有过ASP.NET或其它现代Web框架开发经历的开发者对路由这一名字应该不陌生。如果要用一句话解释什么是路由,可以这样形容:通过对URL的解析,指定相应的处理程序。 回忆下在Web Forms应用程序中使用路由的方式: 然后是MVC应用程序: 再到了ASP.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的方法。

以一张思维导图可以简单概括上述的过程。

.NET Core开发日志——简述路由

或者用三句话也可以描述整个流程。

  • 添加路由
  • 匹配地址
  • 处理请求