/// <summary>
        /// Maps the specified OData route and the OData route attributes.
        /// </summary>
        /// <param name="configuration">The server configuration.</param>
        /// <param name="routeName">The name of the route to map.</param>
        /// <param name="routePrefix">The prefix to add to the OData route's path template.</param>
        /// <param name="apiVersion">The <see cref="ApiVersion">API version</see> associated with the model.</param>
        /// <param name="configureAction">The configuring action to add the services to the root container.</param>
        /// <param name="configureRoutingConventions">The configuring action to add or update routing conventions.</param>
        /// <returns>The added <see cref="ODataRoute"/>.</returns>
        public static ODataRoute MapVersionedODataRoute(
            this HttpConfiguration configuration,
            string routeName,
            string routePrefix,
            ApiVersion apiVersion,
            Action <IContainerBuilder> configureAction,
            Action <ODataConventionConfigurationContext> configureRoutingConventions)
        {
            Arg.NotNull(configuration, nameof(configuration));
            Arg.NotNullOrEmpty(routeName, nameof(routeName));
            Arg.NotNull(apiVersion, nameof(apiVersion));
            Contract.Ensures(Contract.Result <ODataRoute>() != null);

            object ConfigureRoutingConventions(IServiceProvider serviceProvider)
            {
                var model = serviceProvider.GetRequiredService <IEdmModel>();
                var routingConventions = VersionedODataRoutingConventions.CreateDefault();
                var context            = new ODataConventionConfigurationContext(configuration, routeName, model, apiVersion, routingConventions);

                model.SetAnnotationValue(model, new ApiVersionAnnotation(apiVersion));
                routingConventions.Insert(0, new VersionedAttributeRoutingConvention(routeName, configuration, apiVersion));
                configureRoutingConventions?.Invoke(context);

                return(context.RoutingConventions.ToArray());
            }

            if (!IsNullOrEmpty(routePrefix))
            {
                routePrefix = routePrefix.TrimEnd('/');
            }

            var rootContainer = configuration.CreateODataRootContainer(
                routeName,
                builder =>
            {
                builder.AddService(Singleton, typeof(IEnumerable <IODataRoutingConvention>), ConfigureRoutingConventions);
                configureAction?.Invoke(builder);
            });
            var pathHandler = rootContainer.GetRequiredService <IODataPathHandler>();

            if (pathHandler != null && pathHandler.UrlKeyDelimiter == null)
            {
                pathHandler.UrlKeyDelimiter = configuration.GetUrlKeyDelimiter();
            }

            rootContainer.InitializeAttributeRouting();

            var routeConstraint = new VersionedODataPathRouteConstraint(routeName, apiVersion);
            var route           = default(ODataRoute);
            var routes          = configuration.Routes;
            var messageHandler  = rootContainer.GetService <HttpMessageHandler>();
            var options         = configuration.GetApiVersioningOptions();

            if (messageHandler != null)
            {
                route = new ODataRoute(
                    routePrefix,
                    routeConstraint,
                    defaults: null,
                    constraints: null,
                    dataTokens: null,
                    handler: messageHandler);
            }
            else
            {
                var batchHandler = rootContainer.GetService <ODataBatchHandler>();

                if (batchHandler != null)
                {
                    batchHandler.ODataRouteName = routeName;
                    var batchTemplate = IsNullOrEmpty(routePrefix) ? ODataRouteConstants.Batch : routePrefix + '/' + ODataRouteConstants.Batch;
                    routes.MapHttpBatchRoute(routeName + nameof(ODataRouteConstants.Batch), batchTemplate, batchHandler);
                }

                route = new ODataRoute(routePrefix, routeConstraint);
            }

            routes.Add(routeName, route);
            AddApiVersionConstraintIfNecessary(route, options);

            var unversionedRouteConstraint = new ODataPathRouteConstraint(routeName);
            var unversionedRoute           = new ODataRoute(routePrefix, new UnversionedODataPathRouteConstraint(unversionedRouteConstraint, apiVersion));

            AddApiVersionConstraintIfNecessary(unversionedRoute, options);
            configuration.Routes.Add(routeName + UnversionedRouteSuffix, unversionedRoute);

            return(route);
        }
        /// <summary>
        /// Maps the specified versioned OData routes.
        /// </summary>
        /// <param name="configuration">The extended <see cref="HttpConfiguration">HTTP configuration</see>.</param>
        /// <param name="routeName">The name of the route to map.</param>
        /// <param name="routePrefix">The prefix to add to the OData route's path template.</param>
        /// <param name="models">The <see cref="IEnumerable{T}">sequence</see> of <see cref="IEdmModel">EDM models</see> to use for parsing OData paths.</param>
        /// <param name="configureAction">The configuring action to add the services to the root container.</param>
        /// <param name="configureRoutingConventions">The configuring action to add or update routing conventions.</param>
        /// <param name="batchHandler">The <see cref="ODataBatchHandler">OData batch handler</see>.</param>
        /// <returns>The <see cref="IReadOnlyList{T}">read-only list</see> of added <see cref="ODataRoute">OData routes</see>.</returns>
        /// <remarks>The specified <paramref name="models"/> must contain the <see cref="ApiVersionAnnotation">API version annotation</see>.  This annotation is
        /// automatically applied when you use the <see cref="VersionedODataModelBuilder"/> and call <see cref="VersionedODataModelBuilder.GetEdmModels"/> to
        /// create the <paramref name="models"/>.</remarks>
        public static IReadOnlyList <ODataRoute> MapVersionedODataRoutes(
            this HttpConfiguration configuration,
            string routeName,
            string routePrefix,
            IEnumerable <IEdmModel> models,
            Action <IContainerBuilder> configureAction,
            Action <ODataConventionConfigurationContext> configureRoutingConventions,
            ODataBatchHandler batchHandler)
        {
            Arg.NotNull(configuration, nameof(configuration));
            Arg.NotNullOrEmpty(routeName, nameof(routeName));
            Arg.NotNull(models, nameof(models));
            Contract.Ensures(Contract.Result <IReadOnlyList <ODataRoute> >() != null);

            object ConfigureRoutingConventions(IEdmModel model, string versionedRouteName, ApiVersion apiVersion)
            {
                var routingConventions = VersionedODataRoutingConventions.CreateDefault();
                var context            = new ODataConventionConfigurationContext(configuration, versionedRouteName, model, apiVersion, routingConventions);

                model.SetAnnotationValue(model, new ApiVersionAnnotation(apiVersion));
                routingConventions.Insert(0, new VersionedAttributeRoutingConvention(versionedRouteName, configuration, apiVersion));
                configureRoutingConventions?.Invoke(context);

                return(context.RoutingConventions.ToArray());
            }

            if (!IsNullOrEmpty(routePrefix))
            {
                routePrefix = routePrefix.TrimEnd('/');
            }

            var routes = configuration.Routes;
            var unversionedRouteName = routeName + UnversionedRouteSuffix;

            if (batchHandler != null)
            {
                batchHandler.ODataRouteName = unversionedRouteName;
                var batchTemplate = IsNullOrEmpty(routePrefix) ? ODataRouteConstants.Batch : routePrefix + '/' + ODataRouteConstants.Batch;
                routes.MapHttpBatchRoute(routeName + nameof(ODataRouteConstants.Batch), batchTemplate, batchHandler);
            }

            var odataRoutes            = new List <ODataRoute>();
            var unversionedConstraints = new List <IHttpRouteConstraint>();

            foreach (var model in models)
            {
                var versionedRouteName = routeName;
                var apiVersion         = model.GetAnnotationValue <ApiVersionAnnotation>(model)?.ApiVersion;
                var routeConstraint    = MakeVersionedODataRouteConstraint(apiVersion, ref versionedRouteName);

                unversionedConstraints.Add(new ODataPathRouteConstraint(versionedRouteName));

                var rootContainer = configuration.CreateODataRootContainer(
                    versionedRouteName,
                    builder =>
                {
                    builder.AddService(Singleton, typeof(IEdmModel), sp => model)
                    .AddService(Singleton, typeof(IEnumerable <IODataRoutingConvention>), sp => ConfigureRoutingConventions(model, versionedRouteName, apiVersion));
                    configureAction?.Invoke(builder);
                });

                var pathHandler = rootContainer.GetRequiredService <IODataPathHandler>();

                if (pathHandler != null && pathHandler.UrlKeyDelimiter == null)
                {
                    pathHandler.UrlKeyDelimiter = configuration.GetUrlKeyDelimiter();
                }

                rootContainer.InitializeAttributeRouting();

                var route          = default(ODataRoute);
                var messageHandler = rootContainer.GetService <HttpMessageHandler>();
                var options        = configuration.GetApiVersioningOptions();

                if (messageHandler == null)
                {
                    route = new ODataRoute(routePrefix, routeConstraint);
                }
                else
                {
                    route = new ODataRoute(routePrefix, routeConstraint, defaults: null, constraints: null, dataTokens: null, handler: messageHandler);
                }

                routes.Add(versionedRouteName, route);
                AddApiVersionConstraintIfNecessary(route, options);
                odataRoutes.Add(route);
            }

            configuration.AddRouteToRespondWithBadRequestWhenAtLeastOneRouteCouldMatch(unversionedRouteName, routePrefix, odataRoutes, unversionedConstraints, configureAction);

            return(odataRoutes);
        }