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;
        }
    }

主要的实现是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;
            }
        }
    }
				
时间: 2024-12-06 03:50:04

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

asp.net mvc源码分析-ModelValidatorProviders 客户端的验证

几年写过asp.net mvc源码分析-ModelValidatorProviders 当时主要是考虑mvc的流程对,客户端的验证也只是简单的提及了一下,现在我们来仔细看一下客户端的验证. 如图所示, 首先我们要知道这里的data-val这些属性是在哪里生成的?可以肯定是在mvc后台生成的, @Html.PasswordFor(m => m.Password) 生成input @Html.ValidationMessageFor(m => m.Password) 生成span 调用层级关系:

asp.net mvc源码分析-DefaultModelBinder 自定义的普通数据类型的绑定和验证

原文:asp.net mvc源码分析-DefaultModelBinder 自定义的普通数据类型的绑定和验证 在前面的文章中我们曾经涉及到ControllerActionInvoker类GetParameterValue方法中有这么一句代码: ModelBindingContext bindingContext = new ModelBindingContext() { FallbackToEmptyPrefix = (parameterDescriptor.BindingInfo.Prefix

ASP.NET MVC 源码分析(一)

ASP.NET MVC 源码分析(一) 直接上图: 我们先来看Core的设计: 从项目结构来看,asp.net.mvc.core有以下目录: ActionConstraints:action限制相关 AntiForgery:防伪相关 ActionResults:action返回对象相关 ApiExplorer:API描述和元数据相关接口 ApplicationModels:应用程序模型相关,应该是全局的model Areas:地区标签 Filters:大名鼎鼎的过滤器组件 Formatters:

asp.net MVC 源码分析

先上一张图吧 asp.net请求机制的图  by传智播客邹华栋老师 然后是 邹老师添加MVC请求过程的图 其实MVC 实在.netframework上加了一个过滤器  HttpModule 在C:\Windows\Microsoft.NET\Framework\v4.0.30319 下的配置文件里加入的  <add name="UrlRoutingModule-4.0" type="System.Web.Routing.UrlRoutingModule" /&

ASP.NET MVC源码分析系列

Controller下的JsonResult的ExecuteResult方法 public override void ExecuteResult(ControllerContext context) { if (context == null) { throw new ArgumentNullException("context"); } if ((this.JsonRequestBehavior == JsonRequestBehavior.DenyGet) &&

asp.net mvc源码分析-Action篇 IModelBinder

我们首先还是看看ReflectedParameterBindingInfo的Binder属性吧: public override IModelBinder Binder {            get {                IModelBinder binder = ModelBinders.GetBinderFromAttributes(_parameterInfo,                    () => String.Format(CultureInfo.Curre

WebForm / MVC 源码分析

ASP.NET WebForm / MVC 源码分析 浏览器 Url:https//localhost:6565/Home/Index ,https//localhost:6565/WebForm1.aspx,请求服务器(构建请求报文,并且将请求报文发送给服务器) 1:服务器(内核模式 Http.sys)对请求报文做基本的处理 2:请求服务器(用户模式,IIS服务器) 2.1:对发送过来的数据做一个检查,如果请求的是静态资源的(.html,jpg,js),那么IIS直接将这些资源返回浏览器 2.

netty 源码分析二

以服务端启动,接收客户端连接整个过程为例分析, 简略分为 五个过程: 1.NioServerSocketChannel 管道生成, 2.NioServerSocketChannel 管道完成初始化, 3.NioServerSocketChannel注册至Selector选择器, 4.NioServerSocketChannel管道绑定到指定端口,启动服务 5.NioServerSocketChannel接受客户端的连接,进行相应IO操作 Ps:netty内部过程远比这复杂,简略记录下方便以后回忆

[Android]Volley源码分析(二)Cache

Cache作为Volley最为核心的一部分,Volley花了重彩来实现它.本章我们顺着Volley的源码思路往下,来看下Volley对Cache的处理逻辑. 我们回想一下昨天的简单代码,我们的入口是从构造一个Request队列开始的,而我们并不直接调用new来构造,而是将控制权反转给Volley这个静态工厂来构造. com.android.volley.toolbox.Volley: public static RequestQueue newRequestQueue(Context conte