/// <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); }
/// <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); }
/// <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 !);
/// <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);
/// <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); }
/// <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); }
/// <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);
/// <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);
/// <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); }
/// <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); }