Esempio n. 1
0
        /// <summary>
        /// Maps a versioned OData route. When the <paramref name="batchHandler"/> is provided, it will create a '$batch' endpoint to handle the batch requests.
        /// </summary>
        /// <param name="builder">The extended <see cref="IRouteBuilder">route builder</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="model">The <see cref="IEdmModel">EDM model</see> to use for parsing OData paths.</param>
        /// <param name="apiVersion">The <see cref="ApiVersion">API version</see> associated with the model.</param>
        /// <param name="pathHandler">The <see cref="IODataPathHandler">OData path handler</see> to use for parsing the OData path.</param>
        /// <param name="routingConventions">The <see cref="IEnumerable{T}">sequence</see> of <see cref="IODataRoutingConvention">OData routing conventions</see>
        /// to use for controller and action selection.</param>
        /// <param name="batchHandler">The <see cref="ODataBatchHandler">OData batch handler</see>.</param>
        /// <returns>The mapped <see cref="ODataRoute">OData route</see>.</returns>
        /// <remarks>The <see cref="ApiVersionAnnotation">API version annotation</see> will be added or updated on the specified <paramref name="model"/> using
        /// the provided <paramref name="apiVersion">API version</paramref>.</remarks>
        public static ODataRoute MapVersionedODataRoute(
            this IRouteBuilder builder,
            string routeName,
            string routePrefix,
            IEdmModel model,
            ApiVersion apiVersion,
            IODataPathHandler pathHandler,
            IEnumerable <IODataRoutingConvention> routingConventions,
            ODataBatchHandler batchHandler)
        {
            Arg.NotNull(builder, nameof(builder));
            Arg.NotNullOrEmpty(routeName, nameof(routeName));
            Arg.NotNull(model, nameof(model));
            Arg.NotNull(apiVersion, nameof(apiVersion));
            Contract.Ensures(Contract.Result <ODataRoute>() != null);

            IEnumerable <IODataRoutingConvention> NewRoutingConventions(IServiceProvider serviceProvider)
            {
                var conventions = VersionedODataRoutingConventions.AddOrUpdate(routingConventions.ToList());

                conventions.Insert(0, new VersionedAttributeRoutingConvention(routeName, builder.ServiceProvider, apiVersion));
                return(conventions.ToArray());
            }

            var routeCollection          = builder.ServiceProvider.GetRequiredService <IODataRouteCollectionProvider>();
            var perRouteContainer        = builder.ServiceProvider.GetRequiredService <IPerRouteContainer>();
            var options                  = builder.ServiceProvider.GetRequiredService <ODataOptions>();
            var inlineConstraintResolver = builder.ServiceProvider.GetRequiredService <IInlineConstraintResolver>();

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

            model.SetAnnotationValue(model, new ApiVersionAnnotation(apiVersion));

            var configureAction = builder.ConfigureDefaultServices(container =>
                                                                   container.AddService(Singleton, typeof(IEdmModel), sp => model)
                                                                   .AddService(Singleton, typeof(IODataPathHandler), sp => pathHandler)
                                                                   .AddService(Singleton, typeof(IEnumerable <IODataRoutingConvention>), NewRoutingConventions)
                                                                   .AddService(Singleton, typeof(ODataBatchHandler), sp => batchHandler));
            var rootContainer   = perRouteContainer.CreateODataRootContainer(routeName, configureAction);
            var router          = rootContainer.GetService <IRouter>() ?? builder.DefaultHandler;
            var routeConstraint = new VersionedODataPathRouteConstraint(routeName, apiVersion);
            var route           = new ODataRoute(router, routeName, routePrefix.RemoveTrailingSlash(), routeConstraint, inlineConstraintResolver);

            builder.ConfigureBatchHandler(rootContainer, route);
            builder.Routes.Add(route);
            routeCollection.Add(new ODataRouteMapping(route, apiVersion, rootContainer));
            builder.AddRouteToRespondWithBadRequestWhenAtLeastOneRouteCouldMatch(routeName, routePrefix, apiVersion, inlineConstraintResolver);
            NotifyRoutesMapped();

            return(route);
        }
