ASP.NET MVC 源码分析(二) —— 从 IRouteBuilder认识路由构建

  我们来看IRouteBuilder的定义:

    public interface IRouteBuilder
    {
        IRouter DefaultHandler { get; set; }

        IServiceProvider ServiceProvider { get; }

        IList<IRouter> Routes { get; }

        IRouter Build();
    }

一个默认的IRouter对象,一个Build方法,一个IRouter集合和一个获取服务对象IServiceProvider。

我们进一步看IRouteBuilder的实现RouterBuilder:

public class RouteBuilder : IRouteBuilder
    {
        public RouteBuilder()
        {
            Routes = new List<IRouter>();
        }

        public IRouter DefaultHandler { get; set; }

        public IServiceProvider ServiceProvider { get; set; }

        public IList<IRouter> Routes
        {
            get;
            private set;
        }

        public IRouter Build()
        {
            var routeCollection = new RouteCollection();

            foreach (var route in Routes)
            {
                routeCollection.Add(route);
            }

            return routeCollection;
        }
    }
View Code

主要的实现是Build方法,这个方法的实现也很简单,遍历Routes向一个实现了IRouter接口的RouteCollection对象添加IRouter,我们可以先看一下RouteCollection的实现:

public class RouteCollection : IRouteCollection
    {
        private readonly List<IRouter> _routes = new List<IRouter>();
        private readonly List<IRouter> _unnamedRoutes = new List<IRouter>();
        private readonly Dictionary<string, INamedRouter> _namedRoutes =
                                    new Dictionary<string, INamedRouter>(StringComparer.OrdinalIgnoreCase);

        private RouteOptions _options;

        public IRouter this[int index]
        {
            get { return _routes[index]; }
        }

        public int Count
        {
            get { return _routes.Count; }
        }

        public void Add([NotNull] IRouter router)
        {
            var namedRouter = router as INamedRouter;
            if (namedRouter != null)
            {
                if (!string.IsNullOrEmpty(namedRouter.Name))
                {
                    _namedRoutes.Add(namedRouter.Name, namedRouter);
                }
            }
            else
            {
                _unnamedRoutes.Add(router);
            }

            _routes.Add(router);
        }

        public async virtual Task RouteAsync(RouteContext context)
        {
            for (var i = 0; i < Count; i++)
            {
                var route = this[i];

                var oldRouteData = context.RouteData;

                var newRouteData = new RouteData(oldRouteData);
                newRouteData.Routers.Add(route);

                try
                {
                    context.RouteData = newRouteData;

                    await route.RouteAsync(context);
                    if (context.IsHandled)
                    {
                        break;
                    }
                }
                finally
                {
                    if (!context.IsHandled)
                    {
                        context.RouteData = oldRouteData;
                    }
                }
            }
        }

        public virtual VirtualPathData GetVirtualPath(VirtualPathContext context)
        {
            EnsureOptions(context.Context);

            // If we're using Best-Effort link generation then it means that we'll first look for a route where
            // the route values are validated (context.IsBound == true). If we can't find a match like that, then
            // we'll return the path from the first route to return one.
            var useBestEffort = _options.UseBestEffortLinkGeneration;

            if (!string.IsNullOrEmpty(context.RouteName))
            {
                var isValidated = false;
                VirtualPathData bestPathData = null;
                INamedRouter matchedNamedRoute;
                if (_namedRoutes.TryGetValue(context.RouteName, out matchedNamedRoute))
                {
                    bestPathData = matchedNamedRoute.GetVirtualPath(context);
                    isValidated = context.IsBound;
                }

                // If we get here and context.IsBound == true, then we know we have a match, we want to keep
                // iterating to see if we have multiple matches.
                foreach (var unnamedRoute in _unnamedRoutes)
                {
                    // reset because we're sharing the context
                    context.IsBound = false;

                    var pathData = unnamedRoute.GetVirtualPath(context);
                    if (pathData == null)
                    {
                        continue;
                    }

                    if (bestPathData != null)
                    {
                        // There was already a previous route which matched the name.
                        throw new InvalidOperationException(
                            Resources.FormatNamedRoutes_AmbiguousRoutesFound(context.RouteName));
                    }
                    else if (context.IsBound)
                    {
                        // This is the first 'validated' match that we've found.
                        bestPathData = pathData;
                        isValidated = true;
                    }
                    else
                    {
                        Debug.Assert(bestPathData == null);

                        // This is the first 'unvalidated' match that we've found.
                        bestPathData = pathData;
                        isValidated = false;
                    }
                }

                if (isValidated || useBestEffort)
                {
                    context.IsBound = isValidated;

                    if (bestPathData != null)
                    {
                        bestPathData = new VirtualPathData(
                            bestPathData.Router,
                            NormalizeVirtualPath(bestPathData.VirtualPath),
                            bestPathData.DataTokens);
                    }

                    return bestPathData;
                }
                else
                {
                    return null;
                }
            }
            else
            {
                VirtualPathData bestPathData = null;
                for (var i = 0; i < Count; i++)
                {
                    var route = this[i];

                    var pathData = route.GetVirtualPath(context);
                    if (pathData == null)
                    {
                        continue;
                    }

                    if (context.IsBound)
                    {
                        // This route has validated route values, short circuit.
                        return new VirtualPathData(
                            pathData.Router,
                            NormalizeVirtualPath(pathData.VirtualPath),
                            pathData.DataTokens);
                    }
                    else if (bestPathData == null)
                    {
                        // The values aren't validated, but this is the best we've seen so far
                        bestPathData = pathData;
                    }
                }

                if (useBestEffort)
                {
                    return new VirtualPathData(
                        bestPathData.Router,
                        NormalizeVirtualPath(bestPathData.VirtualPath),
                        bestPathData.DataTokens);
                }
                else
                {
                    return null;
                }
            }
        }

        private PathString NormalizeVirtualPath(PathString path)
        {
            var url = path.Value;

            if (!string.IsNullOrEmpty(url) && _options.LowercaseUrls)
            {
                var indexOfSeparator = url.IndexOfAny(new char[] { '?', '#' });

                // No query string, lowercase the url
                if (indexOfSeparator == -1)
                {
                    url = url.ToLowerInvariant();
                }
                else
                {
                    var lowercaseUrl = url.Substring(0, indexOfSeparator).ToLowerInvariant();
                    var queryString = url.Substring(indexOfSeparator);

                    // queryString will contain the delimiter ? or # as the first character, so it's safe to append.
                    url = lowercaseUrl + queryString;
                }

                return new PathString(url);
            }

            return path;
        }

        private void EnsureOptions(HttpContext context)
        {
            if (_options == null)
            {
                _options = context.RequestServices.GetRequiredService<IOptions<RouteOptions>>().Options;
            }
        }
    }

 乍一看这个类的功能还是比较庞大的,我们主要关注他对IRouter接口签名的实现:

Task RouteAsync(RouteContext context):

通过代码我们可以看到,这个方法主要对RouteCollection本身持有的Route 规则循环添加到路由上下文RouteContext.RouteData中。

VirtualPathData GetVirtualPath(VirtualPathContext context):

 

 

 


posted @ 2015-06-06 18:01  David·Li  阅读(861)  评论(1编辑  收藏  举报