diff --git a/sample/ODataRoutingSample/ExampleQueryOptionsBindingExtension.cs b/sample/ODataRoutingSample/ExampleQueryOptionsBindingExtension.cs
new file mode 100644
index 000000000..f63104844
--- /dev/null
+++ b/sample/ODataRoutingSample/ExampleQueryOptionsBindingExtension.cs
@@ -0,0 +1,22 @@
+//-----------------------------------------------------------------------------
+//
+// Copyright (c) .NET Foundation and Contributors. All rights reserved.
+// See License.txt in the project root for license information.
+//
+//------------------------------------------------------------------------------
+
+using Microsoft.AspNetCore.OData.Query;
+using Microsoft.AspNetCore.OData.Query.Extension;
+using System.Linq;
+
+namespace ODataRoutingSample
+{
+ public class ExampleQueryOptionsBindingExtension : IODataQueryOptionsBindingExtension
+ {
+ public IQueryable ApplyTo(IQueryable query, ODataQueryOptions queryOptions, ODataQuerySettings querySettings)
+ {
+ //Do something here
+ return query;
+ }
+ }
+}
diff --git a/sample/ODataRoutingSample/Startup.cs b/sample/ODataRoutingSample/Startup.cs
index 43a4dab2f..d2b13b3ef 100644
--- a/sample/ODataRoutingSample/Startup.cs
+++ b/sample/ODataRoutingSample/Startup.cs
@@ -16,11 +16,9 @@
using Microsoft.AspNetCore.OData;
using Microsoft.EntityFrameworkCore;
using Microsoft.AspNetCore.OData.Batch;
-using Microsoft.OData;
using ODataRoutingSample.Models;
using Microsoft.AspNetCore.Mvc.Controllers;
using System.Reflection;
-using System.Linq;
namespace ODataRoutingSample
{
@@ -76,7 +74,10 @@ public void ConfigureServices(IServiceCollection services)
*/
.AddOData(opt => opt.Count().Filter().Expand().Select().OrderBy().SetMaxTop(5)
.AddRouteComponents(model0)
- .AddRouteComponents("v1", model1)
+ .AddRouteComponents("v1", model1, (services) =>
+ {
+ services.AddODataQueryOptionsBindingExtension(new ExampleQueryOptionsBindingExtension());
+ })
.AddRouteComponents("v2{data}", model2, services => services.AddSingleton())
.AddRouteComponents("v3", model3)
.Conventions.Add(new MyConvention())
diff --git a/src/Microsoft.AspNetCore.OData/Microsoft.AspNetCore.OData.xml b/src/Microsoft.AspNetCore.OData/Microsoft.AspNetCore.OData.xml
index 2ea93aae3..147fae409 100644
--- a/src/Microsoft.AspNetCore.OData/Microsoft.AspNetCore.OData.xml
+++ b/src/Microsoft.AspNetCore.OData/Microsoft.AspNetCore.OData.xml
@@ -6299,6 +6299,22 @@
The to add the services to.
The so that additional calls can be chained.
+
+
+ Adds a to a service collection.
+
+ The to add the services to.
+ The to add to the service collection.
+ The so that additional calls can be chained.
+
+
+
+ Adds multiple to a service collection.
+
+ The to add the services to.
+ The to add to the service collection.
+ The so that additional calls can be chained.
+
OData UriFunctions helper.
@@ -9474,6 +9490,42 @@
'methodName(argTypeName1,argTypeName2,argTypeName3..)'
+
+
+ This interface allows to extend the method
+ and apply custom query features.
+
+
+
+
+ Apply a custom query to the given IQueryable.
+
+ The original .
+ The object that is executing this method.
+ The settings to use in query composition.
+ The new after the query has been applied to.
+
+
+
+ This class allows to use multiple extenion interfaces.
+
+
+
+
+ Initializes a new instance of the class
+ with a list of extension interfaces.
+
+ A list of query extensions to apply.
+
+
+
+ Apply multiple custom queries to the given IQueryable.
+
+ The original .
+ The object that is executing this method.
+ The settings to use in query composition.
+ The new after the query has been applied to.
+
This enum defines how to handle null propagation in queryable support.
diff --git a/src/Microsoft.AspNetCore.OData/ODataServiceCollectionExtensions.cs b/src/Microsoft.AspNetCore.OData/ODataServiceCollectionExtensions.cs
index 4481291fa..ba1700a86 100644
--- a/src/Microsoft.AspNetCore.OData/ODataServiceCollectionExtensions.cs
+++ b/src/Microsoft.AspNetCore.OData/ODataServiceCollectionExtensions.cs
@@ -5,11 +5,14 @@
//
//------------------------------------------------------------------------------
+using System;
+using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ApplicationModels;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.OData.Query;
+using Microsoft.AspNetCore.OData.Query.Extension;
using Microsoft.AspNetCore.OData.Routing;
using Microsoft.AspNetCore.OData.Routing.Parser;
using Microsoft.AspNetCore.OData.Routing.Template;
@@ -105,5 +108,52 @@ internal static IServiceCollection AddODataCore(this IServiceCollection services
return services;
}
+
+ ///
+ /// Adds a to a service collection.
+ ///
+ /// The to add the services to.
+ /// The to add to the service collection.
+ /// The so that additional calls can be chained.
+ public static IServiceCollection AddODataQueryOptionsBindingExtension(this IServiceCollection services, IODataQueryOptionsBindingExtension extension)
+ {
+ if (services == null)
+ {
+ throw Error.ArgumentNull(nameof(services));
+ }
+
+ if (services == null)
+ {
+ throw Error.ArgumentNull(nameof(extension));
+ }
+
+ services.AddSingleton(extension);
+ return services;
+ }
+
+ ///
+ /// Adds multiple to a service collection.
+ ///
+ /// The to add the services to.
+ /// The to add to the service collection.
+ /// The so that additional calls can be chained.
+ public static IServiceCollection AddMultipleODataQueryOptionsBindingExtension(this IServiceCollection services, Action> extensions)
+ {
+ if (services == null)
+ {
+ throw Error.ArgumentNull(nameof(services));
+ }
+
+ if (extensions == null)
+ {
+ throw Error.ArgumentNull(nameof(extensions));
+ }
+
+ IList extensionResult = new List();
+ extensions.Invoke(extensionResult);
+ services.AddSingleton(new MultipleODataQueryOptionsBindingExtension(extensionResult));
+
+ return services;
+ }
}
}
diff --git a/src/Microsoft.AspNetCore.OData/PublicAPI.Unshipped.txt b/src/Microsoft.AspNetCore.OData/PublicAPI.Unshipped.txt
index e40289656..d057c22d0 100644
--- a/src/Microsoft.AspNetCore.OData/PublicAPI.Unshipped.txt
+++ b/src/Microsoft.AspNetCore.OData/PublicAPI.Unshipped.txt
@@ -910,6 +910,11 @@ Microsoft.AspNetCore.OData.Query.Expressions.SelectExpandBinder
Microsoft.AspNetCore.OData.Query.Expressions.SelectExpandBinder.FilterBinder.get -> Microsoft.AspNetCore.OData.Query.Expressions.IFilterBinder
Microsoft.AspNetCore.OData.Query.Expressions.SelectExpandBinder.OrderByBinder.get -> Microsoft.AspNetCore.OData.Query.Expressions.IOrderByBinder
Microsoft.AspNetCore.OData.Query.Expressions.SelectExpandBinder.SelectExpandBinder(Microsoft.AspNetCore.OData.Query.Expressions.IFilterBinder filterBinder, Microsoft.AspNetCore.OData.Query.Expressions.IOrderByBinder orderByBinder) -> void
+Microsoft.AspNetCore.OData.Query.Extension.IODataQueryOptionsBindingExtension
+Microsoft.AspNetCore.OData.Query.Extension.IODataQueryOptionsBindingExtension.ApplyTo(System.Linq.IQueryable query, Microsoft.AspNetCore.OData.Query.ODataQueryOptions queryOptions, Microsoft.AspNetCore.OData.Query.ODataQuerySettings querySettings) -> System.Linq.IQueryable
+Microsoft.AspNetCore.OData.Query.Extension.MultipleODataQueryOptionsBindingExtension
+Microsoft.AspNetCore.OData.Query.Extension.MultipleODataQueryOptionsBindingExtension.ApplyTo(System.Linq.IQueryable query, Microsoft.AspNetCore.OData.Query.ODataQueryOptions queryOptions, Microsoft.AspNetCore.OData.Query.ODataQuerySettings querySettings) -> System.Linq.IQueryable
+Microsoft.AspNetCore.OData.Query.Extension.MultipleODataQueryOptionsBindingExtension.MultipleODataQueryOptionsBindingExtension(System.Collections.Generic.IList extensions) -> void
Microsoft.AspNetCore.OData.Query.FilterQueryOption
Microsoft.AspNetCore.OData.Query.FilterQueryOption.ApplyTo(System.Linq.IQueryable query, Microsoft.AspNetCore.OData.Query.ODataQuerySettings querySettings) -> System.Linq.IQueryable
Microsoft.AspNetCore.OData.Query.FilterQueryOption.Compute.get -> Microsoft.AspNetCore.OData.Query.ComputeQueryOption
@@ -1613,6 +1618,8 @@ static Microsoft.AspNetCore.OData.ODataMvcBuilderExtensions.AddOData(this Micros
static Microsoft.AspNetCore.OData.ODataMvcCoreBuilderExtensions.AddOData(this Microsoft.Extensions.DependencyInjection.IMvcCoreBuilder builder) -> Microsoft.Extensions.DependencyInjection.IMvcCoreBuilder
static Microsoft.AspNetCore.OData.ODataMvcCoreBuilderExtensions.AddOData(this Microsoft.Extensions.DependencyInjection.IMvcCoreBuilder builder, System.Action setupAction) -> Microsoft.Extensions.DependencyInjection.IMvcCoreBuilder
static Microsoft.AspNetCore.OData.ODataMvcCoreBuilderExtensions.AddOData(this Microsoft.Extensions.DependencyInjection.IMvcCoreBuilder builder, System.Action setupAction) -> Microsoft.Extensions.DependencyInjection.IMvcCoreBuilder
+static Microsoft.AspNetCore.OData.ODataServiceCollectionExtensions.AddDataQueryOptionsBindingExtension(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, Microsoft.AspNetCore.OData.Query.Extension.IODataQueryOptionsBindingExtension extension) -> Microsoft.Extensions.DependencyInjection.IServiceCollection
+static Microsoft.AspNetCore.OData.ODataServiceCollectionExtensions.AddMultipleDataQueryOptionsBindingExtension(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, System.Action> extensions) -> Microsoft.Extensions.DependencyInjection.IServiceCollection
static Microsoft.AspNetCore.OData.ODataServiceCollectionExtensions.AddODataQueryFilter(this Microsoft.Extensions.DependencyInjection.IServiceCollection services) -> Microsoft.Extensions.DependencyInjection.IServiceCollection
static Microsoft.AspNetCore.OData.ODataServiceCollectionExtensions.AddODataQueryFilter(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, Microsoft.AspNetCore.Mvc.Filters.IActionFilter queryFilter) -> Microsoft.Extensions.DependencyInjection.IServiceCollection
static Microsoft.AspNetCore.OData.ODataUriFunctions.AddCustomUriFunction(string functionName, Microsoft.OData.UriParser.FunctionSignatureWithReturnType functionSignature, System.Reflection.MethodInfo methodInfo) -> void
diff --git a/src/Microsoft.AspNetCore.OData/Query/Extension/IODataQueryOptionsBindingExtension.cs b/src/Microsoft.AspNetCore.OData/Query/Extension/IODataQueryOptionsBindingExtension.cs
new file mode 100644
index 000000000..5c781090c
--- /dev/null
+++ b/src/Microsoft.AspNetCore.OData/Query/Extension/IODataQueryOptionsBindingExtension.cs
@@ -0,0 +1,30 @@
+//-----------------------------------------------------------------------------
+//
+// Copyright (c) .NET Foundation and Contributors. All rights reserved.
+// See License.txt in the project root for license information.
+//
+//------------------------------------------------------------------------------
+
+using System.Linq;
+
+namespace Microsoft.AspNetCore.OData.Query.Extension
+{
+
+ ///
+ /// This interface allows to extend the method
+ /// and apply custom query features.
+ ///
+ public interface IODataQueryOptionsBindingExtension
+ {
+
+ ///
+ /// Apply a custom query to the given IQueryable.
+ ///
+ /// The original .
+ /// The object that is executing this method.
+ /// The settings to use in query composition.
+ /// The new after the query has been applied to.
+ public IQueryable ApplyTo(IQueryable query, ODataQueryOptions queryOptions, ODataQuerySettings querySettings);
+
+ }
+}
diff --git a/src/Microsoft.AspNetCore.OData/Query/Extension/MultipleODataQueryOptionsBindingExtension.cs b/src/Microsoft.AspNetCore.OData/Query/Extension/MultipleODataQueryOptionsBindingExtension.cs
new file mode 100644
index 000000000..53e53325f
--- /dev/null
+++ b/src/Microsoft.AspNetCore.OData/Query/Extension/MultipleODataQueryOptionsBindingExtension.cs
@@ -0,0 +1,64 @@
+//-----------------------------------------------------------------------------
+//
+// Copyright (c) .NET Foundation and Contributors. All rights reserved.
+// See License.txt in the project root for license information.
+//
+//------------------------------------------------------------------------------
+
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Microsoft.AspNetCore.OData.Query.Extension
+{
+
+ ///
+ /// This class allows to use multiple extenion interfaces.
+ ///
+ public class MultipleODataQueryOptionsBindingExtension : IODataQueryOptionsBindingExtension
+ {
+
+ private readonly IList _extensions;
+
+ ///
+ /// Initializes a new instance of the class
+ /// with a list of extension interfaces.
+ ///
+ /// A list of query extensions to apply.
+ public MultipleODataQueryOptionsBindingExtension(IList extensions)
+ {
+ _extensions = extensions;
+ }
+
+ ///
+ /// Apply multiple custom queries to the given IQueryable.
+ ///
+ /// The original .
+ /// The object that is executing this method.
+ /// The settings to use in query composition.
+ /// The new after the query has been applied to.
+ public IQueryable ApplyTo(IQueryable query, ODataQueryOptions queryOptions, ODataQuerySettings querySettings)
+ {
+ if (query == null)
+ {
+ throw Error.ArgumentNull(nameof(query));
+ }
+
+ if (queryOptions == null)
+ {
+ throw Error.ArgumentNull(nameof(queryOptions));
+ }
+
+ if (querySettings == null)
+ {
+ throw Error.ArgumentNull(nameof(querySettings));
+ }
+
+ IQueryable result = query;
+ foreach (var extension in _extensions)
+ {
+ result = extension.ApplyTo(query, queryOptions, querySettings);
+ }
+ return result;
+ }
+ }
+}
diff --git a/src/Microsoft.AspNetCore.OData/Query/ODataQueryOptions.cs b/src/Microsoft.AspNetCore.OData/Query/ODataQueryOptions.cs
index 86dcb3e05..e92add89d 100644
--- a/src/Microsoft.AspNetCore.OData/Query/ODataQueryOptions.cs
+++ b/src/Microsoft.AspNetCore.OData/Query/ODataQueryOptions.cs
@@ -18,6 +18,7 @@
using Microsoft.AspNetCore.OData.Edm;
using Microsoft.AspNetCore.OData.Extensions;
using Microsoft.AspNetCore.OData.Query.Container;
+using Microsoft.AspNetCore.OData.Query.Extension;
using Microsoft.AspNetCore.OData.Query.Validator;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Primitives;
@@ -455,6 +456,8 @@ public virtual IQueryable ApplyTo(IQueryable query, ODataQuerySettings querySett
result = Top.ApplyTo(result, querySettings);
}
+ result = ApplyCustom(result, querySettings);
+
result = ApplyPaging(result, querySettings);
return result;
@@ -495,6 +498,22 @@ internal IQueryable ApplyPaging(IQueryable result, ODataQuerySettings querySetti
return result;
}
+ internal IQueryable ApplyCustom(IQueryable query, ODataQuerySettings querySettings)
+ {
+ if(Context.RequestContainer != null)
+ {
+ IODataQueryOptionsBindingExtension extension = Context.RequestContainer
+ .GetService();
+
+ if(extension != null)
+ {
+ return extension.ApplyTo(query, this, querySettings);
+ }
+ }
+
+ return query;
+ }
+
///
/// Generates the Stable OrderBy query option based on the existing OrderBy and other query options.
///
diff --git a/test/Microsoft.AspNetCore.OData.Tests/PublicApi/Microsoft.AspNetCore.OData.PublicApi.Net5.bsl b/test/Microsoft.AspNetCore.OData.Tests/PublicApi/Microsoft.AspNetCore.OData.PublicApi.Net5.bsl
index 87b1c80ee..aa7221b1f 100644
--- a/test/Microsoft.AspNetCore.OData.Tests/PublicApi/Microsoft.AspNetCore.OData.PublicApi.Net5.bsl
+++ b/test/Microsoft.AspNetCore.OData.Tests/PublicApi/Microsoft.AspNetCore.OData.PublicApi.Net5.bsl
@@ -67,6 +67,11 @@ public sealed class Microsoft.AspNetCore.OData.ODataMvcCoreBuilderExtensions {
ExtensionAttribute(),
]
public sealed class Microsoft.AspNetCore.OData.ODataServiceCollectionExtensions {
+ [
+ ExtensionAttribute(),
+ ]
+ public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddMultipleODataQueryOptionsBindingExtension (Microsoft.Extensions.DependencyInjection.IServiceCollection services, System.Action`1[[System.Collections.Generic.IList`1[[Microsoft.AspNetCore.OData.Query.Extension.IODataQueryOptionsBindingExtension]]]] extensions)
+
[
ExtensionAttribute(),
]
@@ -76,6 +81,11 @@ public sealed class Microsoft.AspNetCore.OData.ODataServiceCollectionExtensions
ExtensionAttribute(),
]
public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddODataQueryFilter (Microsoft.Extensions.DependencyInjection.IServiceCollection services, Microsoft.AspNetCore.Mvc.Filters.IActionFilter queryFilter)
+
+ [
+ ExtensionAttribute(),
+ ]
+ public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddODataQueryOptionsBindingExtension (Microsoft.Extensions.DependencyInjection.IServiceCollection services, Microsoft.AspNetCore.OData.Query.Extension.IODataQueryOptionsBindingExtension extension)
}
public sealed class Microsoft.AspNetCore.OData.ODataUriFunctions {
@@ -2753,6 +2763,16 @@ public class Microsoft.AspNetCore.OData.Query.Expressions.SelectExpandBinder : M
public virtual System.Linq.Expressions.Expression CreateTypeNameExpression (System.Linq.Expressions.Expression source, Microsoft.OData.Edm.IEdmStructuredType elementType, Microsoft.OData.Edm.IEdmModel model)
}
+public interface Microsoft.AspNetCore.OData.Query.Extension.IODataQueryOptionsBindingExtension {
+ System.Linq.IQueryable ApplyTo (System.Linq.IQueryable query, Microsoft.AspNetCore.OData.Query.ODataQueryOptions queryOptions, Microsoft.AspNetCore.OData.Query.ODataQuerySettings querySettings)
+}
+
+public class Microsoft.AspNetCore.OData.Query.Extension.MultipleODataQueryOptionsBindingExtension : IODataQueryOptionsBindingExtension {
+ public MultipleODataQueryOptionsBindingExtension (System.Collections.Generic.IList`1[[Microsoft.AspNetCore.OData.Query.Extension.IODataQueryOptionsBindingExtension]] extensions)
+
+ public virtual System.Linq.IQueryable ApplyTo (System.Linq.IQueryable query, Microsoft.AspNetCore.OData.Query.ODataQueryOptions queryOptions, Microsoft.AspNetCore.OData.Query.ODataQuerySettings querySettings)
+}
+
public class Microsoft.AspNetCore.OData.Query.Validator.CountQueryValidator {
public CountQueryValidator ()
diff --git a/test/Microsoft.AspNetCore.OData.Tests/PublicApi/Microsoft.AspNetCore.OData.PublicApi.NetCore31.bsl b/test/Microsoft.AspNetCore.OData.Tests/PublicApi/Microsoft.AspNetCore.OData.PublicApi.NetCore31.bsl
index 87b1c80ee..aa7221b1f 100644
--- a/test/Microsoft.AspNetCore.OData.Tests/PublicApi/Microsoft.AspNetCore.OData.PublicApi.NetCore31.bsl
+++ b/test/Microsoft.AspNetCore.OData.Tests/PublicApi/Microsoft.AspNetCore.OData.PublicApi.NetCore31.bsl
@@ -67,6 +67,11 @@ public sealed class Microsoft.AspNetCore.OData.ODataMvcCoreBuilderExtensions {
ExtensionAttribute(),
]
public sealed class Microsoft.AspNetCore.OData.ODataServiceCollectionExtensions {
+ [
+ ExtensionAttribute(),
+ ]
+ public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddMultipleODataQueryOptionsBindingExtension (Microsoft.Extensions.DependencyInjection.IServiceCollection services, System.Action`1[[System.Collections.Generic.IList`1[[Microsoft.AspNetCore.OData.Query.Extension.IODataQueryOptionsBindingExtension]]]] extensions)
+
[
ExtensionAttribute(),
]
@@ -76,6 +81,11 @@ public sealed class Microsoft.AspNetCore.OData.ODataServiceCollectionExtensions
ExtensionAttribute(),
]
public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddODataQueryFilter (Microsoft.Extensions.DependencyInjection.IServiceCollection services, Microsoft.AspNetCore.Mvc.Filters.IActionFilter queryFilter)
+
+ [
+ ExtensionAttribute(),
+ ]
+ public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddODataQueryOptionsBindingExtension (Microsoft.Extensions.DependencyInjection.IServiceCollection services, Microsoft.AspNetCore.OData.Query.Extension.IODataQueryOptionsBindingExtension extension)
}
public sealed class Microsoft.AspNetCore.OData.ODataUriFunctions {
@@ -2753,6 +2763,16 @@ public class Microsoft.AspNetCore.OData.Query.Expressions.SelectExpandBinder : M
public virtual System.Linq.Expressions.Expression CreateTypeNameExpression (System.Linq.Expressions.Expression source, Microsoft.OData.Edm.IEdmStructuredType elementType, Microsoft.OData.Edm.IEdmModel model)
}
+public interface Microsoft.AspNetCore.OData.Query.Extension.IODataQueryOptionsBindingExtension {
+ System.Linq.IQueryable ApplyTo (System.Linq.IQueryable query, Microsoft.AspNetCore.OData.Query.ODataQueryOptions queryOptions, Microsoft.AspNetCore.OData.Query.ODataQuerySettings querySettings)
+}
+
+public class Microsoft.AspNetCore.OData.Query.Extension.MultipleODataQueryOptionsBindingExtension : IODataQueryOptionsBindingExtension {
+ public MultipleODataQueryOptionsBindingExtension (System.Collections.Generic.IList`1[[Microsoft.AspNetCore.OData.Query.Extension.IODataQueryOptionsBindingExtension]] extensions)
+
+ public virtual System.Linq.IQueryable ApplyTo (System.Linq.IQueryable query, Microsoft.AspNetCore.OData.Query.ODataQueryOptions queryOptions, Microsoft.AspNetCore.OData.Query.ODataQuerySettings querySettings)
+}
+
public class Microsoft.AspNetCore.OData.Query.Validator.CountQueryValidator {
public CountQueryValidator ()
diff --git a/test/Microsoft.AspNetCore.OData.Tests/Query/Query/QueryOptionsBindingExtensionTests.cs b/test/Microsoft.AspNetCore.OData.Tests/Query/Query/QueryOptionsBindingExtensionTests.cs
new file mode 100644
index 000000000..e6d5c467f
--- /dev/null
+++ b/test/Microsoft.AspNetCore.OData.Tests/Query/Query/QueryOptionsBindingExtensionTests.cs
@@ -0,0 +1,146 @@
+//-----------------------------------------------------------------------------
+//
+// Copyright (c) .NET Foundation and Contributors. All rights reserved.
+// See License.txt in the project root for license information.
+//
+//------------------------------------------------------------------------------
+
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.OData.Query;
+using Microsoft.AspNetCore.OData.Query.Extension;
+using Microsoft.AspNetCore.OData.TestCommon;
+using Microsoft.AspNetCore.OData.Tests.Extensions;
+using Microsoft.AspNetCore.OData.Tests.Models;
+using Microsoft.OData;
+using Microsoft.OData.Edm;
+using Microsoft.OData.ModelBuilder;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Xunit;
+
+namespace Microsoft.AspNetCore.OData.Tests.Query
+{
+ public class QueryOptionsBindingExtensionTests
+ {
+
+ [Fact]
+ public void ODataQueryOptions_ApplyCustom_RequestContainerIsNull()
+ {
+ // Arrange
+ HttpRequest request = RequestFactory.Create(HttpMethods.Get, "http://server/service/Customers");
+ var context = new ODataQueryContext(CreateModel(), typeof(Customer));
+ ODataQueryOptions queryOptions = new ODataQueryOptions(context, request);
+ context.RequestContainer = null;
+ ODataQuerySettings querySettings = context.GetODataQuerySettings();
+ var customers = CreateTestQueryable();
+
+ // Act
+ var result = queryOptions.ApplyCustom(customers, querySettings);
+
+ // Arrange
+ Assert.Equal(customers, result);
+ }
+
+ [Fact]
+ public void ODataQueryOptions_ApplyCustom_NoExtensionHandlerProvided()
+ {
+ // Arrange
+ HttpRequest request = RequestFactory.Create(HttpMethods.Get, "http://server/service/Customers");
+ var context = new ODataQueryContext(CreateModel(), typeof(Customer));
+ ODataQueryOptions queryOptions = new ODataQueryOptions(context, request);
+ ODataQuerySettings querySettings = context.GetODataQuerySettings();
+ var customers = CreateTestQueryable();
+
+ // Act
+ var result = queryOptions.ApplyCustom(customers, querySettings);
+
+ // Arrange
+ Assert.Equal(customers, result);
+ }
+
+ [Fact]
+ public void ODataQueryOptions_ApplyCustom_WithExtensionHandlerProvided()
+ {
+ // Arrange
+ IServiceProvider serviceProvider = new MockServiceProvider((builder) =>
+ {
+ builder.AddService(ServiceLifetime.Singleton
+ ,typeof(IODataQueryOptionsBindingExtension)
+ ,typeof(MockODataQueryOptionsBindingExtension));
+ });
+
+ HttpRequest request = RequestFactory.Create(HttpMethods.Get, "http://server/service/Customers");
+ var context = new ODataQueryContext(CreateModel(), typeof(Customer));
+ ODataQueryOptions queryOptions = new ODataQueryOptions(context, request);
+ context.RequestContainer = serviceProvider;
+ ODataQuerySettings querySettings = context.GetODataQuerySettings();
+ var customers = CreateTestQueryable();
+
+ var expected = ApplyTestExpressions(customers,querySettings);
+
+ // Act
+ var result = queryOptions.ApplyCustom(customers, querySettings);
+
+ // Arrange
+ Assert.Equal(expected, result);
+ }
+
+ [Fact]
+ public void MultipleODataQueryOptionsBindingExtension_ApplyTo()
+ {
+ // Arrange
+ MultipleODataQueryOptionsBindingExtension extensions = new MultipleODataQueryOptionsBindingExtension(
+ new List{
+ new MockODataQueryOptionsBindingExtension(),
+ new MockODataQueryOptionsBindingExtension(),
+ });
+
+ HttpRequest request = RequestFactory.Create(HttpMethods.Get, "http://server/service/Customers");
+ var context = new ODataQueryContext(CreateModel(), typeof(Customer));
+ ODataQueryOptions queryOptions = new ODataQueryOptions(context, request);
+ ODataQuerySettings querySettings = context.GetODataQuerySettings();
+
+ var customers = CreateTestQueryable();
+
+ var expected = ApplyTestExpressions(ApplyTestExpressions(customers, querySettings), querySettings);
+
+ // Act
+ var result = extensions.ApplyTo(customers, queryOptions, querySettings);
+
+ // Arrange
+ Assert.Equal(expected, result);
+ }
+
+ private static IEdmModel CreateModel()
+ {
+ return new ODataModelBuilder()
+ .Add_Customer_EntityType_With_Address()
+ .Add_Customers_EntitySet().GetEdmModel();
+ }
+
+ private static IQueryable CreateTestQueryable()
+ {
+ return (new List{
+ new Customer { Id = 1, Address = new Address { City = "A" } },
+ new Customer { Id = 2, Address = new Address { City = "B" } },
+ new Customer { Id = 3, Address = new Address { City = "C" } }
+ }).AsQueryable();
+ }
+
+ //Used skip expression for tests, because it has a simple usage
+ private static IQueryable ApplyTestExpressions(IQueryable queryable, ODataQuerySettings querySettings)
+ {
+ return ExpressionHelpers.Skip(queryable, 10, queryable.ElementType, querySettings.EnableConstantParameterization);
+ }
+
+ private class MockODataQueryOptionsBindingExtension : IODataQueryOptionsBindingExtension
+ {
+ public IQueryable ApplyTo(IQueryable query, ODataQueryOptions queryOptions, ODataQuerySettings querySettings)
+ {
+ return ApplyTestExpressions(query, querySettings);
+ }
+ }
+
+ }
+}