Esempio n. 2
0
        /// <summary>
        /// Maps the specified OData route and the OData route attributes.
        /// </summary>
        /// <param name="builder">The extended <see cref="IRouteBuilder">route builder</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="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 IRouteBuilder builder,
            string routeName,
            string routePrefix,
            ApiVersion apiVersion,
            Action <IContainerBuilder> configureAction,
            Action <ODataConventionConfigurationContext> configureRoutingConventions)
        {
            Arg.NotNull(builder, nameof(builder));
            Arg.NotNullOrEmpty(routeName, nameof(routeName));
            Arg.NotNull(apiVersion, nameof(apiVersion));
            Contract.Ensures(Contract.Result <ODataRoute>() != null);

            IEnumerable <IODataRoutingConvention> NewRoutingConventions(IServiceProvider serviceProvider)
            {
                var model = serviceProvider.GetRequiredService <IEdmModel>();
                var routingConventions = VersionedODataRoutingConventions.CreateDefault();
                var context            = new ODataConventionConfigurationContext(routeName, model, apiVersion, routingConventions);

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

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

            var routeCollection          = builder.ServiceProvider.GetRequiredService <IODataRouteCollectionProvider>();
            var perRouteContainer        = builder.ServiceProvider.GetRequiredService <IPerRouteContainer>();
            var inlineConstraintResolver = builder.ServiceProvider.GetRequiredService <IInlineConstraintResolver>();
            var preConfigureAction       = builder.ConfigureDefaultServices(
                container =>
            {
                container.AddService(Singleton, typeof(IEnumerable <IODataRoutingConvention>), NewRoutingConventions);
                configureAction?.Invoke(container);
            });
            var rootContainer = perRouteContainer.CreateODataRootContainer(routeName, preConfigureAction);
            var router        = rootContainer.GetService <IRouter>() ?? builder.DefaultHandler;

            builder.ConfigurePathHandler(rootContainer);

            var routeConstraint = new VersionedODataPathRouteConstraint(routeName, apiVersion);
            var route           = new ODataRoute(router, routeName, routePrefix.RemoveTrailingSlash(), routeConstraint, inlineConstraintResolver);

            builder.ConfigureBatchHandler(rootContainer, route);
            builder.Routes.Add(route);
            routeCollection.Add(new ODataRouteMapping(route, apiVersion, rootContainer));
            builder.AddRouteToRespondWithBadRequestWhenAtLeastOneRouteCouldMatch(routeName, routePrefix, apiVersion, inlineConstraintResolver);
            NotifyRoutesMapped();

            return(route);
        }
Esempio n. 3
0
        /// <summary>
        /// Maps the specified versioned OData routes. When the <paramref name="batchHandler"/> is provided, it will create a '$batch' endpoint to handle the batch requests.
        /// </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="pathHandler">The <see cref="IODataPathHandler">OData path handler</see> to use for parsing the OData path.</param>
        /// <param name="routingConventions">The <see cref="IEnumerable{T}">sequence</see> of <see cref="IODataRoutingConvention">OData routing conventions</see>
        /// to use for controller and action selection.</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,
            IODataPathHandler pathHandler,
            IEnumerable <IODataRoutingConvention> routingConventions,
            ODataBatchHandler?batchHandler)
        {
            if (configuration == null)
            {
                throw new ArgumentNullException(nameof(configuration));
            }

            if (models == null)
            {
                throw new ArgumentNullException(nameof(models));
            }

            var routeConventions     = VersionedODataRoutingConventions.AddOrUpdate(routingConventions.ToList());
            var routes               = configuration.Routes;
            var unversionedRouteName = routeName + UnversionedRouteSuffix;

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

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

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

            routeConventions.Insert(0, default !);
Esempio n. 4
0
 /// <summary>
 /// Maps the specified versioned OData routes. When the <paramref name="batchHandler"/> is provided, it will create a
 /// '$batch' endpoint to handle the batch requests.
 /// </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="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,
     ODataBatchHandler?batchHandler) =>
 MapVersionedODataRoutes(configuration, routeName, routePrefix, models, new DefaultODataPathHandler(), VersionedODataRoutingConventions.CreateDefault(), batchHandler);
