/// <summary>
        /// Maps the attribute-defined routes for the application.
        /// </summary>
        /// <param name="routes"></param>
        /// <param name="controllerTypes">The controller types to scan.</param>
        /// <param name="constraintResolver">
        /// The <see cref="IInlineConstraintResolver"/> to use for resolving inline constraints in route templates.
        /// </param>
        public static void MapAttributeRoutes(RouteCollection routes, IEnumerable <Type> controllerTypes,
                                              IInlineConstraintResolver constraintResolver)
        {
            AttributeRoutingMapper mapper = new AttributeRoutingMapper(new RouteBuilder2(constraintResolver));

            SubRouteCollection subRoutes = new SubRouteCollection();

            mapper.AddRouteEntries(subRoutes, controllerTypes);
            IReadOnlyCollection <RouteEntry> entries = subRoutes.Entries;

            if (entries.Count > 0)
            {
                RouteCollectionRoute aggregrateRoute = new RouteCollectionRoute(subRoutes);
                routes.Add(aggregrateRoute);

                // This sort is here to enforce a static ordering for link generation using these routes.
                // We don't apply dynamic criteria like ActionSelectors on link generation, but we can use the static
                // ones.
                RouteEntry[] sorted = entries
                                      .OrderBy(r => r.Route.GetOrder())
                                      .ThenBy(r => r.Route.GetPrecedence())
                                      .ToArray();

                AddGenerationHooksForSubRoutes(routes, sorted);
            }
        }