diff --git a/samples/webapi/AdvancedODataWebApiSample/AdvancedODataWebApiSample.csproj b/samples/webapi/AdvancedODataWebApiSample/AdvancedODataWebApiSample.csproj index 7e644568..058d391e 100644 --- a/samples/webapi/AdvancedODataWebApiSample/AdvancedODataWebApiSample.csproj +++ b/samples/webapi/AdvancedODataWebApiSample/AdvancedODataWebApiSample.csproj @@ -48,13 +48,17 @@ True - - ..\..\..\packages\Microsoft.OData.Core.6.15.0\lib\portable-net45+win+wpa81\Microsoft.OData.Core.dll - True + + ..\..\..\packages\Microsoft.Extensions.DependencyInjection.1.0.0\lib\netstandard1.1\Microsoft.Extensions.DependencyInjection.dll - - ..\..\..\packages\Microsoft.OData.Edm.6.15.0\lib\portable-net45+win+wpa81\Microsoft.OData.Edm.dll - True + + ..\..\..\packages\Microsoft.Extensions.DependencyInjection.Abstractions.1.0.0\lib\netstandard1.0\Microsoft.Extensions.DependencyInjection.Abstractions.dll + + + ..\..\..\packages\Microsoft.OData.Core.7.0.0\lib\portable-net45+win8+wpa81\Microsoft.OData.Core.dll + + + ..\..\..\packages\Microsoft.OData.Edm.7.0.0\lib\portable-net45+win8+wpa81\Microsoft.OData.Edm.dll ..\..\..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll @@ -64,9 +68,8 @@ ..\..\..\packages\Microsoft.Owin.Host.SystemWeb.3.0.1\lib\net45\Microsoft.Owin.Host.SystemWeb.dll True - - ..\..\..\packages\Microsoft.Spatial.6.15.0\lib\portable-net45+win+wpa81\Microsoft.Spatial.dll - True + + ..\..\..\packages\Microsoft.Spatial.7.0.0\lib\portable-net45+win8+wpa81\Microsoft.Spatial.dll ..\..\..\packages\Newtonsoft.Json.6.0.4\lib\net45\Newtonsoft.Json.dll @@ -98,9 +101,8 @@ ..\..\..\packages\Microsoft.AspNet.WebApi.Owin.5.2.3\lib\net45\System.Web.Http.Owin.dll True - - ..\..\..\packages\Microsoft.AspNet.OData.5.9.1\lib\net45\System.Web.OData.dll - True + + ..\..\..\packages\Microsoft.AspNet.OData.6.0.0\lib\net45\System.Web.OData.dll @@ -129,6 +131,7 @@ + diff --git a/samples/webapi/AdvancedODataWebApiSample/CaseInsensitiveODataUriResolver.cs b/samples/webapi/AdvancedODataWebApiSample/CaseInsensitiveODataUriResolver.cs new file mode 100644 index 00000000..113b81fa --- /dev/null +++ b/samples/webapi/AdvancedODataWebApiSample/CaseInsensitiveODataUriResolver.cs @@ -0,0 +1,12 @@ +namespace Microsoft.Examples +{ + using Microsoft.OData.UriParser; + using System; + + // HACK: required due to bug in ODL + // REF: https://github.com/OData/odata.net/issues/695 + public sealed class CaseInsensitiveODataUriResolver : UnqualifiedODataUriResolver + { + public override bool EnableCaseInsensitive { get { return true; } set { } } + } +} \ No newline at end of file diff --git a/samples/webapi/AdvancedODataWebApiSample/Startup.cs b/samples/webapi/AdvancedODataWebApiSample/Startup.cs index ef3e728c..84ab63ac 100644 --- a/samples/webapi/AdvancedODataWebApiSample/Startup.cs +++ b/samples/webapi/AdvancedODataWebApiSample/Startup.cs @@ -4,12 +4,14 @@ namespace Microsoft.Examples { using Configuration; using global::Owin; + using Microsoft.OData; + using Microsoft.OData.UriParser; using Microsoft.Web.Http.Versioning; using Microsoft.Web.OData.Builder; using System.Web.Http; using System.Web.OData.Batch; using System.Web.OData.Builder; - using System.Web.OData.Extensions; + using static Microsoft.OData.ServiceLifetime; using static System.Web.Http.RouteParameter; public class Startup @@ -28,8 +30,6 @@ public void Configuration( IAppBuilder appBuilder ) new QueryStringApiVersionReader(), new HeaderApiVersionReader( "api-version", "x-ms-version" ) ); } ); - configuration.EnableCaseInsensitive( true ); - configuration.EnableUnqualifiedNameCall( true ); var modelBuilder = new VersionedODataModelBuilder( configuration ) { @@ -43,9 +43,14 @@ public void Configuration( IAppBuilder appBuilder ) var models = modelBuilder.GetEdmModels(); var batchHandler = new DefaultODataBatchHandler( httpServer ); - configuration.MapVersionedODataRoutes( "odata", "api", models, batchHandler ); + configuration.MapVersionedODataRoutes( "odata", "api", models, ConfigureODataServices, batchHandler ); configuration.Routes.MapHttpRoute( "orders", "api/{controller}/{id}", new { id = Optional } ); appBuilder.UseWebApi( httpServer ); } + + static void ConfigureODataServices( IContainerBuilder builder ) + { + builder.AddService( Singleton, typeof( ODataUriResolver ), sp => new CaseInsensitiveODataUriResolver() ); + } } } \ No newline at end of file diff --git a/samples/webapi/AdvancedODataWebApiSample/packages.config b/samples/webapi/AdvancedODataWebApiSample/packages.config index 2275831d..6b4617b1 100644 --- a/samples/webapi/AdvancedODataWebApiSample/packages.config +++ b/samples/webapi/AdvancedODataWebApiSample/packages.config @@ -1,16 +1,30 @@  - + + + - - + + - + + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/webapi/BasicODataWebApiSample/BasicODataWebApiSample.csproj b/samples/webapi/BasicODataWebApiSample/BasicODataWebApiSample.csproj index 675e6e3d..92f50292 100644 --- a/samples/webapi/BasicODataWebApiSample/BasicODataWebApiSample.csproj +++ b/samples/webapi/BasicODataWebApiSample/BasicODataWebApiSample.csproj @@ -51,13 +51,17 @@ True - - ..\..\..\packages\Microsoft.OData.Core.6.15.0\lib\portable-net45+win+wpa81\Microsoft.OData.Core.dll - True + + ..\..\..\packages\Microsoft.Extensions.DependencyInjection.1.0.0\lib\netstandard1.1\Microsoft.Extensions.DependencyInjection.dll - - ..\..\..\packages\Microsoft.OData.Edm.6.15.0\lib\portable-net45+win+wpa81\Microsoft.OData.Edm.dll - True + + ..\..\..\packages\Microsoft.Extensions.DependencyInjection.Abstractions.1.0.0\lib\netstandard1.0\Microsoft.Extensions.DependencyInjection.Abstractions.dll + + + ..\..\..\packages\Microsoft.OData.Core.7.0.0\lib\portable-net45+win8+wpa81\Microsoft.OData.Core.dll + + + ..\..\..\packages\Microsoft.OData.Edm.7.0.0\lib\portable-net45+win8+wpa81\Microsoft.OData.Edm.dll ..\..\..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll @@ -67,9 +71,8 @@ ..\..\..\packages\Microsoft.Owin.Host.SystemWeb.3.0.1\lib\net45\Microsoft.Owin.Host.SystemWeb.dll True - - ..\..\..\packages\Microsoft.Spatial.6.15.0\lib\portable-net45+win+wpa81\Microsoft.Spatial.dll - True + + ..\..\..\packages\Microsoft.Spatial.7.0.0\lib\portable-net45+win8+wpa81\Microsoft.Spatial.dll ..\..\..\packages\Newtonsoft.Json.6.0.4\lib\net45\Newtonsoft.Json.dll @@ -101,9 +104,8 @@ ..\..\..\packages\Microsoft.AspNet.WebApi.Owin.5.2.3\lib\net45\System.Web.Http.Owin.dll True - - ..\..\..\packages\Microsoft.AspNet.OData.5.9.1\lib\net45\System.Web.OData.dll - True + + ..\..\..\packages\Microsoft.AspNet.OData.6.0.0\lib\net45\System.Web.OData.dll @@ -126,6 +128,7 @@ + diff --git a/samples/webapi/BasicODataWebApiSample/CaseInsensitiveODataUriResolver.cs b/samples/webapi/BasicODataWebApiSample/CaseInsensitiveODataUriResolver.cs new file mode 100644 index 00000000..113b81fa --- /dev/null +++ b/samples/webapi/BasicODataWebApiSample/CaseInsensitiveODataUriResolver.cs @@ -0,0 +1,12 @@ +namespace Microsoft.Examples +{ + using Microsoft.OData.UriParser; + using System; + + // HACK: required due to bug in ODL + // REF: https://github.com/OData/odata.net/issues/695 + public sealed class CaseInsensitiveODataUriResolver : UnqualifiedODataUriResolver + { + public override bool EnableCaseInsensitive { get { return true; } set { } } + } +} \ No newline at end of file diff --git a/samples/webapi/BasicODataWebApiSample/Startup.cs b/samples/webapi/BasicODataWebApiSample/Startup.cs index 8aae1b74..77353fde 100644 --- a/samples/webapi/BasicODataWebApiSample/Startup.cs +++ b/samples/webapi/BasicODataWebApiSample/Startup.cs @@ -4,11 +4,13 @@ namespace Microsoft.Examples { using Configuration; using global::Owin; + using Microsoft.OData; + using Microsoft.OData.UriParser; using Microsoft.Web.OData.Builder; using System.Web.Http; using System.Web.OData.Batch; using System.Web.OData.Builder; - using System.Web.OData.Extensions; + using static Microsoft.OData.ServiceLifetime; public class Startup { @@ -19,8 +21,6 @@ public void Configuration( IAppBuilder appBuilder ) // reporting api versions will return the headers "api-supported-versions" and "api-deprecated-versions" configuration.AddApiVersioning( o => o.ReportApiVersions = true ); - configuration.EnableCaseInsensitive( true ); - configuration.EnableUnqualifiedNameCall( true ); var modelBuilder = new VersionedODataModelBuilder( configuration ) { @@ -34,9 +34,14 @@ public void Configuration( IAppBuilder appBuilder ) var models = modelBuilder.GetEdmModels(); var batchHandler = new DefaultODataBatchHandler( httpServer ); - configuration.MapVersionedODataRoutes( "odata", "api", models, batchHandler ); - configuration.MapVersionedODataRoutes( "odata-bypath", "v{apiVersion}", models, batchHandler ); + configuration.MapVersionedODataRoutes( "odata", "api", models, ConfigureODataServices, batchHandler ); + configuration.MapVersionedODataRoutes( "odata-bypath", "v{apiVersion}", models, ConfigureODataServices ); appBuilder.UseWebApi( httpServer ); } + + static void ConfigureODataServices( IContainerBuilder builder ) + { + builder.AddService( Singleton, typeof( ODataUriResolver ), sp => new CaseInsensitiveODataUriResolver() ); + } } } \ No newline at end of file diff --git a/samples/webapi/BasicODataWebApiSample/packages.config b/samples/webapi/BasicODataWebApiSample/packages.config index 2275831d..6b4617b1 100644 --- a/samples/webapi/BasicODataWebApiSample/packages.config +++ b/samples/webapi/BasicODataWebApiSample/packages.config @@ -1,16 +1,30 @@  - + + + - - + + - + + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/webapi/ConventionsODataWebApiSample/CaseInsensitiveODataUriResolver.cs b/samples/webapi/ConventionsODataWebApiSample/CaseInsensitiveODataUriResolver.cs new file mode 100644 index 00000000..113b81fa --- /dev/null +++ b/samples/webapi/ConventionsODataWebApiSample/CaseInsensitiveODataUriResolver.cs @@ -0,0 +1,12 @@ +namespace Microsoft.Examples +{ + using Microsoft.OData.UriParser; + using System; + + // HACK: required due to bug in ODL + // REF: https://github.com/OData/odata.net/issues/695 + public sealed class CaseInsensitiveODataUriResolver : UnqualifiedODataUriResolver + { + public override bool EnableCaseInsensitive { get { return true; } set { } } + } +} \ No newline at end of file diff --git a/samples/webapi/ConventionsODataWebApiSample/ConventionsODataWebApiSample.csproj b/samples/webapi/ConventionsODataWebApiSample/ConventionsODataWebApiSample.csproj index dce94d47..634f1d12 100644 --- a/samples/webapi/ConventionsODataWebApiSample/ConventionsODataWebApiSample.csproj +++ b/samples/webapi/ConventionsODataWebApiSample/ConventionsODataWebApiSample.csproj @@ -48,13 +48,17 @@ True - - ..\..\..\packages\Microsoft.OData.Core.6.15.0\lib\portable-net45+win+wpa81\Microsoft.OData.Core.dll - True + + ..\..\..\packages\Microsoft.Extensions.DependencyInjection.1.0.0\lib\netstandard1.1\Microsoft.Extensions.DependencyInjection.dll - - ..\..\..\packages\Microsoft.OData.Edm.6.15.0\lib\portable-net45+win+wpa81\Microsoft.OData.Edm.dll - True + + ..\..\..\packages\Microsoft.Extensions.DependencyInjection.Abstractions.1.0.0\lib\netstandard1.0\Microsoft.Extensions.DependencyInjection.Abstractions.dll + + + ..\..\..\packages\Microsoft.OData.Core.7.0.0\lib\portable-net45+win8+wpa81\Microsoft.OData.Core.dll + + + ..\..\..\packages\Microsoft.OData.Edm.7.0.0\lib\portable-net45+win8+wpa81\Microsoft.OData.Edm.dll ..\..\..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll @@ -64,9 +68,8 @@ ..\..\..\packages\Microsoft.Owin.Host.SystemWeb.3.0.1\lib\net45\Microsoft.Owin.Host.SystemWeb.dll True - - ..\..\..\packages\Microsoft.Spatial.6.15.0\lib\portable-net45+win+wpa81\Microsoft.Spatial.dll - True + + ..\..\..\packages\Microsoft.Spatial.7.0.0\lib\portable-net45+win8+wpa81\Microsoft.Spatial.dll ..\..\..\packages\Newtonsoft.Json.6.0.4\lib\net45\Newtonsoft.Json.dll @@ -98,9 +101,8 @@ ..\..\..\packages\Microsoft.AspNet.WebApi.Owin.5.2.3\lib\net45\System.Web.Http.Owin.dll True - - ..\..\..\packages\Microsoft.AspNet.OData.5.9.1\lib\net45\System.Web.OData.dll - True + + ..\..\..\packages\Microsoft.AspNet.OData.6.0.0\lib\net45\System.Web.OData.dll @@ -123,6 +125,7 @@ + diff --git a/samples/webapi/ConventionsODataWebApiSample/Startup.cs b/samples/webapi/ConventionsODataWebApiSample/Startup.cs index c0742bfc..bdd44d48 100644 --- a/samples/webapi/ConventionsODataWebApiSample/Startup.cs +++ b/samples/webapi/ConventionsODataWebApiSample/Startup.cs @@ -5,12 +5,14 @@ namespace Microsoft.Examples using Configuration; using Controllers; using global::Owin; + using Microsoft.OData; + using Microsoft.OData.UriParser; using Microsoft.Web.Http.Versioning.Conventions; using Microsoft.Web.OData.Builder; using System.Web.Http; using System.Web.OData.Batch; using System.Web.OData.Builder; - using System.Web.OData.Extensions; + using static Microsoft.OData.ServiceLifetime; public class Startup { @@ -37,8 +39,6 @@ public void Configuration( IAppBuilder appBuilder ) options.Conventions.Controller() .HasApiVersion( 3, 0 ); } ); - configuration.EnableCaseInsensitive( true ); - configuration.EnableUnqualifiedNameCall( true ); var modelBuilder = new VersionedODataModelBuilder( configuration ) { @@ -52,9 +52,14 @@ public void Configuration( IAppBuilder appBuilder ) var models = modelBuilder.GetEdmModels(); var batchHandler = new DefaultODataBatchHandler( httpServer ); - configuration.MapVersionedODataRoutes( "odata", "api", models, batchHandler ); - configuration.MapVersionedODataRoutes( "odata-bypath", "v{apiVersion}", models, batchHandler ); + configuration.MapVersionedODataRoutes( "odata", "api", models, ConfigureODataServices, batchHandler ); + configuration.MapVersionedODataRoutes( "odata-bypath", "v{apiVersion}", models, ConfigureODataServices ); appBuilder.UseWebApi( httpServer ); } + + static void ConfigureODataServices( IContainerBuilder builder ) + { + builder.AddService( Singleton, typeof( ODataUriResolver ), sp => new CaseInsensitiveODataUriResolver() ); + } } } \ No newline at end of file diff --git a/samples/webapi/ConventionsODataWebApiSample/packages.config b/samples/webapi/ConventionsODataWebApiSample/packages.config index 2275831d..6b4617b1 100644 --- a/samples/webapi/ConventionsODataWebApiSample/packages.config +++ b/samples/webapi/ConventionsODataWebApiSample/packages.config @@ -1,16 +1,30 @@  - + + + - - + + - + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Microsoft.AspNet.OData.Versioning/Microsoft.AspNet.OData.Versioning.csproj b/src/Microsoft.AspNet.OData.Versioning/Microsoft.AspNet.OData.Versioning.csproj index 91cfc19d..5548d60f 100644 --- a/src/Microsoft.AspNet.OData.Versioning/Microsoft.AspNet.OData.Versioning.csproj +++ b/src/Microsoft.AspNet.OData.Versioning/Microsoft.AspNet.OData.Versioning.csproj @@ -17,7 +17,7 @@ - + @@ -25,6 +25,21 @@ + + + SR.resx + True + True + + + + + + SR.Designer.cs + ResXFileCodeGenerator + + + diff --git a/src/Microsoft.AspNet.OData.Versioning/SR.Designer.cs b/src/Microsoft.AspNet.OData.Versioning/SR.Designer.cs new file mode 100644 index 00000000..e9ce6a22 --- /dev/null +++ b/src/Microsoft.AspNet.OData.Versioning/SR.Designer.cs @@ -0,0 +1,81 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Microsoft { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class SR { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal SR() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.SR", typeof(SR).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to The container built by the container builder must not be null.. + /// + internal static string NullContainer { + get { + return ResourceManager.GetString("NullContainer", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The container builder created by the container builder factory must not be null.. + /// + internal static string NullContainerBuilder { + get { + return ResourceManager.GetString("NullContainerBuilder", resourceCulture); + } + } + } +} diff --git a/src/Microsoft.AspNet.OData.Versioning/SR.resx b/src/Microsoft.AspNet.OData.Versioning/SR.resx new file mode 100644 index 00000000..db2fc33b --- /dev/null +++ b/src/Microsoft.AspNet.OData.Versioning/SR.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + The container built by the container builder must not be null. + + + The container builder created by the container builder factory must not be null. + + \ No newline at end of file diff --git a/src/Microsoft.AspNet.OData.Versioning/System.Web.Http/HttpConfigurationExtensions.cs b/src/Microsoft.AspNet.OData.Versioning/System.Web.Http/HttpConfigurationExtensions.cs index 81840004..9df1f684 100644 --- a/src/Microsoft.AspNet.OData.Versioning/System.Web.Http/HttpConfigurationExtensions.cs +++ b/src/Microsoft.AspNet.OData.Versioning/System.Web.Http/HttpConfigurationExtensions.cs @@ -6,6 +6,8 @@ using Diagnostics.Contracts; using Linq; using Microsoft; + using Microsoft.Extensions.DependencyInjection; + using Microsoft.OData; using Microsoft.OData.Edm; using Microsoft.Web.Http; using Microsoft.Web.Http.Routing; @@ -16,7 +18,10 @@ using OData.Routing; using OData.Routing.Conventions; using Routing; - using static Linq.Expressions.Expression; + using System.Collections.Concurrent; + using System.Net.Http; + using System.Web.OData; + using static Microsoft.OData.ServiceLifetime; using static System.String; using static System.StringComparison; @@ -25,86 +30,132 @@ /// public static class HttpConfigurationExtensions { - const string ResolverSettingsKey = "System.Web.OData.ResolverSettingsKey"; + const string ContainerBuilderFactoryKey = "System.Web.OData.ContainerBuilderFactoryKey"; + const string RootContainerMappingsKey = "System.Web.OData.RootContainerMappingsKey"; + const string UrlKeyDelimiterKey = "System.Web.OData.UrlKeyDelimiterKey"; const string UnversionedRouteSuffix = "-Unversioned"; const string ApiVersionConstraintName = "apiVersion"; const string ApiVersionConstraint = "{" + ApiVersionConstraintName + "}"; - static readonly Lazy> setResolverSettings = new Lazy>( GetResolverSettingsMutator ); - static Action GetResolverSettingsMutator() - { - Contract.Ensures( Contract.Result>() != null ); - - // build a strong-typed delegate to the DefaultODataPathHandler.ResolverSettings property mutator - var handlerType = typeof( DefaultODataPathHandler ); - var resolverSettingsType = handlerType.Assembly.GetType( "System.Web.OData.ODataUriResolverSetttings" ); - var h = Parameter( handlerType, "h" ); - var rs = Parameter( typeof( object ), "rs" ); - var property = Property( h, "ResolverSetttings" ); - var body = Assign( property, Convert( rs, resolverSettingsType ) ); - var lambda = Lambda>( body, h, rs ); - var action = lambda.Compile(); - - return action; - } + /// + /// Maps the specified versioned OData routes. + /// + /// The extended HTTP configuration. + /// The name of the route to map. + /// The prefix to add to the OData route's path template. + /// The sequence of EDM models to use for parsing OData paths. + /// The configuring action to add the services to the root container. + /// The read-only list of added OData routes. + /// The specified must contain the API version annotation. This annotation is + /// automatically applied when you use the and call to + /// create the . + public static IReadOnlyList MapVersionedODataRoutes( + this HttpConfiguration configuration, + string routeName, + string routePrefix, + IEnumerable models, + Action configureAction ) => + MapVersionedODataRoutes( configuration, routeName, routePrefix, models, configureAction, null ); - static void SetResolverSettings( this HttpConfiguration configuration, IODataPathHandler pathHandler ) + /// + /// Maps the specified versioned OData routes. + /// + /// The extended HTTP configuration. + /// The name of the route to map. + /// The prefix to add to the OData route's path template. + /// The sequence of EDM models to use for parsing OData paths. + /// The configuring action to add the services to the root container. + /// The OData batch handler. + /// The read-only list of added OData routes. + /// The specified must contain the API version annotation. This annotation is + /// automatically applied when you use the and call to + /// create the . + public static IReadOnlyList MapVersionedODataRoutes( + this HttpConfiguration configuration, + string routeName, + string routePrefix, + IEnumerable models, + Action configureAction, + ODataBatchHandler batchHandler ) { - Contract.Requires( configuration != null ); + Arg.NotNull( configuration, nameof( configuration ) ); + Arg.NotNull( models, nameof( models ) ); + Contract.Ensures( Contract.Result>() != null ); - // REMARKS: the DefaultODataPathHandler.ResolverSettings property is internal as is the ODataUriResolverSetttings class. - // The MapODataServiceRoute normally hooks this up, but we are replacing that process. in order to retain functional - // fidelity we'll build and compile a strong-typed delegate that can be used to set the property. - // - // in additional, the ODataUriResolverSetttings are created lazy-initialized from the property bag. instead of using - // Reflection, we'll test for the known key. if the key is not present, we'll use a public extension method - // (e.g. EnableCaseInsensitive) with the default, unconfigured value. this will trigger the creation of the - // settings and populate the property. + object ConfigureRoutingConventions( IEdmModel model, string versionedRouteName, ApiVersion apiVersion ) + { + var routingConventions = EnsureConventions( ODataRoutingConventions.CreateDefault() ); - var handler = pathHandler as DefaultODataPathHandler; + model.SetAnnotationValue( model, new ApiVersionAnnotation( apiVersion ) ); + routingConventions.Insert( 0, new VersionedAttributeRoutingConvention( versionedRouteName, configuration, apiVersion ) ); - if ( handler == null ) - { - return; + return routingConventions.ToArray(); } - // REMARKS: this creates and populates the ODataUriResolverSetttings; OData URLs are case-sensitive by default. - if ( !configuration.Properties.ContainsKey( ResolverSettingsKey ) ) + if ( !IsNullOrEmpty( routePrefix ) ) { - configuration.EnableCaseInsensitive( false ); + routePrefix = routePrefix.TrimEnd( '/' ); } - setResolverSettings.Value( handler, configuration.Properties[ResolverSettingsKey] ); - } + var routes = configuration.Routes; + var unversionedRouteName = routeName + UnversionedRouteSuffix; - static IList EnsureConventions( IList conventions ) - { - Contract.Requires( conventions != null ); - Contract.Ensures( Contract.Result>() != null ); + if ( batchHandler != null ) + { + batchHandler.ODataRouteName = unversionedRouteName; + var batchTemplate = IsNullOrEmpty( routePrefix ) ? ODataRouteConstants.Batch : routePrefix + '/' + ODataRouteConstants.Batch; + routes.MapHttpBatchRoute( routeName + nameof( ODataRouteConstants.Batch ), batchTemplate, batchHandler ); + } - var discovered = new BitVector32( 0 ); + var odataRoutes = new List(); + var unversionedConstraints = new List(); - for ( var i = 0; i < conventions.Count; i++ ) + foreach ( var model in models ) { - var convention = conventions[i]; + var versionedRouteName = routeName; + var apiVersion = model.GetAnnotationValue( model )?.ApiVersion; + var routeConstraint = MakeVersionedODataRouteConstraint( apiVersion, ref versionedRouteName ); - if ( convention is MetadataRoutingConvention ) + unversionedConstraints.Add( new ODataPathRouteConstraint( versionedRouteName ) ); + + var rootContainer = configuration.CreateODataRootContainer( + versionedRouteName, + builder => + { + builder.AddService( Singleton, typeof( IEdmModel ), sp => model ) + .AddService( Singleton, typeof( IEnumerable ), sp => ConfigureRoutingConventions( model, versionedRouteName, apiVersion ) ); + configureAction?.Invoke( builder ); + } ); + + var pathHandler = rootContainer.GetRequiredService(); + + if ( pathHandler != null && pathHandler.UrlKeyDelimiter == null ) { - conventions[i] = new VersionedMetadataRoutingConvention(); - discovered[1] = true; + pathHandler.UrlKeyDelimiter = configuration.GetUrlKeyDelimiter(); } - else if ( convention is VersionedMetadataRoutingConvention ) + + rootContainer.InitializeAttributeRouting(); + + var route = default( ODataRoute ); + var messageHandler = rootContainer.GetService(); + + if ( messageHandler == null ) { - discovered[1] = true; + route = new ODataRoute( routePrefix, routeConstraint ); + } + else + { + route = new ODataRoute( routePrefix, routeConstraint, defaults: null, constraints: null, dataTokens: null, handler: messageHandler ); } - } - if ( !discovered[1] ) - { - conventions.Insert( 0, new VersionedMetadataRoutingConvention() ); + routes.Add( versionedRouteName, route ); + AddApiVersionConstraintIfNecessary( route ); + odataRoutes.Add( route ); } - return conventions; + configuration.AddRouteToRespondWithBadRequestWhenAtLeastOneRouteCouldMatch( unversionedRouteName, routePrefix, odataRoutes, unversionedConstraints, configureAction ); + + return odataRoutes; } /// @@ -119,7 +170,7 @@ static IList EnsureConventions( IList and call to /// create the . public static IReadOnlyList MapVersionedODataRoutes( this HttpConfiguration configuration, string routeName, string routePrefix, IEnumerable models ) => - MapVersionedODataRoutes( configuration, routeName, routePrefix, models, null ); + MapVersionedODataRoutes( configuration, routeName, routePrefix, models, new DefaultODataPathHandler(), ODataRoutingConventions.CreateDefault(), null ); /// /// Maps the specified versioned OData routes. When the is provided, it will create a @@ -198,6 +249,7 @@ public static IReadOnlyList MapVersionedODataRoutes( var routeConventions = EnsureConventions( routingConventions.ToList() ); var routes = configuration.Routes; + var unversionedRouteName = routeName + UnversionedRouteSuffix; if ( !IsNullOrEmpty( routePrefix ) ) { @@ -206,11 +258,16 @@ public static IReadOnlyList MapVersionedODataRoutes( if ( batchHandler != null ) { + batchHandler.ODataRouteName = unversionedRouteName; var batchTemplate = IsNullOrEmpty( routePrefix ) ? ODataRouteConstants.Batch : routePrefix + '/' + ODataRouteConstants.Batch; - routes.MapHttpBatchRoute( routeName + "Batch", batchTemplate, batchHandler ); + routes.MapHttpBatchRoute( routeName + nameof( ODataRouteConstants.Batch ), batchTemplate, batchHandler ); + } + + if ( pathHandler != null && pathHandler.UrlKeyDelimiter == null ) + { + pathHandler.UrlKeyDelimiter = configuration.GetUrlKeyDelimiter(); } - configuration.SetResolverSettings( pathHandler ); routeConventions.Insert( 0, null ); var odataRoutes = new List(); @@ -219,25 +276,132 @@ public static IReadOnlyList MapVersionedODataRoutes( foreach ( var model in models ) { var versionedRouteName = routeName; - var routeConstraint = default( ODataPathRouteConstraint ); + var apiVersion = model.GetAnnotationValue( model )?.ApiVersion; + var routeConstraint = MakeVersionedODataRouteConstraint( apiVersion, ref versionedRouteName ); - routeConventions[0] = new VersionedAttributeRoutingConvention( model, configuration ); - routeConstraint = new ODataPathRouteConstraint( pathHandler, model, versionedRouteName, routeConventions.ToArray() ); - unversionedConstraints.Add( routeConstraint ); - routeConstraint = MakeVersionedODataRouteConstraint( routeConstraint, pathHandler, routeConventions, model, ref versionedRouteName ); + routeConventions[0] = new VersionedAttributeRoutingConvention( versionedRouteName, configuration, apiVersion ); + unversionedConstraints.Add( new ODataPathRouteConstraint( versionedRouteName ) ); - var route = new ODataRoute( routePrefix, routeConstraint ); + var rootContainer = configuration.CreateODataRootContainer( + versionedRouteName, + builder => builder.AddService( Singleton, typeof( IEdmModel ), sp => model ) + .AddService( Singleton, typeof( IODataPathHandler ), sp => pathHandler ) + .AddService( Singleton, typeof( IEnumerable ), sp => routeConventions.ToArray() ) + .AddService( Singleton, typeof( ODataBatchHandler ), sp => batchHandler ) ); + + rootContainer.InitializeAttributeRouting(); + + var route = default( ODataRoute ); + var messageHandler = rootContainer.GetService(); + + + if ( messageHandler == null ) + { + route = new ODataRoute( routePrefix, routeConstraint ); + } + else + { + route = new ODataRoute( routePrefix, routeConstraint, defaults: null, constraints: null, dataTokens: null, handler: messageHandler ); + } - AddApiVersionConstraintIfNecessary( route ); routes.Add( versionedRouteName, route ); + AddApiVersionConstraintIfNecessary( route ); odataRoutes.Add( route ); } - AddRouteToRespondWithBadRequestWhenAtLeastOneRouteCouldMatch( routeName, routePrefix, routes, odataRoutes, unversionedConstraints ); + configuration.AddRouteToRespondWithBadRequestWhenAtLeastOneRouteCouldMatch( unversionedRouteName, routePrefix, odataRoutes, unversionedConstraints, _ => { } ); return odataRoutes; } + /// + /// Maps the specified OData route and the OData route attributes. + /// + /// The server configuration. + /// The name of the route to map. + /// The prefix to add to the OData route's path template. + /// The API version associated with the model. + /// The configuring action to add the services to the root container. + /// The added . + public static ODataRoute MapVersionedODataRoute( this HttpConfiguration configuration, string routeName, string routePrefix, ApiVersion apiVersion, Action configureAction ) + { + Arg.NotNull( configuration, nameof( configuration ) ); + Arg.NotNull( apiVersion, nameof( apiVersion ) ); + Contract.Ensures( Contract.Result() != null ); + + object ConfigureRoutingConventions( IServiceProvider serviceProvider ) + { + var model = serviceProvider.GetRequiredService(); + var routingConventions = EnsureConventions( ODataRoutingConventions.CreateDefault() ); + + model.SetAnnotationValue( model, new ApiVersionAnnotation( apiVersion ) ); + routingConventions.Insert( 0, new VersionedAttributeRoutingConvention( routeName, configuration, apiVersion ) ); + + return routingConventions.ToArray(); + } + + if ( !IsNullOrEmpty( routePrefix ) ) + { + routePrefix = routePrefix.TrimEnd( '/' ); + } + + var rootContainer = configuration.CreateODataRootContainer( + routeName, + builder => + { + builder.AddService( Singleton, typeof( IEnumerable ), ConfigureRoutingConventions ); + configureAction?.Invoke( builder ); + } ); + var pathHandler = rootContainer.GetRequiredService(); + + 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(); + + if ( messageHandler != null ) + { + route = new ODataRoute( + routePrefix, + routeConstraint, + defaults: null, + constraints: null, + dataTokens: null, + handler: messageHandler ); + } + else + { + var batchHandler = rootContainer.GetService(); + + 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 ); + + var unversionedRouteConstraint = new ODataPathRouteConstraint( routeName ); + var unversionedRoute = new ODataRoute( routePrefix, new UnversionedODataPathRouteConstraint( unversionedRouteConstraint, apiVersion ) ); + + AddApiVersionConstraintIfNecessary( unversionedRoute ); + configuration.Routes.Add( routeName + UnversionedRouteSuffix, unversionedRoute ); + + return route; + } + /// /// Maps a versioned OData route. /// @@ -250,7 +414,7 @@ public static IReadOnlyList MapVersionedODataRoutes( /// The API version annotation will be added or updated on the specified using /// the provided API version. public static ODataRoute MapVersionedODataRoute( this HttpConfiguration configuration, string routeName, string routePrefix, IEdmModel model, ApiVersion apiVersion ) => - MapVersionedODataRoute( configuration, routeName, routePrefix, model, apiVersion, new DefaultODataPathHandler(), ODataRoutingConventions.CreateDefault(), null ); + MapVersionedODataRoute( configuration, routeName, routePrefix, model, apiVersion, new DefaultODataPathHandler(), ODataRoutingConventions.CreateDefault(), null, null ); /// /// Maps a versioned OData route. @@ -271,7 +435,27 @@ public static ODataRoute MapVersionedODataRoute( IEdmModel model, ApiVersion apiVersion, ODataBatchHandler batchHandler ) => - MapVersionedODataRoute( configuration, routeName, routePrefix, model, apiVersion, new DefaultODataPathHandler(), ODataRoutingConventions.CreateDefault(), batchHandler ); + MapVersionedODataRoute( configuration, routeName, routePrefix, model, apiVersion, new DefaultODataPathHandler(), ODataRoutingConventions.CreateDefault(), batchHandler, null ); + + /// + /// Maps the specified OData route and the OData route attributes. When the + /// is non-null, it will map it as the default handler for the route. + /// + /// The server configuration. + /// The name of the route to map. + /// The prefix to add to the OData route's path template. + /// The EDM model to use for parsing OData paths. + /// The API version associated with the model. + /// The default for this route. + /// The added . + 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(), ODataRoutingConventions.CreateDefault(), null, defaultHandler ); /// /// Maps a versioned OData route. @@ -295,7 +479,7 @@ public static ODataRoute MapVersionedODataRoute( ApiVersion apiVersion, IODataPathHandler pathHandler, IEnumerable routingConventions ) => - MapVersionedODataRoute( configuration, routeName, routePrefix, model, apiVersion, pathHandler, routingConventions, null ); + MapVersionedODataRoute( configuration, routeName, routePrefix, model, apiVersion, pathHandler, routingConventions, null, null ); /// /// Maps a versioned OData route. When the is provided, it will create a '$batch' endpoint to handle the batch requests. @@ -322,7 +506,45 @@ public static ODataRoute MapVersionedODataRoute( ApiVersion apiVersion, IODataPathHandler pathHandler, IEnumerable routingConventions, - ODataBatchHandler batchHandler ) + ODataBatchHandler batchHandler ) => + MapVersionedODataRoute( configuration, routeName, routePrefix, model, apiVersion, pathHandler, routingConventions, batchHandler, null ); + + /// + /// Maps the specified OData route. When the is non-null, it will map + /// it as the handler for the route. + /// + /// The server configuration. + /// The name of the route to map. + /// The prefix to add to the OData route's path template. + /// The EDM model to use for parsing OData paths. + /// The API version associated with the model. + /// The to use for parsing the OData path. + /// The OData routing conventions to use for controller and action selection. + /// The default for this route. + /// The added . + public static ODataRoute MapVersionedODataRoute( + this HttpConfiguration configuration, + string routeName, + string routePrefix, + IEdmModel model, + ApiVersion apiVersion, + IODataPathHandler pathHandler, + IEnumerable routingConventions, + HttpMessageHandler defaultHandler ) => + MapVersionedODataRoute( configuration, routeName, routePrefix, model, apiVersion, pathHandler, routingConventions, null, defaultHandler ); + + [SuppressMessage( "Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0", Justification = "Validated by a code contract." )] + [SuppressMessage( "Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "The specified handler must be the batch handler." )] + static ODataRoute MapVersionedODataRoute( + HttpConfiguration configuration, + string routeName, + string routePrefix, + IEdmModel model, + ApiVersion apiVersion, + IODataPathHandler pathHandler, + IEnumerable routingConventions, + ODataBatchHandler batchHandler, + HttpMessageHandler defaultHandler ) { Arg.NotNull( configuration, nameof( configuration ) ); Arg.NotNull( model, nameof( model ) ); @@ -337,23 +559,47 @@ public static ODataRoute MapVersionedODataRoute( routePrefix = routePrefix.TrimEnd( '/' ); } - if ( batchHandler != null ) + if ( pathHandler != null && pathHandler.UrlKeyDelimiter == null ) { - var batchTemplate = IsNullOrEmpty( routePrefix ) ? ODataRouteConstants.Batch : routePrefix + '/' + ODataRouteConstants.Batch; - routes.MapHttpBatchRoute( routeName + "Batch", batchTemplate, batchHandler ); + pathHandler.UrlKeyDelimiter = configuration.GetUrlKeyDelimiter(); } - configuration.SetResolverSettings( pathHandler ); model.SetAnnotationValue( model, new ApiVersionAnnotation( apiVersion ) ); - routeConventions.Insert( 0, new VersionedAttributeRoutingConvention( model, configuration ) ); + routeConventions.Insert( 0, new VersionedAttributeRoutingConvention( routeName, configuration, apiVersion ) ); - var routeConstraint = new VersionedODataPathRouteConstraint( pathHandler, model, routeName, routeConventions.ToArray(), apiVersion ); - var route = new ODataRoute( routePrefix, routeConstraint ); + var rootContainer = configuration.CreateODataRootContainer( + routeName, + builder => builder.AddService( Singleton, typeof( IEdmModel ), sp => model ) + .AddService( Singleton, typeof( IODataPathHandler ), sp => pathHandler ) + .AddService( Singleton, typeof( IEnumerable ), 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 ); + + 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 ); + } - AddApiVersionConstraintIfNecessary( route ); routes.Add( routeName, route ); + AddApiVersionConstraintIfNecessary( route ); - var unversionedRouteConstraint = new ODataPathRouteConstraint( pathHandler, model, routeName, routeConventions.ToArray() ); + var unversionedRouteConstraint = new ODataPathRouteConstraint( routeName ); var unversionedRoute = new ODataRoute( routePrefix, new UnversionedODataPathRouteConstraint( unversionedRouteConstraint, apiVersion ) ); AddApiVersionConstraintIfNecessary( unversionedRoute ); @@ -362,29 +608,48 @@ public static ODataRoute MapVersionedODataRoute( return route; } - static ODataPathRouteConstraint MakeVersionedODataRouteConstraint( - ODataPathRouteConstraint routeConstraint, - IODataPathHandler pathHandler, - IList routeConventions, - IEdmModel model, - ref string versionedRouteName ) + static IList EnsureConventions( IList conventions ) + { + Contract.Requires( conventions != null ); + Contract.Ensures( Contract.Result>() != null ); + + var discovered = new BitVector32( 0 ); + + for ( var i = 0; i < conventions.Count; i++ ) + { + var convention = conventions[i]; + + if ( convention is MetadataRoutingConvention ) + { + conventions[i] = new VersionedMetadataRoutingConvention(); + discovered[1] = true; + } + else if ( convention is VersionedMetadataRoutingConvention ) + { + discovered[1] = true; + } + } + + if ( !discovered[1] ) + { + conventions.Insert( 0, new VersionedMetadataRoutingConvention() ); + } + + return conventions; + } + + static ODataPathRouteConstraint MakeVersionedODataRouteConstraint( ApiVersion apiVersion, ref string versionedRouteName ) { - Contract.Requires( routeConstraint != null ); - Contract.Requires( pathHandler != null ); - Contract.Requires( routeConventions != null ); - Contract.Requires( model != null ); Contract.Requires( !IsNullOrEmpty( versionedRouteName ) ); Contract.Ensures( Contract.Result() != null ); - var apiVersion = model.GetAnnotationValue( model )?.ApiVersion; - if ( apiVersion == null ) { - return routeConstraint; + return new ODataPathRouteConstraint( versionedRouteName ); } versionedRouteName += "-" + apiVersion.ToString(); - return new VersionedODataPathRouteConstraint( pathHandler, model, versionedRouteName, routeConventions.ToArray(), apiVersion ); + return new VersionedODataPathRouteConstraint( versionedRouteName, apiVersion ); } static void AddApiVersionConstraintIfNecessary( ODataRoute route ) @@ -412,18 +677,96 @@ static void AddApiVersionConstraintIfNecessary( ODataRoute route ) } } - static void AddRouteToRespondWithBadRequestWhenAtLeastOneRouteCouldMatch( string routeName, string routePrefix, HttpRouteCollection routes, List odataRoutes, List unversionedConstraints ) + static void AddRouteToRespondWithBadRequestWhenAtLeastOneRouteCouldMatch( + this HttpConfiguration configuration, + string routeName, + string routePrefix, + List odataRoutes, + List unversionedConstraints, + Action configureAction ) { Contract.Requires( !IsNullOrEmpty( routeName ) ); - Contract.Requires( routes != null ); + Contract.Requires( configuration != null ); Contract.Requires( odataRoutes != null ); Contract.Requires( unversionedConstraints != null ); + Contract.Requires( configureAction != null ); var unversionedRoute = new ODataRoute( routePrefix, new UnversionedODataPathRouteConstraint( unversionedConstraints ) ); AddApiVersionConstraintIfNecessary( unversionedRoute ); - routes.Add( routeName + UnversionedRouteSuffix, unversionedRoute ); + configuration.Routes.Add( routeName, unversionedRoute ); odataRoutes.Add( unversionedRoute ); + configuration.CreateODataRootContainer( routeName, configureAction ); + } + + static ODataUrlKeyDelimiter GetUrlKeyDelimiter( this HttpConfiguration configuration ) + { + Contract.Requires( configuration != null ); + + if ( configuration.Properties.TryGetValue( UrlKeyDelimiterKey, out var value ) ) + { + return value as ODataUrlKeyDelimiter; + } + + configuration.Properties[UrlKeyDelimiterKey] = null; + return null; + } + + static IServiceProvider CreateODataRootContainer( this HttpConfiguration configuration, string routeName, Action configureAction ) + { + var rootContainer = configuration.CreateRootContainerImplementation( configureAction ); + configuration.SetODataRootContainer( routeName, rootContainer ); + return rootContainer; + } + + static void SetODataRootContainer( this HttpConfiguration configuration, string routeName, IServiceProvider rootContainer ) => + configuration.GetRootContainerMappings()[routeName] = rootContainer; + + static ConcurrentDictionary GetRootContainerMappings( this HttpConfiguration configuration ) => + (ConcurrentDictionary) configuration.Properties.GetOrAdd( RootContainerMappingsKey, key => new ConcurrentDictionary() ); + + static IServiceProvider CreateRootContainerImplementation( this HttpConfiguration configuration, Action configureAction ) + { + var builder = configuration.CreateContainerBuilderWithDefaultServices(); + + configureAction?.Invoke( builder ); + + var rootContainer = builder.BuildContainer(); + + if ( rootContainer == null ) + { + throw new InvalidOperationException( SR.NullContainer ); + } + + return rootContainer; + } + + static IContainerBuilder CreateContainerBuilderWithDefaultServices( this HttpConfiguration configuration ) + { + IContainerBuilder builder; + + if ( configuration.Properties.TryGetValue( ContainerBuilderFactoryKey, out var value ) ) + { + var builderFactory = (Func) value; + + builder = builderFactory(); + + if ( builder == null ) + { + throw new InvalidOperationException( SR.NullContainerBuilder ); + } + } + else + { + builder = new DefaultContainerBuilder(); + } + + builder.AddService( Singleton, sp => configuration ); + builder.AddService( Singleton, sp => configuration.GetDefaultQuerySettings() ); + builder.AddDefaultODataServices(); + builder.AddDefaultWebApiServices(); + + return builder; } } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.OData.Versioning/System.Web.Http/IContainerBuilderExtensions.cs b/src/Microsoft.AspNet.OData.Versioning/System.Web.Http/IContainerBuilderExtensions.cs new file mode 100644 index 00000000..b7ac2486 --- /dev/null +++ b/src/Microsoft.AspNet.OData.Versioning/System.Web.Http/IContainerBuilderExtensions.cs @@ -0,0 +1,97 @@ +namespace System.Web.Http +{ + using Microsoft.Extensions.DependencyInjection; + using Microsoft.OData; + using OData.Routing; + using System.Web.Http.Dispatcher; + using System.Web.OData.Formatter.Deserialization; + using System.Web.OData.Formatter.Serialization; + using System.Web.OData.Query; + using System.Web.OData.Query.Expressions; + using System.Web.OData.Query.Validators; + using System.Web.OData.Routing.Conventions; + using static Microsoft.OData.ServiceLifetime; + + internal static class IContainerBuilderExtensions + { + internal static void InitializeAttributeRouting( this IServiceProvider serviceProvider ) => serviceProvider.GetServices(); + + internal static void AddDefaultWebApiServices( this IContainerBuilder builder ) + { + builder.AddService( Singleton ); + AddServicePrototypes( builder ); + AddQueryValidators( builder ); + AddSerializationProviders( builder ); + AddDeserializerServices( builder ); + AddSerializerServices( builder ); + AddBinders( builder ); + builder.AddService( Singleton, sp => sp.GetService()?.Services.GetAssembliesResolver() ?? new DefaultAssembliesResolver() ); + } + + static void AddServicePrototypes( IContainerBuilder builder ) + { + builder.AddServicePrototype( new ODataMessageReaderSettings() + { + EnableMessageStreamDisposal = false, + MessageQuotas = new ODataMessageQuotas { MaxReceivedMessageSize = long.MaxValue }, + + } ); + + builder.AddServicePrototype( new ODataMessageWriterSettings + { + EnableMessageStreamDisposal = false, + MessageQuotas = new ODataMessageQuotas { MaxReceivedMessageSize = long.MaxValue }, + } ); + } + + static void AddQueryValidators( IContainerBuilder builder ) + { + builder.AddService( Singleton ); + builder.AddService( Singleton ); + builder.AddService( Singleton ); + builder.AddService( Singleton ); + builder.AddService( Singleton ); + builder.AddService( Singleton ); + builder.AddService( Singleton ); + } + + static void AddSerializationProviders( IContainerBuilder builder ) + { + builder.AddService( Singleton ); + builder.AddService( Singleton ); + } + + static void AddDeserializerServices( IContainerBuilder builder ) + { + builder.AddService( Singleton ); + builder.AddService( Singleton ); + builder.AddService( Singleton ); + builder.AddService( Singleton ); + builder.AddService( Singleton ); + builder.AddService( Singleton ); + builder.AddService( Singleton ); + } + + static void AddSerializerServices( IContainerBuilder builder ) + { + builder.AddService( Singleton ); + builder.AddService( Singleton ); + builder.AddService( Singleton ); + builder.AddService( Singleton ); + builder.AddService( Singleton ); + builder.AddService( Singleton ); + builder.AddService( Singleton ); + builder.AddService( Singleton ); + builder.AddService( Singleton ); + builder.AddService( Singleton ); + builder.AddService( Singleton ); + builder.AddService( Singleton ); + } + + static void AddBinders( IContainerBuilder builder ) + { + builder.AddService( Scoped ); + builder.AddService( Transient ); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.OData.Versioning/Web.OData/Controllers/VersionedMetadataController.cs b/src/Microsoft.AspNet.OData.Versioning/Web.OData/Controllers/VersionedMetadataController.cs index c59621ca..2b6928c3 100644 --- a/src/Microsoft.AspNet.OData.Versioning/Web.OData/Controllers/VersionedMetadataController.cs +++ b/src/Microsoft.AspNet.OData.Versioning/Web.OData/Controllers/VersionedMetadataController.cs @@ -4,9 +4,9 @@ using System.Net.Http; using System.Web.Http; using System.Web.OData; - using static Microsoft.OData.Core.ODataConstants; - using static Microsoft.OData.Core.ODataUtils; - using static Microsoft.OData.Core.ODataVersion; + using static Microsoft.OData.ODataConstants; + using static Microsoft.OData.ODataUtils; + using static Microsoft.OData.ODataVersion; using static System.Net.HttpStatusCode; using static System.String; diff --git a/src/Microsoft.AspNet.OData.Versioning/Web.OData/Routing/VersionedAttributeRoutingConvention.cs b/src/Microsoft.AspNet.OData.Versioning/Web.OData/Routing/VersionedAttributeRoutingConvention.cs index 672c3975..9e132989 100644 --- a/src/Microsoft.AspNet.OData.Versioning/Web.OData/Routing/VersionedAttributeRoutingConvention.cs +++ b/src/Microsoft.AspNet.OData.Versioning/Web.OData/Routing/VersionedAttributeRoutingConvention.cs @@ -1,6 +1,6 @@ namespace Microsoft.Web.OData.Routing { - using Microsoft.OData.Edm; + using Microsoft.Web.Http; using System.Collections.Generic; using System.Diagnostics.Contracts; using System.Linq; @@ -14,38 +14,62 @@ /// public class VersionedAttributeRoutingConvention : AttributeRoutingConvention { + readonly ApiVersion apiVersion; + /// /// Initializes a new instance of the class. /// - /// The EDM model associated with the routing convention. + /// The name of the route. /// The current HTTP configuration. - public VersionedAttributeRoutingConvention( IEdmModel model, HttpConfiguration configuration ) : base( model, configuration ) { } + /// The API version associated with the convention. + public VersionedAttributeRoutingConvention( string routeName, HttpConfiguration configuration, ApiVersion apiVersion ) + : base( routeName, configuration ) + { + Arg.NotNull( apiVersion, nameof( apiVersion ) ); + this.apiVersion = apiVersion; + } /// /// Initializes a new instance of the class. /// - /// The EDM model associated with the routing convention. + /// The name of the route. /// The current HTTP configuration. /// The OData path template handler associated with the routing convention. - public VersionedAttributeRoutingConvention( IEdmModel model, HttpConfiguration configuration, IODataPathTemplateHandler pathTemplateHandler ) - : base( model, configuration, pathTemplateHandler ) { } + /// The API version associated with the convention. + public VersionedAttributeRoutingConvention( string routeName, HttpConfiguration configuration, IODataPathTemplateHandler pathTemplateHandler, ApiVersion apiVersion ) + : base( routeName, configuration, pathTemplateHandler ) + { + Arg.NotNull( apiVersion, nameof( apiVersion ) ); + this.apiVersion = apiVersion; + } /// /// Initializes a new instance of the class. /// - /// The EDM model associated with the routing convention. + /// The name of the route. /// The sequence of controller descriptors - public VersionedAttributeRoutingConvention( IEdmModel model, IEnumerable controllers ) : base( model, controllers ) { } + /// The API version associated with the convention. + public VersionedAttributeRoutingConvention( string routeName, IEnumerable controllers, ApiVersion apiVersion ) + : base( routeName, controllers ) + { + Arg.NotNull( apiVersion, nameof( apiVersion ) ); + this.apiVersion = apiVersion; + } /// /// Initializes a new instance of the class. /// - /// The EDM model associated with the routing convention. + /// The name of the route. /// The sequence of controller descriptors /// associated with the routing convention. /// The OData path template handler associated with the routing convention. - public VersionedAttributeRoutingConvention( IEdmModel model, IEnumerable controllers, IODataPathTemplateHandler pathTemplateHandler ) - : base( model, controllers, pathTemplateHandler ) { } + /// The API version associated with the convention. + public VersionedAttributeRoutingConvention( string routeName, IEnumerable controllers, IODataPathTemplateHandler pathTemplateHandler, ApiVersion apiVersion ) + : base( routeName, controllers, pathTemplateHandler ) + { + Arg.NotNull( apiVersion, nameof( apiVersion ) ); + this.apiVersion = apiVersion; + } /// /// Returns a value indicating whether the specified controller should be mapped using attribute routing conventions. @@ -65,8 +89,6 @@ public override bool ShouldMapController( HttpControllerDescriptor controller ) return true; } - var apiVersion = Model.GetAnnotationValue( Model )?.ApiVersion; - return apiVersion != null && versionModel.DeclaredApiVersions.Contains( apiVersion ); } } diff --git a/src/Microsoft.AspNet.OData.Versioning/Web.OData/Routing/VersionedODataPathRouteConstraint.cs b/src/Microsoft.AspNet.OData.Versioning/Web.OData/Routing/VersionedODataPathRouteConstraint.cs index b604cb7d..dc76b4a4 100644 --- a/src/Microsoft.AspNet.OData.Versioning/Web.OData/Routing/VersionedODataPathRouteConstraint.cs +++ b/src/Microsoft.AspNet.OData.Versioning/Web.OData/Routing/VersionedODataPathRouteConstraint.cs @@ -2,8 +2,7 @@ { using Http; using Http.Versioning; - using Microsoft.OData.Core; - using Microsoft.OData.Edm; + using Microsoft.OData; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Diagnostics.Contracts; @@ -11,7 +10,6 @@ using System.Web.Http; using System.Web.Http.Routing; using System.Web.OData.Routing; - using System.Web.OData.Routing.Conventions; using static System.Net.HttpStatusCode; using static System.Web.Http.Routing.HttpRouteDirection; @@ -23,30 +21,14 @@ public class VersionedODataPathRouteConstraint : ODataPathRouteConstraint /// /// Initializes a new instance of the class. /// - /// The OData path handler to use for parsing. - /// The EDM model to use for parsing the path. /// The name of the route this constraint is associated with. - /// The OData routing conventions to use for selecting the controller name. /// The API version associated with the route constraint. - public VersionedODataPathRouteConstraint( - IODataPathHandler pathHandler, - IEdmModel model, - string routeName, - IEnumerable routingConventions, - ApiVersion apiVersion ) - : base( pathHandler, model, routeName, routingConventions ) + public VersionedODataPathRouteConstraint( string routeName, ApiVersion apiVersion ) : base( routeName ) { Arg.NotNull( apiVersion, nameof( apiVersion ) ); ApiVersion = apiVersion; } - static bool IsServiceDocumentOrMetadataRoute( IDictionary values ) - { - Contract.Requires( values != null ); - object value; - return values.TryGetValue( "odataPath", out value ) && ( value == null || Equals( value, "$metadata" ) ); - } - /// /// Gets the API version matched by the current OData path route constraint. /// @@ -103,6 +85,9 @@ public override bool Match( HttpRequestMessage request, IHttpRoute route, string return false; } + static bool IsServiceDocumentOrMetadataRoute( IDictionary values ) => + values.TryGetValue( "odataPath", out var value ) && ( value == null || Equals( value, "$metadata" ) ); + static ApiVersion GetRequestedApiVersionOrReturnBadRequest( HttpRequestMessage request, ApiVersionRequestProperties properties ) { Contract.Requires( request != null ); diff --git a/test/Microsoft.AspNet.OData.Versioning.Tests/Microsoft.AspNet.OData.Versioning.Tests.csproj b/test/Microsoft.AspNet.OData.Versioning.Tests/Microsoft.AspNet.OData.Versioning.Tests.csproj index f846c3f4..8b556869 100644 --- a/test/Microsoft.AspNet.OData.Versioning.Tests/Microsoft.AspNet.OData.Versioning.Tests.csproj +++ b/test/Microsoft.AspNet.OData.Versioning.Tests/Microsoft.AspNet.OData.Versioning.Tests.csproj @@ -3,6 +3,7 @@ net452 Microsoft.AspNet.OData.Versioning.Tests + Microsoft diff --git a/test/Microsoft.AspNet.OData.Versioning.Tests/System.Web.OData/HttpConfigurationExtensionsTest.cs b/test/Microsoft.AspNet.OData.Versioning.Tests/System.Web.OData/HttpConfigurationExtensionsTest.cs index c0b84d62..5f6229cf 100644 --- a/test/Microsoft.AspNet.OData.Versioning.Tests/System.Web.OData/HttpConfigurationExtensionsTest.cs +++ b/test/Microsoft.AspNet.OData.Versioning.Tests/System.Web.OData/HttpConfigurationExtensionsTest.cs @@ -13,29 +13,21 @@ using Microsoft.Web.OData.Routing; using Moq; using Routing.Conventions; + using System.Collections.Concurrent; + using System.Web.Http.Routing; + using System.Web.OData.Routing; using Xunit; public class HttpConfigurationExtensionsTest { + const string RootContainerMappingsKey = "System.Web.OData.RootContainerMappingsKey"; + [ApiVersion( "1.0" )] sealed class ControllerV1 : ODataController { } [ApiVersion( "2.0" )] sealed class ControllerV2 : ODataController { } - static IEnumerable CreateModels( HttpConfiguration configuration ) - { - var controllerTypeResolver = new Mock(); - var controllerTypes = new List() { typeof( ControllerV1 ), typeof( ControllerV2 ) }; - - controllerTypeResolver.Setup( ctr => ctr.GetControllerTypes( It.IsAny() ) ).Returns( controllerTypes ); - configuration.Services.Replace( typeof( IHttpControllerTypeResolver ), controllerTypeResolver.Object ); - - var builder = new VersionedODataModelBuilder( configuration ); - - return builder.GetEdmModels(); - } - [Fact] public void map_versioned_odata_routes_should_return_expected_result() { @@ -50,13 +42,14 @@ public void map_versioned_odata_routes_should_return_expected_result() // act var route = configuration.MapVersionedODataRoute( routeName, routePrefix, model, apiVersion, batchHandler ); - var constraint = (VersionedODataPathRouteConstraint) route.PathRouteConstraint; + var constraint = route.PathRouteConstraint; + var routingConventions = GetRoutingConventions( configuration, route ); var batchRoute = configuration.Routes["odataBatch"]; // assert - constraint.RoutingConventions[0].Should().BeOfType(); - constraint.RoutingConventions[1].Should().BeOfType(); - constraint.RoutingConventions.OfType().Should().BeEmpty(); + routingConventions[0].Should().BeOfType(); + routingConventions[1].Should().BeOfType(); + routingConventions.OfType().Should().BeEmpty(); constraint.RouteName.Should().Be( routeName ); route.RoutePrefix.Should().Be( routePrefix ); batchRoute.Handler.Should().Be( batchHandler ); @@ -88,12 +81,13 @@ public void map_versioned_odata_routes_should_return_expected_results() continue; } - var apiVersion = constraint.EdmModel.GetAnnotationValue( constraint.EdmModel ).ApiVersion; + var apiVersion = constraint.ApiVersion; + var routingConventions = GetRoutingConventions( configuration, route ); var versionedRouteName = routeName + "-" + apiVersion.ToString(); - constraint.RoutingConventions[0].Should().BeOfType(); - constraint.RoutingConventions[1].Should().BeOfType(); - constraint.RoutingConventions.OfType().Should().BeEmpty(); + routingConventions[0].Should().BeOfType(); + routingConventions[1].Should().BeOfType(); + routingConventions.OfType().Should().BeEmpty(); constraint.RouteName.Should().Be( versionedRouteName ); route.RoutePrefix.Should().Be( routePrefix ); } @@ -101,5 +95,32 @@ public void map_versioned_odata_routes_should_return_expected_results() batchRoute.Handler.Should().Be( batchHandler ); batchRoute.RouteTemplate.Should().Be( "api/$batch" ); } + + static IEnumerable CreateModels( HttpConfiguration configuration ) + { + var controllerTypeResolver = new Mock(); + var controllerTypes = new List() { typeof( ControllerV1 ), typeof( ControllerV2 ) }; + + controllerTypeResolver.Setup( ctr => ctr.GetControllerTypes( It.IsAny() ) ).Returns( controllerTypes ); + configuration.Services.Replace( typeof( IHttpControllerTypeResolver ), controllerTypeResolver.Object ); + + var builder = new VersionedODataModelBuilder( configuration ); + + return builder.GetEdmModels(); + } + + static IReadOnlyList GetRoutingConventions( HttpConfiguration configuration, ODataRoute route ) + { + var routes = configuration.Routes; + var pairs = new KeyValuePair[routes.Count]; + + routes.CopyTo( pairs, 0 ); + + var key = pairs.Single( p => p.Value == route ).Key; + var serviceProviders = (ConcurrentDictionary) configuration.Properties[RootContainerMappingsKey]; + var routingConventions = (IEnumerable) serviceProviders[key].GetService( typeof( IEnumerable ) ); + + return routingConventions.ToArray(); + } } } \ No newline at end of file diff --git a/test/Microsoft.AspNet.OData.Versioning.Tests/Test.cs b/test/Microsoft.AspNet.OData.Versioning.Tests/Test.cs new file mode 100644 index 00000000..6cc35f95 --- /dev/null +++ b/test/Microsoft.AspNet.OData.Versioning.Tests/Test.cs @@ -0,0 +1,22 @@ +namespace Microsoft +{ + using Microsoft.OData.Edm; + using System; + using System.Web.OData.Builder; + + internal static class Test + { + static Test() + { + var builder = new ODataModelBuilder(); + var tests = builder.EntitySet( "Tests" ).EntityType; + + tests.HasKey( t => t.Id ); + Model = builder.GetEdmModel(); + } + + internal static IEdmModel Model { get; } + + internal static IEdmModel EmptyModel { get; } = new ODataModelBuilder().GetEdmModel(); + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.OData.Versioning.Tests/TestEntity.cs b/test/Microsoft.AspNet.OData.Versioning.Tests/TestEntity.cs new file mode 100644 index 00000000..4391cb2c --- /dev/null +++ b/test/Microsoft.AspNet.OData.Versioning.Tests/TestEntity.cs @@ -0,0 +1,9 @@ +namespace Microsoft +{ + using System; + + public class TestEntity + { + public int Id { get; set; } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.OData.Versioning.Tests/Web.OData/Routing/VersionedAttributeRoutingConventionTest.cs b/test/Microsoft.AspNet.OData.Versioning.Tests/Web.OData/Routing/VersionedAttributeRoutingConventionTest.cs index 846188e0..2de2993b 100644 --- a/test/Microsoft.AspNet.OData.Versioning.Tests/Web.OData/Routing/VersionedAttributeRoutingConventionTest.cs +++ b/test/Microsoft.AspNet.OData.Versioning.Tests/Web.OData/Routing/VersionedAttributeRoutingConventionTest.cs @@ -12,7 +12,6 @@ using System.Web.Http.Controllers; using System.Web.Http.Dispatcher; using System.Web.OData; - using System.Web.OData.Builder; using Xunit; public class VersionedAttributeRoutingConventionTest @@ -40,12 +39,9 @@ static IEdmModel CreateModel( HttpConfiguration configuration, Type controllerTy public void should_map_controller_should_return_true_for_versionX2Dneutral_controller() { // arrange - var model = new ODataModelBuilder().GetEdmModel(); - var controller = new HttpControllerDescriptor( new HttpConfiguration(), string.Empty, typeof( NeutralController ) ); - var convention = new VersionedAttributeRoutingConvention( model, new HttpControllerDescriptor[0] ); - var annotation = new ApiVersionAnnotation( new ApiVersion( 1, 0 ) ); - - model.SetAnnotationValue( model, annotation ); + var configuration = new HttpConfiguration(); + var controller = new HttpControllerDescriptor( configuration, string.Empty, typeof( NeutralController ) ); + var convention = new VersionedAttributeRoutingConvention( "Tests", configuration, new ApiVersion( 1, 0 ) ); // act var result = convention.ShouldMapController( controller ); @@ -60,12 +56,9 @@ public void should_map_controller_should_return_true_for_versionX2Dneutral_contr public void should_map_controller_should_return_expected_result_for_controller_version( int majorVersion, bool expected ) { // arrange - var model = new ODataModelBuilder().GetEdmModel(); - var controller = new HttpControllerDescriptor( new HttpConfiguration(), string.Empty, typeof( ControllerV1 ) ); - var convention = new VersionedAttributeRoutingConvention( model, new HttpControllerDescriptor[0] ); - var annotation = new ApiVersionAnnotation( new ApiVersion( majorVersion, 0 ) ); - - model.SetAnnotationValue( model, annotation ); + var configuration = new HttpConfiguration(); + var controller = new HttpControllerDescriptor( configuration, string.Empty, typeof( ControllerV1 ) ); + var convention = new VersionedAttributeRoutingConvention( "Tests", configuration, new ApiVersion( majorVersion, 0 ) ); // act var result = convention.ShouldMapController( controller ); diff --git a/test/Microsoft.AspNet.OData.Versioning.Tests/Web.OData/Routing/VersionedMetadataRoutingConventionTest.cs b/test/Microsoft.AspNet.OData.Versioning.Tests/Web.OData/Routing/VersionedMetadataRoutingConventionTest.cs index 917d1b7b..3b25e6c1 100644 --- a/test/Microsoft.AspNet.OData.Versioning.Tests/Web.OData/Routing/VersionedMetadataRoutingConventionTest.cs +++ b/test/Microsoft.AspNet.OData.Versioning.Tests/Web.OData/Routing/VersionedMetadataRoutingConventionTest.cs @@ -1,24 +1,44 @@ namespace Microsoft.Web.OData.Routing { using FluentAssertions; + using Microsoft.OData; + using Microsoft.OData.Edm; using Moq; + using System; using System.Collections.Generic; using System.Linq; using System.Net.Http; using System.Web.Http.Controllers; + using System.Web.OData; using System.Web.OData.Routing; using Xunit; + using static Microsoft.OData.ServiceLifetime; + using ODataPath = System.Web.OData.Routing.ODataPath; public class VersionedMetadataRoutingConventionTest { + readonly IODataPathHandler pathHandler = new DefaultODataPathHandler(); + readonly IServiceProvider serviceProvider; + + public VersionedMetadataRoutingConventionTest() + { + var builder = new DefaultContainerBuilder(); + + builder.AddDefaultODataServices(); + builder.AddService( Singleton, typeof( IEdmModel ), sp => Test.Model ); + serviceProvider = builder.BuildContainer(); + } + + ODataPath ParseUrl( string odataPath ) => pathHandler.Parse( "http://localhost", odataPath, serviceProvider ); + public static IEnumerable SelectControllerData { get { - yield return new object[] { new ODataPath(), "VersionedMetadata" }; - yield return new object[] { new ODataPath( new MetadataPathSegment() ), "VersionedMetadata" }; - yield return new object[] { new ODataPath( new EntitySetPathSegment( "Tests" ) ), null }; - yield return new object[] { new ODataPath( new EntitySetPathSegment( "Tests" ), new KeyValuePathSegment( "42" ) ), null }; + yield return new object[] { "", "VersionedMetadata" }; + yield return new object[] { "$metadata", "VersionedMetadata" }; + yield return new object[] { "Tests", null }; + yield return new object[] { "Tests(42)", null }; } } @@ -26,19 +46,20 @@ public static IEnumerable SelectActionData { get { - yield return new object[] { new ODataPath(), "GET", "GetServiceDocument" }; - yield return new object[] { new ODataPath( new MetadataPathSegment() ), "GET", "GetMetadata" }; - yield return new object[] { new ODataPath( new MetadataPathSegment() ), "OPTIONS", "GetOptions" }; - yield return new object[] { new ODataPath( new EntitySetPathSegment( "Tests" ) ), "GET", null }; - yield return new object[] { new ODataPath( new EntitySetPathSegment( "Tests" ), new KeyValuePathSegment( "42" ) ), "GET", null }; + yield return new object[] { "", "GET", "GetServiceDocument" }; + yield return new object[] { "$metadata", "GET", "GetMetadata" }; + yield return new object[] { "$metadata", "OPTIONS", "GetOptions" }; + yield return new object[] { "Tests", "GET", null }; + yield return new object[] { "Tests(42)", "GET", null }; } } [Theory] [MemberData( nameof( SelectControllerData ) )] - public void select_controller_should_return_expected_name( ODataPath odataPath, string expected ) + public void select_controller_should_return_expected_name( string requestUrl, string expected ) { // arrange + var odataPath = ParseUrl( requestUrl ); var request = new HttpRequestMessage(); var routingConvention = new VersionedMetadataRoutingConvention(); @@ -51,9 +72,10 @@ public void select_controller_should_return_expected_name( ODataPath odataPath, [Theory] [MemberData( nameof( SelectActionData ) )] - public void select_action_should_return_expected_name( ODataPath odataPath, string verb, string expected ) + public void select_action_should_return_expected_name( string requestUrl, string verb, string expected ) { // arrange + var odataPath = ParseUrl( requestUrl ); var request = new HttpRequestMessage( new HttpMethod( verb ), "http://localhost/$metadata" ); var controllerContext = new HttpControllerContext() { Request = request }; var actionMap = new Mock>().Object; diff --git a/test/Microsoft.AspNet.OData.Versioning.Tests/Web.OData/Routing/VersionedODataPathRouteConstraintTest.cs b/test/Microsoft.AspNet.OData.Versioning.Tests/Web.OData/Routing/VersionedODataPathRouteConstraintTest.cs index bc8f1ac6..856cdefe 100644 --- a/test/Microsoft.AspNet.OData.Versioning.Tests/Web.OData/Routing/VersionedODataPathRouteConstraintTest.cs +++ b/test/Microsoft.AspNet.OData.Versioning.Tests/Web.OData/Routing/VersionedODataPathRouteConstraintTest.cs @@ -21,35 +21,18 @@ public class VersionedODataPathRouteConstraintTest { - public class Test - { - public int Id { get; set; } - } - - static IEdmModel EmptyModel => new ODataModelBuilder().GetEdmModel(); - - static IEdmModel TestModel - { - get - { - var builder = new ODataModelBuilder(); - var tests = builder.EntitySet( "Tests" ).EntityType; - tests.HasKey( t => t.Id ); - return builder.GetEdmModel(); - } - } - static VersionedODataPathRouteConstraint NewVersionedODataPathRouteConstraint( HttpRequestMessage request, IEdmModel model, ApiVersion apiVersion, string routePrefix = null ) { var pathHandler = new DefaultODataPathHandler(); var conventions = ODataRoutingConventions.CreateDefault(); var configuration = new HttpConfiguration(); var routingConventions = Enumerable.Empty(); - var constraint = new VersionedODataPathRouteConstraint( pathHandler, model, "odata", routingConventions, apiVersion ); + var constraint = new VersionedODataPathRouteConstraint( "odata", apiVersion ); configuration.AddApiVersioning(); configuration.MapVersionedODataRoute( "odata", routePrefix, model, apiVersion ); request.SetConfiguration( configuration ); + configuration.EnsureInitialized(); return constraint; } @@ -66,7 +49,7 @@ public void match_should_always_return_true_for_uri_resolution() var pathHandler = new Mock().Object; var model = new Mock().Object; var routingConventions = Enumerable.Empty(); - var constraint = new VersionedODataPathRouteConstraint( pathHandler, model, "odata", routingConventions, Default ); + var constraint = new VersionedODataPathRouteConstraint( "odata", Default ); // act var result = constraint.Match( request, route, parameterName, values, routeDirection ); @@ -81,10 +64,9 @@ public void match_should_always_return_true_for_uri_resolution() public void match_should_be_true_when_api_version_is_requested_in_query_string( string apiVersion ) { // arrange - var model = TestModel; var request = new HttpRequestMessage( Get, $"http://localhost/Tests(1)?api-version={apiVersion}" ); var values = new Dictionary() { { "odataPath", "Tests(1)" } }; - var constraint = NewVersionedODataPathRouteConstraint( request, model, Parse( apiVersion ) ); + var constraint = NewVersionedODataPathRouteConstraint( request, Test.Model, Parse( apiVersion ) ); // act var result = constraint.Match( request, null, null, values, UriResolution ); @@ -102,10 +84,9 @@ public void match_should_return_expected_result_for_service_and_metadata_documen { // arrange var apiVersion = Parse( apiVersionValue ); - var model = EmptyModel; var request = new HttpRequestMessage( Get, requestUri ); var values = new Dictionary() { { "odataPath", odataPath } }; - var constraint = NewVersionedODataPathRouteConstraint( request, model, apiVersion ); + var constraint = NewVersionedODataPathRouteConstraint( request, Test.EmptyModel, apiVersion ); // act var result = constraint.Match( request, null, null, values, UriResolution ); @@ -121,10 +102,9 @@ public void match_should_return_expected_result_when_controller_is_implicitly_ve { // arrange var apiVersion = new ApiVersion( 2, 0 ); - var model = TestModel; var request = new HttpRequestMessage( Get, $"http://localhost/Tests(1)" ); var values = new Dictionary() { { "odataPath", "Tests(1)" } }; - var constraint = NewVersionedODataPathRouteConstraint( request, model, apiVersion ); + var constraint = NewVersionedODataPathRouteConstraint( request, Test.Model, apiVersion ); request.GetConfiguration().AddApiVersioning( o => @@ -144,10 +124,9 @@ public void match_should_return_expected_result_when_controller_is_implicitly_ve public void match_should_return_400_when_requested_api_version_is_ambiguous() { // arrange - var model = TestModel; var request = new HttpRequestMessage( Get, $"http://localhost/Tests(1)?api-version=1.0&api-version=2.0" ); var values = new Dictionary() { { "odataPath", "Tests(1)" } }; - var constraint = NewVersionedODataPathRouteConstraint( request, model, new ApiVersion( 1, 0 ) ); + var constraint = NewVersionedODataPathRouteConstraint( request, Test.Model, new ApiVersion( 1, 0 ) ); // act Action match = () => constraint.Match( request, null, null, values, UriResolution ); diff --git a/test/Microsoft.AspNet.WebApi.Acceptance.Tests/OData/Advanced/AdvancedAcceptanceTest.cs b/test/Microsoft.AspNet.WebApi.Acceptance.Tests/OData/Advanced/AdvancedAcceptanceTest.cs index 3115559e..692ffb0c 100644 --- a/test/Microsoft.AspNet.WebApi.Acceptance.Tests/OData/Advanced/AdvancedAcceptanceTest.cs +++ b/test/Microsoft.AspNet.WebApi.Acceptance.Tests/OData/Advanced/AdvancedAcceptanceTest.cs @@ -5,9 +5,11 @@ using Controllers; using Http; using Http.Versioning; + using Microsoft.OData.UriParser; using System.Web.Http; using System.Web.OData.Builder; using static System.Web.Http.RouteParameter; + using static Microsoft.OData.ServiceLifetime; public abstract class AdvancedAcceptanceTest : ODataAcceptanceTest { @@ -40,7 +42,7 @@ protected AdvancedAcceptanceTest() }; var models = modelBuilder.GetEdmModels(); - Configuration.MapVersionedODataRoutes( "odata", "api", models ); + Configuration.MapVersionedODataRoutes( "odata", "api", models, builder => builder.AddService( Singleton, typeof( ODataUriResolver ), sp => TestUriResolver ) ); Configuration.Routes.MapHttpRoute( "orders", "api/{controller}/{key}", new { key = Optional } ); Configuration.EnsureInitialized(); } diff --git a/test/Microsoft.AspNet.WebApi.Acceptance.Tests/OData/Basic/BasicAcceptanceTest.cs b/test/Microsoft.AspNet.WebApi.Acceptance.Tests/OData/Basic/BasicAcceptanceTest.cs index 020cfaa4..150ea1ee 100644 --- a/test/Microsoft.AspNet.WebApi.Acceptance.Tests/OData/Basic/BasicAcceptanceTest.cs +++ b/test/Microsoft.AspNet.WebApi.Acceptance.Tests/OData/Basic/BasicAcceptanceTest.cs @@ -4,11 +4,13 @@ using Configuration; using Controllers; using FluentAssertions; + using Microsoft.OData.UriParser; using System.Net.Http; using System.Threading.Tasks; using System.Web.Http; using System.Web.OData.Builder; using Xunit; + using static Microsoft.OData.ServiceLifetime; using static System.Net.HttpStatusCode; public abstract class BasicAcceptanceTest : ODataAcceptanceTest @@ -32,8 +34,8 @@ protected BasicAcceptanceTest() }; var models = modelBuilder.GetEdmModels(); - Configuration.MapVersionedODataRoutes( "odata", "api", models ); - Configuration.MapVersionedODataRoutes( "odata-bypath", "v{apiVersion}", models ); + Configuration.MapVersionedODataRoutes( "odata", "api", models, builder => builder.AddService( Singleton, typeof( ODataUriResolver ), sp => TestUriResolver ) ); + Configuration.MapVersionedODataRoutes( "odata-bypath", "v{apiVersion}", models, builder => builder.AddService( Singleton, typeof( ODataUriResolver ), sp => TestUriResolver ) ); Configuration.EnsureInitialized(); } diff --git a/test/Microsoft.AspNet.WebApi.Acceptance.Tests/OData/Conventions/ConventionsAcceptanceTest.cs b/test/Microsoft.AspNet.WebApi.Acceptance.Tests/OData/Conventions/ConventionsAcceptanceTest.cs index 7298a994..bc497e0d 100644 --- a/test/Microsoft.AspNet.WebApi.Acceptance.Tests/OData/Conventions/ConventionsAcceptanceTest.cs +++ b/test/Microsoft.AspNet.WebApi.Acceptance.Tests/OData/Conventions/ConventionsAcceptanceTest.cs @@ -4,9 +4,10 @@ using Configuration; using Controllers; using Http.Versioning.Conventions; + using Microsoft.OData.UriParser; using System.Web.Http; using System.Web.OData.Builder; - using System.Web.OData.Extensions; + using static Microsoft.OData.ServiceLifetime; public abstract class ConventionsAcceptanceTest : ODataAcceptanceTest { @@ -41,8 +42,8 @@ protected ConventionsAcceptanceTest() }; var models = modelBuilder.GetEdmModels(); - Configuration.MapVersionedODataRoutes( "odata", "api", models ); - Configuration.MapVersionedODataRoutes( "odata-bypath", "v{apiVersion}", models ); + Configuration.MapVersionedODataRoutes( "odata", "api", models, builder => builder.AddService( Singleton, typeof( ODataUriResolver ), sp => TestUriResolver ) ); + Configuration.MapVersionedODataRoutes( "odata-bypath", "v{apiVersion}", models, builder => builder.AddService( Singleton, typeof( ODataUriResolver ), sp => TestUriResolver ) ); Configuration.EnsureInitialized(); } } diff --git a/test/Microsoft.AspNet.WebApi.Acceptance.Tests/OData/ODataAcceptanceTest.cs b/test/Microsoft.AspNet.WebApi.Acceptance.Tests/OData/ODataAcceptanceTest.cs index 8c0c8220..f8cb42e2 100644 --- a/test/Microsoft.AspNet.WebApi.Acceptance.Tests/OData/ODataAcceptanceTest.cs +++ b/test/Microsoft.AspNet.WebApi.Acceptance.Tests/OData/ODataAcceptanceTest.cs @@ -1,22 +1,19 @@ namespace Microsoft.Web.OData { using FluentAssertions; + using Microsoft.OData.UriParser; using OData.Controllers; using System.Net.Http; using System.Threading.Tasks; - using System.Web.OData.Extensions; using Xunit; using static System.Net.HttpStatusCode; [Trait( "Framework", "OData" )] public abstract class ODataAcceptanceTest : AcceptanceTest { - protected ODataAcceptanceTest() - { - FilteredControllerTypes.Add( typeof( VersionedMetadataController ) ); - Configuration.EnableCaseInsensitive( true ); - Configuration.EnableUnqualifiedNameCall( true ); - } + protected ODataAcceptanceTest() => FilteredControllerTypes.Add( typeof( VersionedMetadataController ) ); + + protected ODataUriResolver TestUriResolver { get; } = new CustomUriResolver(); [Fact] public async Task then_the_service_document_should_allow_an_unspecified_version() @@ -105,5 +102,12 @@ public async Task then_X24metadata_should_return_400_for_an_unsupported_version( response.StatusCode.Should().Be( BadRequest ); content.Error.Code.Should().Be( "UnsupportedApiVersion" ); } + + // HACK: required due to bug in ODL + // REF: https://github.com/OData/odata.net/issues/695 + sealed class CustomUriResolver : UnqualifiedODataUriResolver + { + public override bool EnableCaseInsensitive { get => true; set { } } + } } } \ No newline at end of file