Esempio n. 5
0
        /// <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)
        {
            if (configuration == null)
            {
                throw new ArgumentNullException(nameof(configuration));
            }

            if (models == null)
            {
                throw new ArgumentNullException(nameof(models));
            }

            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 annotation         = model.GetAnnotationValue <ApiVersionAnnotation>(model) ?? throw new InvalidOperationException(LocalSR.MissingAnnotation.FormatDefault(typeof(ApiVersionAnnotation).Name));
                var apiVersion         = annotation.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);
        }
Esempio n. 6
0
        /// <summary>
        /// Maps the specified versioned OData routes.
        /// </summary>
        /// <param name="builder">The extended <see cref="IRouteBuilder">route builder</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>
        /// <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 IRouteBuilder builder,
            string routeName,
            string routePrefix,
            IEnumerable <IEdmModel> models,
            Action <IContainerBuilder> configureAction,
            Action <ODataConventionConfigurationContext> configureRoutingConventions)
        {
            Arg.NotNull(builder, nameof(builder));
            Arg.NotNullOrEmpty(routeName, nameof(routeName));
            Arg.NotNull(models, nameof(models));
            Contract.Ensures(Contract.Result <IReadOnlyList <ODataRoute> >() != null);

            IEnumerable <IODataRoutingConvention> ConfigureRoutingConventions(IEdmModel model, string versionedRouteName, ApiVersion apiVersion)
            {
                var routingConventions = VersionedODataRoutingConventions.CreateDefault();
                var context            = new ODataConventionConfigurationContext(versionedRouteName, model, apiVersion, routingConventions);

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

                return(context.RoutingConventions);
            }

            builder.EnsureMetadataController();

            var routeCollection          = builder.ServiceProvider.GetRequiredService <IODataRouteCollectionProvider>();
            var perRouteContainer        = builder.ServiceProvider.GetRequiredService <IPerRouteContainer>();
            var options                  = builder.ServiceProvider.GetRequiredService <ODataOptions>();
            var inlineConstraintResolver = builder.ServiceProvider.GetRequiredService <IInlineConstraintResolver>();
            var routes                 = builder.Routes;
            var odataRoutes            = new List <ODataRoute>();
            var unversionedConstraints = new List <IRouteConstraint>();

            foreach (var model in models)
            {
                var versionedRouteName = routeName;
                var apiVersion         = model.GetAnnotationValue <ApiVersionAnnotation>(model)?.ApiVersion;
                var routeConstraint    = MakeVersionedODataRouteConstraint(apiVersion, ref versionedRouteName);
                var preConfigureAction = builder.ConfigureDefaultServices(
                    container =>
                {
                    container.AddService(Singleton, typeof(IEdmModel), sp => model)
                    .AddService(Singleton, typeof(IEnumerable <IODataRoutingConvention>), sp => ConfigureRoutingConventions(model, versionedRouteName, apiVersion));
                    configureAction?.Invoke(container);
                });
                var rootContainer = perRouteContainer.CreateODataRootContainer(versionedRouteName, preConfigureAction);
                var router        = rootContainer.GetService <IRouter>() ?? builder.DefaultHandler;

                rootContainer.ConfigurePathHandler(options);

                var route = new ODataRoute(router, versionedRouteName, routePrefix.RemoveTrailingSlash(), routeConstraint, inlineConstraintResolver);

                unversionedConstraints.Add(new ODataPathRouteConstraint(versionedRouteName));
                builder.ConfigureBatchHandler(rootContainer, route);
                routes.Add(route);
                odataRoutes.Add(route);
                routeCollection.Add(new ODataRouteMapping(route, apiVersion, rootContainer));
            }

            builder.AddRouteToRespondWithBadRequestWhenAtLeastOneRouteCouldMatch(routeName, routePrefix, unversionedConstraints, inlineConstraintResolver);
            NotifyRoutesMapped();

            return(odataRoutes);
        }
Esempio n. 7
0
 /// <summary>
 /// Maps a versioned OData route.
 /// </summary>
 /// <param name="builder">The extended <see cref="IRouteBuilder">route builder</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="model">The <see cref="IEdmModel">EDM model</see> to use for parsing OData paths.</param>
 /// <param name="apiVersion">The <see cref="ApiVersion">API version</see> associated with the model.</param>
 /// <param name="batchHandler">The <see cref="ODataBatchHandler">OData batch handler</see>.</param>
 /// <returns>The mapped <see cref="ODataRoute">OData route</see>.</returns>
 /// <remarks>The <see cref="ApiVersionAnnotation">API version annotation</see> will be added or updated on the specified <paramref name="model"/> using
 /// the provided <paramref name="apiVersion">API version</paramref>.</remarks>
 public static ODataRoute MapVersionedODataRoute(
     this IRouteBuilder builder,
     string routeName,
     string routePrefix,
     IEdmModel model,
     ApiVersion apiVersion,
     ODataBatchHandler batchHandler) =>
 MapVersionedODataRoute(builder, routeName, routePrefix, model, apiVersion, new DefaultODataPathHandler(), VersionedODataRoutingConventions.CreateDefault(), batchHandler);
Esempio n. 8
0
 /// <summary>
 /// Maps the specified versioned OData routes. When the <paramref name="newBatchHandler"/> is provided, it will create a
 /// '$batch' endpoint to handle the batch requests.
 /// </summary>
 /// <param name="builder">The extended <see cref="IRouteBuilder">route builder</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="newBatchHandler">The <see cref="Func{TResult}">factory method</see> used to create new <see cref="ODataBatchHandler">OData batch handlers</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 IRouteBuilder builder,
     string routeName,
     string routePrefix,
     IEnumerable <IEdmModel> models,
     Func <ODataBatchHandler> newBatchHandler) =>
 MapVersionedODataRoutes(builder, routeName, routePrefix, models, new DefaultODataPathHandler(), VersionedODataRoutingConventions.CreateDefault(), newBatchHandler);
Esempio n. 9
0
        /// <summary>
        /// Maps the specified versioned OData routes. When the <paramref name="newBatchHandler"/> is provided, it will create a '$batch' endpoint to handle the batch requests.
        /// </summary>
        /// <param name="builder">The extended <see cref="IRouteBuilder">route builder</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="pathHandler">The <see cref="IODataPathHandler">OData path handler</see> to use for parsing the OData path.</param>
        /// <param name="routingConventions">The <see cref="IEnumerable{T}">sequence</see> of <see cref="IODataRoutingConvention">OData routing conventions</see>
        /// to use for controller and action selection.</param>
        /// <param name="newBatchHandler">The <see cref="Func{TResult}">factory method</see> used to create new <see cref="ODataBatchHandler">OData batch handlers</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 IRouteBuilder builder,
            string routeName,
            string routePrefix,
            IEnumerable <IEdmModel> models,
            IODataPathHandler pathHandler,
            IEnumerable <IODataRoutingConvention> routingConventions,
            Func <ODataBatchHandler> newBatchHandler)
        {
            Arg.NotNull(builder, nameof(builder));
            Arg.NotNullOrEmpty(routeName, nameof(routeName));
            Arg.NotNull(models, nameof(models));
            Contract.Ensures(Contract.Result <IReadOnlyList <ODataRoute> >() != null);

            var serviceProvider          = builder.ServiceProvider;
            var options                  = serviceProvider.GetRequiredService <ODataOptions>();
            var routeCollection          = serviceProvider.GetRequiredService <IODataRouteCollectionProvider>();
            var inlineConstraintResolver = serviceProvider.GetRequiredService <IInlineConstraintResolver>();
            var routeConventions         = VersionedODataRoutingConventions.AddOrUpdate(routingConventions.ToList());
            var routes                 = builder.Routes;
            var perRouteContainer      = serviceProvider.GetRequiredService <IPerRouteContainer>();
            var odataRoutes            = new List <ODataRoute>();
            var unversionedConstraints = new List <IRouteConstraint>();

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

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

                IEnumerable <IODataRoutingConvention> NewRouteConventions(IServiceProvider services)
                {
                    var conventions = new IODataRoutingConvention[routeConventions.Count + 1];

                    conventions[0] = new VersionedAttributeRoutingConvention(versionedRouteName, serviceProvider, apiVersion);
                    routeConventions.CopyTo(conventions, 1);
                    return(conventions);
                }

                var edm             = model;
                var batchHandler    = newBatchHandler?.Invoke();
                var configureAction = builder.ConfigureDefaultServices(container =>
                                                                       container.AddService(Singleton, typeof(IEdmModel), sp => edm)
                                                                       .AddService(Singleton, typeof(IODataPathHandler), sp => pathHandler)
                                                                       .AddService(Singleton, typeof(IEnumerable <IODataRoutingConvention>), NewRouteConventions)
                                                                       .AddService(Singleton, typeof(ODataBatchHandler), sp => batchHandler));
                var rootContainer = perRouteContainer.CreateODataRootContainer(versionedRouteName, configureAction);
                var router        = rootContainer.GetService <IRouter>() ?? builder.DefaultHandler;
                var route         = new ODataRoute(router, versionedRouteName, routePrefix.RemoveTrailingSlash(), routeConstraint, inlineConstraintResolver);

                unversionedConstraints.Add(new ODataPathRouteConstraint(versionedRouteName));
                builder.ConfigureBatchHandler(batchHandler, route);
                routes.Add(route);
                odataRoutes.Add(route);
                routeCollection.Add(new ODataRouteMapping(route, apiVersion, rootContainer));
            }

            builder.AddRouteToRespondWithBadRequestWhenAtLeastOneRouteCouldMatch(routeName, routePrefix, unversionedConstraints, inlineConstraintResolver);
            NotifyRoutesMapped();

            return(odataRoutes);
        }
Esempio n. 10
0
        /// <summary>
        /// Maps the specified versioned OData routes. When the <paramref name="newBatchHandler"/> is provided, it will create a '$batch' endpoint to handle the batch requests.
        /// </summary>
        /// <param name="builder">The extended <see cref="IRouteBuilder">route builder</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="pathHandler">The <see cref="IODataPathHandler">OData path handler</see> to use for parsing the OData path.</param>
        /// <param name="routingConventions">The <see cref="IEnumerable{T}">sequence</see> of <see cref="IODataRoutingConvention">OData routing conventions</see>
        /// to use for controller and action selection.</param>
        /// <param name="newBatchHandler">The <see cref="Func{TResult}">factory method</see> used to create new <see cref="ODataBatchHandler">OData batch handlers</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 IRouteBuilder builder,
            string routeName,
            string routePrefix,
            IEnumerable <IEdmModel> models,
            IODataPathHandler pathHandler,
            IEnumerable <IODataRoutingConvention> routingConventions,
            Func <ODataBatchHandler>?newBatchHandler)
        {
            if (builder == null)
            {
                throw new ArgumentNullException(nameof(builder));
            }

            if (IsNullOrEmpty(routeName))
            {
                throw new ArgumentNullException(nameof(routeName));
            }

            if (models == null)
            {
                throw new ArgumentNullException(nameof(models));
            }

            var serviceProvider          = builder.ServiceProvider;
            var options                  = serviceProvider.GetRequiredService <ODataOptions>();
            var routeCollection          = serviceProvider.GetRequiredService <IODataRouteCollectionProvider>();
            var inlineConstraintResolver = serviceProvider.GetRequiredService <IInlineConstraintResolver>();
            var routeConventions         = VersionedODataRoutingConventions.AddOrUpdate(routingConventions.ToList());
            var routes                 = builder.Routes;
            var perRouteContainer      = serviceProvider.GetRequiredService <IPerRouteContainer>();
            var odataRoutes            = new List <ODataRoute>();
            var unversionedConstraints = new List <IRouteConstraint>();

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

            foreach (var model in models)
            {
                var versionedRouteName = routeName;
                var annotation         = model.GetAnnotationValue <ApiVersionAnnotation>(model) ?? throw new ArgumentException(LocalSR.MissingAnnotation.FormatDefault(typeof(ApiVersionAnnotation).Name));
                var apiVersion         = annotation.ApiVersion;
                var routeConstraint    = MakeVersionedODataRouteConstraint(apiVersion, ref versionedRouteName);

                IEnumerable <IODataRoutingConvention> NewRouteConventions(IServiceProvider services)
                {
                    var conventions = new IODataRoutingConvention[routeConventions !.Count + 1];

                    conventions[0] = new VersionedAttributeRoutingConvention(versionedRouteName !, serviceProvider !, apiVersion !);
                    routeConventions.CopyTo(conventions, 1);
                    return(conventions);
                }

                var edm             = model;
                var batchHandler    = newBatchHandler?.Invoke();
                var configureAction = builder.ConfigureDefaultServices(container =>
                                                                       container.AddService(Singleton, typeof(IEdmModel), sp => edm)
                                                                       .AddService(Singleton, typeof(IODataPathHandler), sp => pathHandler)
                                                                       .AddService(Singleton, typeof(IEnumerable <IODataRoutingConvention>), NewRouteConventions)
                                                                       .AddService(Singleton, typeof(ODataBatchHandler), sp => batchHandler));
                var rootContainer = perRouteContainer.CreateODataRootContainer(versionedRouteName, configureAction);
                var router        = rootContainer.GetService <IRouter>() ?? builder.DefaultHandler;
                var route         = new ODataRoute(router, versionedRouteName, routePrefix.RemoveTrailingSlash(), routeConstraint, inlineConstraintResolver);

                unversionedConstraints.Add(new ODataPathRouteConstraint(versionedRouteName));
                builder.ConfigureBatchHandler(batchHandler, route);
                routes.Add(route);
                odataRoutes.Add(route);
                routeCollection.Add(new ODataRouteMapping(route, apiVersion, rootContainer));
            }
        static ODataRoute MapVersionedODataRoute(
            HttpConfiguration configuration,
            string routeName,
            string routePrefix,
            IEdmModel model,
            ApiVersion apiVersion,
            IODataPathHandler pathHandler,
            IEnumerable <IODataRoutingConvention> routingConventions,
            ODataBatchHandler batchHandler,
            HttpMessageHandler defaultHandler)
        {
            Arg.NotNull(configuration, nameof(configuration));
            Arg.NotNullOrEmpty(routeName, nameof(routeName));
            Arg.NotNull(model, nameof(model));
            Arg.NotNull(apiVersion, nameof(apiVersion));
            Contract.Ensures(Contract.Result <ODataRoute>() != null);

            var routeConventions = VersionedODataRoutingConventions.AddOrUpdate(routingConventions.ToList());
            var routes           = configuration.Routes;

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

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

            model.SetAnnotationValue(model, new ApiVersionAnnotation(apiVersion));
            routeConventions.Insert(0, new VersionedAttributeRoutingConvention(routeName, configuration, apiVersion));

            var rootContainer = configuration.CreateODataRootContainer(
                routeName,
                builder => builder.AddService(Singleton, typeof(IEdmModel), sp => model)
                .AddService(Singleton, typeof(IODataPathHandler), sp => pathHandler)
                .AddService(Singleton, typeof(IEnumerable <IODataRoutingConvention>), sp => routeConventions.ToArray())
                .AddService(Singleton, typeof(ODataBatchHandler), sp => batchHandler)
                .AddService(Singleton, typeof(HttpMessageHandler), sp => defaultHandler));

            rootContainer.InitializeAttributeRouting();

            var routeConstraint = new VersionedODataPathRouteConstraint(routeName, apiVersion);
            var route           = default(ODataRoute);
            var options         = configuration.GetApiVersioningOptions();

            if (defaultHandler != null)
            {
                route = new ODataRoute(routePrefix, routeConstraint, defaults: null, constraints: null, dataTokens: null, handler: defaultHandler);
            }
            else
            {
                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);
            routes.Add(routeName + UnversionedRouteSuffix, unversionedRoute);

            return(route);
        }
 /// <summary>
 /// Maps the specified OData route and the OData route attributes. When the <paramref name="defaultHandler"/>
 /// is non-<c>null</c>, it will map it as the default handler for the route.
 /// </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="model">The EDM model to use for parsing OData paths.</param>
 /// <param name="apiVersion">The <see cref="ApiVersion">API version</see> associated with the model.</param>
 /// <param name="defaultHandler">The default <see cref="HttpMessageHandler"/> for this route.</param>
 /// <returns>The added <see cref="ODataRoute"/>.</returns>
 public static ODataRoute MapVersionedODataRoute(
     this HttpConfiguration configuration,
     string routeName,
     string routePrefix,
     IEdmModel model,
     ApiVersion apiVersion,
     HttpMessageHandler defaultHandler) =>
 MapVersionedODataRoute(configuration, routeName, routePrefix, model, apiVersion, new DefaultODataPathHandler(), VersionedODataRoutingConventions.CreateDefault(), default, defaultHandler);
        /// <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. When the <paramref name="batchHandler"/> is provided, it will create a '$batch' endpoint to handle the batch requests.
        /// </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="pathHandler">The <see cref="IODataPathHandler">OData path handler</see> to use for parsing the OData path.</param>
        /// <param name="routingConventions">The <see cref="IEnumerable{T}">sequence</see> of <see cref="IODataRoutingConvention">OData routing conventions</see>
        /// to use for controller and action selection.</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,
            IODataPathHandler pathHandler,
            IEnumerable <IODataRoutingConvention> routingConventions,
            ODataBatchHandler batchHandler)
        {
            Arg.NotNull(configuration, nameof(configuration));
            Arg.NotNullOrEmpty(routeName, nameof(routeName));
            Arg.NotNull(models, nameof(models));
            Contract.Ensures(Contract.Result <IReadOnlyList <ODataRoute> >() != null);

            var routeConventions     = VersionedODataRoutingConventions.AddOrUpdate(routingConventions.ToList());
            var routes               = configuration.Routes;
            var unversionedRouteName = routeName + UnversionedRouteSuffix;

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

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

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

            routeConventions.Insert(0, null);

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

                routeConventions[0] = new VersionedAttributeRoutingConvention(versionedRouteName, configuration, apiVersion);
                unversionedConstraints.Add(new ODataPathRouteConstraint(versionedRouteName));

                var edm           = model;
                var rootContainer = configuration.CreateODataRootContainer(
                    versionedRouteName,
                    builder => builder.AddService(Singleton, typeof(IEdmModel), sp => edm)
                    .AddService(Singleton, typeof(IODataPathHandler), sp => pathHandler)
                    .AddService(Singleton, typeof(IEnumerable <IODataRoutingConvention>), sp => routeConventions.ToArray())
                    .AddService(Singleton, typeof(ODataBatchHandler), sp => batchHandler));

                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, _ => { });

            return(odataRoutes);
        }