From 7192097ae25913ae07a3c7ff2f0406d0936233c5 Mon Sep 17 00:00:00 2001 From: Sergey Saveliev Date: Thu, 3 Oct 2019 12:17:32 +0300 Subject: [PATCH 1/3] Make SelectExpandBinder public and provide possibility to override its functionality (OData#1900) --- .../Extensions/ContainerBuilderExtensions.cs | 3 + .../Microsoft.AspNet.OData.Shared.projitems | 2 + .../Expressions/DefaultODataBinderProvider.cs | 17 +++ .../Query/Expressions/ODataBinderProvider.cs | 20 +++ .../Query/Expressions/SelectExpandBinder.cs | 130 +++++++++++------- .../Query/SelectExpandQueryOption.cs | 16 ++- .../Expressions/SelectExpandBinderTest.cs | 6 +- .../Microsoft.AspNet.OData.PublicApi.bsl | 24 ++++ .../Microsoft.AspNetCore.OData.PublicApi.bsl | 28 ++++ 9 files changed, 196 insertions(+), 50 deletions(-) create mode 100644 src/Microsoft.AspNet.OData.Shared/Query/Expressions/DefaultODataBinderProvider.cs create mode 100644 src/Microsoft.AspNet.OData.Shared/Query/Expressions/ODataBinderProvider.cs diff --git a/src/Microsoft.AspNet.OData.Shared/Extensions/ContainerBuilderExtensions.cs b/src/Microsoft.AspNet.OData.Shared/Extensions/ContainerBuilderExtensions.cs index 72147911b5..595dc235f3 100644 --- a/src/Microsoft.AspNet.OData.Shared/Extensions/ContainerBuilderExtensions.cs +++ b/src/Microsoft.AspNet.OData.Shared/Extensions/ContainerBuilderExtensions.cs @@ -80,6 +80,9 @@ public static IContainerBuilder AddDefaultWebApiServices(this IContainerBuilder builder.AddService(ServiceLifetime.Singleton); builder.AddService(ServiceLifetime.Singleton); + // BinderProvider. + builder.AddService(ServiceLifetime.Singleton); + // Binders. builder.AddService(ServiceLifetime.Scoped); builder.AddService(ServiceLifetime.Transient); diff --git a/src/Microsoft.AspNet.OData.Shared/Microsoft.AspNet.OData.Shared.projitems b/src/Microsoft.AspNet.OData.Shared/Microsoft.AspNet.OData.Shared.projitems index d98135da70..509312b13f 100644 --- a/src/Microsoft.AspNet.OData.Shared/Microsoft.AspNet.OData.Shared.projitems +++ b/src/Microsoft.AspNet.OData.Shared/Microsoft.AspNet.OData.Shared.projitems @@ -69,6 +69,8 @@ + + diff --git a/src/Microsoft.AspNet.OData.Shared/Query/Expressions/DefaultODataBinderProvider.cs b/src/Microsoft.AspNet.OData.Shared/Query/Expressions/DefaultODataBinderProvider.cs new file mode 100644 index 0000000000..8174ca08e5 --- /dev/null +++ b/src/Microsoft.AspNet.OData.Shared/Query/Expressions/DefaultODataBinderProvider.cs @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +namespace Microsoft.AspNet.OData.Query.Expressions +{ + /// + /// The default . + /// + public class DefaultODataBinderProvider : ODataBinderProvider + { + /// + public override SelectExpandBinder GetSelectExpandBinder(ODataQuerySettings settings, SelectExpandQueryOption selectExpandQuery) + { + return new SelectExpandBinder(settings, selectExpandQuery); + } + } +} diff --git a/src/Microsoft.AspNet.OData.Shared/Query/Expressions/ODataBinderProvider.cs b/src/Microsoft.AspNet.OData.Shared/Query/Expressions/ODataBinderProvider.cs new file mode 100644 index 0000000000..00f464dd8a --- /dev/null +++ b/src/Microsoft.AspNet.OData.Shared/Query/Expressions/ODataBinderProvider.cs @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +namespace Microsoft.AspNet.OData.Query.Expressions +{ + /// + /// An ODataBinderProvider is a factory for creating OData binders. + /// + public abstract class ODataBinderProvider + { + /// + /// Gets a . + /// + /// The to use during binding. + /// The that contains the OData $select and $expand query options. + /// The . + public abstract SelectExpandBinder GetSelectExpandBinder(ODataQuerySettings settings, + SelectExpandQueryOption selectExpandQuery); + } +} diff --git a/src/Microsoft.AspNet.OData.Shared/Query/Expressions/SelectExpandBinder.cs b/src/Microsoft.AspNet.OData.Shared/Query/Expressions/SelectExpandBinder.cs index 9e62cd04ed..a77e2e55c1 100644 --- a/src/Microsoft.AspNet.OData.Shared/Query/Expressions/SelectExpandBinder.cs +++ b/src/Microsoft.AspNet.OData.Shared/Query/Expressions/SelectExpandBinder.cs @@ -23,7 +23,7 @@ namespace Microsoft.AspNet.OData.Query.Expressions /// [SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Justification = "Class coupling acceptable.")] - internal class SelectExpandBinder + public class SelectExpandBinder { private SelectExpandQueryOption _selectExpandQuery; private ODataQueryContext _context; @@ -31,7 +31,12 @@ internal class SelectExpandBinder private ODataQuerySettings _settings; private string _modelID; - public SelectExpandBinder(ODataQuerySettings settings, SelectExpandQueryOption selectExpandQuery) + /// + /// Initializes a new instance of the class. + /// + /// The to use during binding. + /// The that contains the OData $select and $expand query options. + protected internal SelectExpandBinder(ODataQuerySettings settings, SelectExpandQueryOption selectExpandQuery) { Contract.Assert(settings != null); Contract.Assert(selectExpandQuery != null); @@ -45,26 +50,12 @@ public SelectExpandBinder(ODataQuerySettings settings, SelectExpandQueryOption s _modelID = ModelContainer.GetModelID(_model); _settings = settings; } - - public static IQueryable Bind(IQueryable queryable, ODataQuerySettings settings, - SelectExpandQueryOption selectExpandQuery) - { - Contract.Assert(queryable != null); - - SelectExpandBinder binder = new SelectExpandBinder(settings, selectExpandQuery); - return binder.Bind(queryable); - } - - public static object Bind(object entity, ODataQuerySettings settings, - SelectExpandQueryOption selectExpandQuery) - { - Contract.Assert(entity != null); - - SelectExpandBinder binder = new SelectExpandBinder(settings, selectExpandQuery); - return binder.Bind(entity); - } - - private object Bind(object entity) + + /// + /// Applies the $select and $expand query options to the given entity. + /// + /// The original entity. + protected internal virtual object Bind(object entity) { Contract.Assert(entity != null); @@ -74,7 +65,11 @@ private object Bind(object entity) return projectionLambda.Compile().DynamicInvoke(entity); } - private IQueryable Bind(IQueryable queryable) + /// + /// Applies the $select and $expand query options to the given . + /// + /// The original . + protected internal virtual IQueryable Bind(IQueryable queryable) { Type elementType = _selectExpandQuery.Context.ElementClrType; @@ -117,13 +112,20 @@ internal Expression ProjectAsWrapper(Expression source, SelectExpandClause selec } } - internal Expression CreatePropertyNameExpression(IEdmStructuredType elementType, IEdmProperty property, Expression source) + /// + /// Returns an that represents the name of . + /// + /// The EDM entity type of the provided . + /// The EDM property which name expression to return. + /// The source that contains the . + /// The property name . + protected internal virtual Expression CreatePropertyNameExpression(IEdmStructuredType elementType, IEdmProperty edmProperty, Expression source) { Contract.Assert(elementType != null); - Contract.Assert(property != null); + Contract.Assert(edmProperty != null); Contract.Assert(source != null); - IEdmStructuredType declaringType = property.DeclaringType as IEdmStructuredType; + IEdmStructuredType declaringType = edmProperty.DeclaringType as IEdmStructuredType; Contract.Assert(declaringType != null, "Unstructured types cannot be projected."); @@ -143,14 +145,14 @@ internal Expression CreatePropertyNameExpression(IEdmStructuredType elementType, // source is navigationPropertyDeclaringType ? propertyName : null return Expression.Condition( test: Expression.TypeIs(source, castType), - ifTrue: Expression.Constant(property.Name), + ifTrue: Expression.Constant(edmProperty.Name), ifFalse: Expression.Constant(null, typeof(string))); } } // Expression // "propertyName" - return Expression.Constant(property.Name); + return Expression.Constant(edmProperty.Name); } internal Expression CreatePropertyValueExpression(IEdmStructuredType elementType, IEdmProperty property, Expression source) @@ -162,11 +164,19 @@ internal Expression CreatePropertyValueExpression(IEdmStructuredType elementType return CreatePropertyValueExpressionWithFilter(elementType, property, source, null); } - internal Expression CreatePropertyValueExpressionWithFilter(IEdmStructuredType elementType, IEdmProperty property, - Expression source, ExpandedReferenceSelectItem expandItem) + /// + /// Returns an that represents the value of . + /// + /// The EDM entity type of the provided . + /// The EDM property which value expression to return. + /// The source that contains the . + /// The that describes how to expand . + /// The property value . + protected internal virtual Expression CreatePropertyValueExpressionWithFilter(IEdmStructuredType elementType, IEdmProperty edmProperty, + Expression source, ExpandedReferenceSelectItem expandItem) { Contract.Assert(elementType != null); - Contract.Assert(property != null); + Contract.Assert(edmProperty != null); Contract.Assert(source != null); FilterClause filterClause = expandItem != null ? expandItem.FilterOption : null; @@ -191,16 +201,16 @@ internal Expression CreatePropertyValueExpressionWithFilter(IEdmStructuredType e // create expression similar to: 'source == null ? null : propertyValue' if (declaringType != currentType) { - Type castType = EdmLibHelpers.GetClrType(property.DeclaringType, _model); + Type castType = EdmLibHelpers.GetClrType(edmProperty.DeclaringType, _model); if (castType == null) { throw new ODataException(Error.Format(SRResources.MappingDoesNotContainResourceType, - property.DeclaringType.FullTypeName())); + edmProperty.DeclaringType.FullTypeName())); } source = Expression.TypeAs(source, castType); } - Expression propertyExpression = Expression.Property(source, currentProperty); + Expression propertyExpression = CreatePropertyAccessExpression(source, currentProperty); Type nullablePropType = TypeHelper.ToNullable(propertyExpression.Type); source = Expression.Condition( @@ -210,7 +220,7 @@ internal Expression CreatePropertyValueExpressionWithFilter(IEdmStructuredType e } else { - source = Expression.Property(source, currentProperty); + source = CreatePropertyAccessExpression(source, currentProperty); } currentType = propertyInPath.Type.ToStructuredType(); @@ -229,29 +239,27 @@ internal Expression CreatePropertyValueExpressionWithFilter(IEdmStructuredType e } // derived property using cast - if (currentType != property.DeclaringType) + if (currentType != edmProperty.DeclaringType) { - Type castType = EdmLibHelpers.GetClrType(property.DeclaringType, _model); + Type castType = EdmLibHelpers.GetClrType(edmProperty.DeclaringType, _model); if (castType == null) { throw new ODataException(Error.Format(SRResources.MappingDoesNotContainResourceType, - property.DeclaringType.FullTypeName())); + edmProperty.DeclaringType.FullTypeName())); } source = Expression.TypeAs(source, castType); } - string propertyName = EdmLibHelpers.GetClrPropertyName(property, _model); - PropertyInfo propertyInfo = source.Type.GetProperty(propertyName); - Expression propertyValue = Expression.Property(source, propertyInfo); + Expression propertyValue = CreatePropertyAccessExpression(source, edmProperty); Type nullablePropertyType = TypeHelper.ToNullable(propertyValue.Type); Expression nullablePropertyValue = ExpressionHelpers.ToNullable(propertyValue); if (filterClause != null) { - bool isCollection = property.Type.IsCollection(); + bool isCollection = edmProperty.Type.IsCollection(); - IEdmTypeReference edmElementType = (isCollection ? property.Type.AsCollection().ElementType() : property.Type); + IEdmTypeReference edmElementType = (isCollection ? edmProperty.Type.AsCollection().ElementType() : edmProperty.Type); Type clrElementType = EdmLibHelpers.GetClrType(edmElementType, _model); if (clrElementType == null) { @@ -285,7 +293,7 @@ internal Expression CreatePropertyValueExpressionWithFilter(IEdmStructuredType e if (filterLambdaExpression == null) { throw new ODataException(Error.Format(SRResources.ExpandFilterExpressionNotLambdaExpression, - property.Name, "LambdaExpression")); + edmProperty.Name, "LambdaExpression")); } ParameterExpression filterParameter = filterLambdaExpression.Parameters.First(); @@ -329,6 +337,30 @@ internal Expression CreatePropertyValueExpressionWithFilter(IEdmStructuredType e return propertyValue; } + /// + /// Returns an that represents access to . + /// + /// The source that contains the . + /// The EDM property which access expression to return. + /// The property access . + protected virtual Expression CreatePropertyAccessExpression(Expression source, IEdmProperty edmProperty) + { + var propertyName = EdmLibHelpers.GetClrPropertyName(edmProperty, _model); + PropertyInfo propertyInfo = source.Type.GetProperty(propertyName); + return Expression.Property(source, propertyInfo); + } + + /// + /// Returns an that represents access to property with name . + /// + /// The source that contains the property. + /// The name of the property. + /// The property access . + protected virtual Expression CreatePropertyAccessExpression(Expression source, string propertyName) + { + return Expression.Property(source, propertyName); + } + private class ReferenceNavigationPropertyExpandFilterVisitor : ExpressionVisitor { private Expression _source; @@ -443,7 +475,13 @@ private static bool GetSelectsOpenTypeSegments(SelectExpandClause selectExpandCl return selectExpandClause.SelectedItems.OfType().Any(x => x.SelectedPath.LastSegment is DynamicPathSegment); } - private Expression CreateTotalCountExpression(Expression source, ExpandedReferenceSelectItem expandItem) + /// + /// Returns an that represents the count of items in if it is applicable. + /// + /// The source which items should be count. + /// The . + /// The count . + protected virtual Expression CreateTotalCountExpression(Expression source, ExpandedReferenceSelectItem expandItem) { Expression countExpression = Expression.Constant(null, typeof(long?)); if (expandItem.CountOption == null || !expandItem.CountOption.Value) @@ -562,7 +600,7 @@ private Expression BuildPropertyContainer(IEdmStructuredType elementType, Expres if (dynamicPropertyDictionary != null) { Expression propertyName = Expression.Constant(dynamicPropertyDictionary.Name); - Expression propertyValue = Expression.Property(source, dynamicPropertyDictionary.Name); + Expression propertyValue = CreatePropertyAccessExpression(source, dynamicPropertyDictionary.Name); Expression nullablePropertyValue = ExpressionHelpers.ToNullable(propertyValue); if (_settings.HandleNullPropagation == HandleNullPropagationOption.True) { diff --git a/src/Microsoft.AspNet.OData.Shared/Query/SelectExpandQueryOption.cs b/src/Microsoft.AspNet.OData.Shared/Query/SelectExpandQueryOption.cs index b380eb8bc3..0d5a2004e9 100644 --- a/src/Microsoft.AspNet.OData.Shared/Query/SelectExpandQueryOption.cs +++ b/src/Microsoft.AspNet.OData.Shared/Query/SelectExpandQueryOption.cs @@ -8,6 +8,7 @@ using Microsoft.AspNet.OData.Formatter; using Microsoft.AspNet.OData.Query.Expressions; using Microsoft.AspNet.OData.Query.Validators; +using Microsoft.Extensions.DependencyInjection; using Microsoft.OData.Edm; using Microsoft.OData.UriParser; @@ -21,6 +22,7 @@ public class SelectExpandQueryOption private SelectExpandClause _selectExpandClause; private ODataQueryOptionParser _queryOptionParser; private SelectExpandClause _processedSelectExpandClause; + private ODataBinderProvider _binderProvider; // Give _levelsMaxLiteralExpansionDepth a negative value meaning it is uninitialized, and it will be set to: // 1. LevelsMaxLiteralExpansionDepth or @@ -62,6 +64,10 @@ public SelectExpandQueryOption(string select, string expand, ODataQueryContext c RawExpand = expand; Validator = SelectExpandQueryValidator.GetSelectExpandQueryValidator(context); _queryOptionParser = queryOptionParser; + + if (context.RequestContainer != null) + _binderProvider = context.RequestContainer.GetRequiredService(); + _binderProvider = _binderProvider ?? new DefaultODataBinderProvider(); } internal SelectExpandQueryOption( @@ -102,6 +108,10 @@ internal SelectExpandQueryOption(string select, string expand, ODataQueryContext context.NavigationSource, new Dictionary { { "$select", select }, { "$expand", expand } }, context.RequestContainer); + + if (context.RequestContainer != null) + _binderProvider = context.RequestContainer.GetRequiredService(); + _binderProvider = _binderProvider ?? new DefaultODataBinderProvider(); } /// @@ -204,7 +214,8 @@ public IQueryable ApplyTo(IQueryable queryable, ODataQuerySettings settings) ODataQuerySettings updatedSettings = Context.UpdateQuerySettings(settings, queryable); - return SelectExpandBinder.Bind(queryable, updatedSettings, this); + var binder = _binderProvider.GetSelectExpandBinder(updatedSettings, this); + return binder.Bind(queryable); } /// @@ -230,7 +241,8 @@ public object ApplyTo(object entity, ODataQuerySettings settings) ODataQuerySettings updatedSettings = Context.UpdateQuerySettings(settings, query: null); - return SelectExpandBinder.Bind(entity, updatedSettings, this); + var binder = _binderProvider.GetSelectExpandBinder(updatedSettings, this); + return binder.Bind(entity); } /// diff --git a/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Query/Expressions/SelectExpandBinderTest.cs b/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Query/Expressions/SelectExpandBinderTest.cs index 659aa92d7d..5a5ecff8d3 100644 --- a/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Query/Expressions/SelectExpandBinderTest.cs +++ b/test/UnitTest/Microsoft.AspNet.OData.Test.Shared/Query/Expressions/SelectExpandBinderTest.cs @@ -50,9 +50,10 @@ public void Bind_ReturnsIEdmObject_WithRightEdmType() { // Arrange SelectExpandQueryOption selectExpand = new SelectExpandQueryOption(select: "ID", expand: null, context: _context); + SelectExpandBinder binder = new SelectExpandBinder(_settings, selectExpand); // Act - IQueryable queryable = SelectExpandBinder.Bind(_queryable, _settings, selectExpand); + IQueryable queryable = binder.Bind(_queryable); // Assert Assert.NotNull(queryable); @@ -69,9 +70,10 @@ public void Bind_GeneratedExpression_ContainsExpandedObject() SelectExpandQueryOption selectExpand = new SelectExpandQueryOption("Orders", "Orders,Orders($expand=Customer)", _context); IPropertyMapper mapper = new IdentityPropertyMapper(); _model.Model.SetAnnotationValue(_model.Order, new DynamicPropertyDictionaryAnnotation(typeof(Order).GetProperty("OrderProperties"))); + SelectExpandBinder binder = new SelectExpandBinder(_settings, selectExpand); // Act - IQueryable queryable = SelectExpandBinder.Bind(_queryable, _settings, selectExpand); + IQueryable queryable = binder.Bind(_queryable); // Assert IEnumerator enumerator = queryable.GetEnumerator(); diff --git a/test/UnitTest/Microsoft.AspNet.OData.Test/PublicApi/Microsoft.AspNet.OData.PublicApi.bsl b/test/UnitTest/Microsoft.AspNet.OData.Test/PublicApi/Microsoft.AspNet.OData.PublicApi.bsl index 8f0d6d4c8d..b8ef99761a 100644 --- a/test/UnitTest/Microsoft.AspNet.OData.Test/PublicApi/Microsoft.AspNet.OData.PublicApi.bsl +++ b/test/UnitTest/Microsoft.AspNet.OData.Test/PublicApi/Microsoft.AspNet.OData.PublicApi.bsl @@ -3355,6 +3355,18 @@ public abstract class Microsoft.AspNet.OData.Query.Expressions.ExpressionBinderB public static int GuidCompare (System.Guid firstValue, System.Guid secondValue) } +public abstract class Microsoft.AspNet.OData.Query.Expressions.ODataBinderProvider { + protected ODataBinderProvider () + + public abstract SelectExpandBinder GetSelectExpandBinder (ODataQuerySettings settings, SelectExpandQueryOption selectExpandQuery) +} + +public class Microsoft.AspNet.OData.Query.Expressions.DefaultODataBinderProvider : ODataBinderProvider { + public DefaultODataBinderProvider () + + public virtual SelectExpandBinder GetSelectExpandBinder (ODataQuerySettings settings, SelectExpandQueryOption selectExpandQuery) +} + public class Microsoft.AspNet.OData.Query.Expressions.FilterBinder : ExpressionBinderBase { public FilterBinder (System.IServiceProvider requestContainer) @@ -3380,6 +3392,18 @@ public class Microsoft.AspNet.OData.Query.Expressions.FilterBinder : ExpressionB public virtual System.Linq.Expressions.Expression BindUnaryOperatorNode (Microsoft.OData.UriParser.UnaryOperatorNode unaryOperatorNode) } +public class Microsoft.AspNet.OData.Query.Expressions.SelectExpandBinder { + protected SelectExpandBinder (ODataQuerySettings settings, SelectExpandQueryOption selectExpandQuery) + + protected virtual System.Linq.IQueryable Bind (System.Linq.IQueryable queryable) + protected virtual object Bind (object entity) + protected virtual System.Linq.Expressions.Expression CreatePropertyAccessExpression (System.Linq.Expressions.Expression source, Microsoft.OData.Edm.IEdmProperty edmProperty) + protected virtual System.Linq.Expressions.Expression CreatePropertyAccessExpression (System.Linq.Expressions.Expression source, string propertyName) + protected virtual System.Linq.Expressions.Expression CreatePropertyNameExpression (Microsoft.OData.Edm.IEdmStructuredType elementType, Microsoft.OData.Edm.IEdmProperty edmProperty, System.Linq.Expressions.Expression source) + protected virtual System.Linq.Expressions.Expression CreatePropertyValueExpressionWithFilter (Microsoft.OData.Edm.IEdmStructuredType elementType, Microsoft.OData.Edm.IEdmProperty edmProperty, System.Linq.Expressions.Expression source, Microsoft.OData.UriParser.ExpandedReferenceSelectItem expandItem) + protected virtual System.Linq.Expressions.Expression CreateTotalCountExpression (System.Linq.Expressions.Expression source, Microsoft.OData.UriParser.ExpandedReferenceSelectItem expandItem) +} + public class Microsoft.AspNet.OData.Query.Validators.CountQueryValidator { public CountQueryValidator (DefaultQuerySettings defaultQuerySettings) diff --git a/test/UnitTest/Microsoft.AspNetCore.OData.Test/PublicApi/Microsoft.AspNetCore.OData.PublicApi.bsl b/test/UnitTest/Microsoft.AspNetCore.OData.Test/PublicApi/Microsoft.AspNetCore.OData.PublicApi.bsl index 3d0cc52e8c..c23402bd9f 100644 --- a/test/UnitTest/Microsoft.AspNetCore.OData.Test/PublicApi/Microsoft.AspNetCore.OData.PublicApi.bsl +++ b/test/UnitTest/Microsoft.AspNetCore.OData.Test/PublicApi/Microsoft.AspNetCore.OData.PublicApi.bsl @@ -2886,6 +2886,8 @@ public sealed class Microsoft.AspNet.OData.Query.UnsortableAttribute : System.At public class Microsoft.AspNet.OData.Results.CreatedODataResult`1 : IActionResult { public CreatedODataResult`1 (T entity) + T Entity { public virtual get; } + [ AsyncStateMachineAttribute(), ] @@ -2895,6 +2897,8 @@ public class Microsoft.AspNet.OData.Results.CreatedODataResult`1 : IActionResult public class Microsoft.AspNet.OData.Results.UpdatedODataResult`1 : IActionResult { public UpdatedODataResult`1 (T entity) + T Entity { public virtual get; } + [ AsyncStateMachineAttribute(), ] @@ -3486,6 +3490,18 @@ public abstract class Microsoft.AspNet.OData.Query.Expressions.ExpressionBinderB public static int GuidCompare (System.Guid firstValue, System.Guid secondValue) } +public abstract class Microsoft.AspNet.OData.Query.Expressions.ODataBinderProvider { + protected ODataBinderProvider () + + public abstract SelectExpandBinder GetSelectExpandBinder (ODataQuerySettings settings, SelectExpandQueryOption selectExpandQuery) +} + +public class Microsoft.AspNet.OData.Query.Expressions.DefaultODataBinderProvider : ODataBinderProvider { + public DefaultODataBinderProvider () + + public virtual SelectExpandBinder GetSelectExpandBinder (ODataQuerySettings settings, SelectExpandQueryOption selectExpandQuery) +} + public class Microsoft.AspNet.OData.Query.Expressions.FilterBinder : ExpressionBinderBase { public FilterBinder (System.IServiceProvider requestContainer) @@ -3511,6 +3527,18 @@ public class Microsoft.AspNet.OData.Query.Expressions.FilterBinder : ExpressionB public virtual System.Linq.Expressions.Expression BindUnaryOperatorNode (Microsoft.OData.UriParser.UnaryOperatorNode unaryOperatorNode) } +public class Microsoft.AspNet.OData.Query.Expressions.SelectExpandBinder { + protected SelectExpandBinder (ODataQuerySettings settings, SelectExpandQueryOption selectExpandQuery) + + protected virtual System.Linq.IQueryable Bind (System.Linq.IQueryable queryable) + protected virtual object Bind (object entity) + protected virtual System.Linq.Expressions.Expression CreatePropertyAccessExpression (System.Linq.Expressions.Expression source, Microsoft.OData.Edm.IEdmProperty edmProperty) + protected virtual System.Linq.Expressions.Expression CreatePropertyAccessExpression (System.Linq.Expressions.Expression source, string propertyName) + protected virtual System.Linq.Expressions.Expression CreatePropertyNameExpression (Microsoft.OData.Edm.IEdmStructuredType elementType, Microsoft.OData.Edm.IEdmProperty edmProperty, System.Linq.Expressions.Expression source) + protected virtual System.Linq.Expressions.Expression CreatePropertyValueExpressionWithFilter (Microsoft.OData.Edm.IEdmStructuredType elementType, Microsoft.OData.Edm.IEdmProperty edmProperty, System.Linq.Expressions.Expression source, Microsoft.OData.UriParser.ExpandedReferenceSelectItem expandItem) + protected virtual System.Linq.Expressions.Expression CreateTotalCountExpression (System.Linq.Expressions.Expression source, Microsoft.OData.UriParser.ExpandedReferenceSelectItem expandItem) +} + public class Microsoft.AspNet.OData.Query.Validators.CountQueryValidator { public CountQueryValidator (DefaultQuerySettings defaultQuerySettings) From 02241a4d1364919ddcbde8e604781867a57d2fcb Mon Sep 17 00:00:00 2001 From: Sergey Saveliev Date: Thu, 3 Oct 2019 12:50:51 +0300 Subject: [PATCH 2/3] Make AggregationBinder public and provide possibility to override its functionality (OData#1900) --- .../Query/ApplyQueryOption.cs | 21 ++-- .../Query/Expressions/AggregationBinder.cs | 95 ++++++++++++++++--- .../Expressions/DefaultODataBinderProvider.cs | 11 +++ .../Query/Expressions/ODataBinderProvider.cs | 16 ++++ .../Microsoft.AspNet.OData.PublicApi.bsl | 16 ++++ .../Microsoft.AspNetCore.OData.PublicApi.bsl | 16 ++++ 6 files changed, 148 insertions(+), 27 deletions(-) diff --git a/src/Microsoft.AspNet.OData.Shared/Query/ApplyQueryOption.cs b/src/Microsoft.AspNet.OData.Shared/Query/ApplyQueryOption.cs index 3e7a9dc871..82931be96b 100644 --- a/src/Microsoft.AspNet.OData.Shared/Query/ApplyQueryOption.cs +++ b/src/Microsoft.AspNet.OData.Shared/Query/ApplyQueryOption.cs @@ -23,6 +23,7 @@ public class ApplyQueryOption { private ApplyClause _applyClause; private ODataQueryOptionParser _queryOptionParser; + private ODataBinderProvider _binderProvider; /// /// Initialize a new instance of based on the raw $apply value and @@ -54,6 +55,9 @@ public ApplyQueryOption(string rawValue, ODataQueryContext context, ODataQueryOp //Validator = new FilterQueryValidator(); _queryOptionParser = queryOptionParser; ResultClrType = Context.ElementClrType; + if (context.RequestContainer != null) + _binderProvider = context.RequestContainer.GetRequiredService(); + _binderProvider = _binderProvider ?? new DefaultODataBinderProvider(); } /// @@ -124,25 +128,12 @@ public IQueryable ApplyTo(IQueryable query, ODataQuerySettings querySettings) Contract.Assert(applyClause != null); ODataQuerySettings updatedSettings = Context.UpdateQuerySettings(querySettings, query); - - // The IWebApiAssembliesResolver service is internal and can only be injected by WebApi. - // This code path may be used in cases when the service container is not available - // and the service container is available but may not contain an instance of IWebApiAssembliesResolver. - IWebApiAssembliesResolver assembliesResolver = WebApiAssembliesResolver.Default; - if (Context.RequestContainer != null) - { - IWebApiAssembliesResolver injectedResolver = Context.RequestContainer.GetService(); - if (injectedResolver != null) - { - assembliesResolver = injectedResolver; - } - } - + foreach (var transformation in applyClause.Transformations) { if (transformation.Kind == TransformationNodeKind.Aggregate || transformation.Kind == TransformationNodeKind.GroupBy) { - var binder = new AggregationBinder(updatedSettings, assembliesResolver, ResultClrType, Context.Model, transformation); + var binder = _binderProvider.GetAggregationBinder(updatedSettings, Context.RequestContainer, ResultClrType, Context.Model, transformation); query = binder.Bind(query); this.ResultClrType = binder.ResultClrType; } diff --git a/src/Microsoft.AspNet.OData.Shared/Query/Expressions/AggregationBinder.cs b/src/Microsoft.AspNet.OData.Shared/Query/Expressions/AggregationBinder.cs index b8eeea85c4..f915421b36 100644 --- a/src/Microsoft.AspNet.OData.Shared/Query/Expressions/AggregationBinder.cs +++ b/src/Microsoft.AspNet.OData.Shared/Query/Expressions/AggregationBinder.cs @@ -3,14 +3,17 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Diagnostics.Contracts; using System.Globalization; using System.Linq; using System.Linq.Expressions; using System.Reflection; +using Microsoft.AspNet.OData.Adapters; using Microsoft.AspNet.OData.Common; using Microsoft.AspNet.OData.Formatter; using Microsoft.AspNet.OData.Interfaces; +using Microsoft.Extensions.DependencyInjection; using Microsoft.OData; using Microsoft.OData.Edm; using Microsoft.OData.UriParser; @@ -18,7 +21,12 @@ namespace Microsoft.AspNet.OData.Query.Expressions { - internal class AggregationBinder : ExpressionBinderBase + /// + /// Translates an OData aggregate or groupby transformations of $apply parse tree represented by to + /// an and applies it to an . + /// + [SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Justification = "Relies on many ODataLib classes.")] + public class AggregationBinder : ExpressionBinderBase { private const string GroupByContainerProperty = "GroupByContainer"; private Type _elementType; @@ -33,6 +41,25 @@ internal class AggregationBinder : ExpressionBinderBase private bool _classicEF = false; + /// + /// Initializes a new instance of the class. + /// + /// The to use during binding. + /// The request container. + /// ClrType for result of transformations. + /// The EDM model. + /// The transformation node. + protected internal AggregationBinder(ODataQuerySettings settings, IServiceProvider requestContainer, Type elementType, + IEdmModel model, TransformationNode transformation) + : this(settings, (requestContainer != null ? requestContainer.GetService() : null) ?? WebApiAssembliesResolver.Default, + elementType, model, transformation) + { + // Notes for: ?? WebApiAssembliesResolver.Default + // The IWebApiAssembliesResolver service is internal and can only be injected by WebApi. + // This code path may be used in cases when the service container is not available + // and the service container is available but may not contain an instance of IWebApiAssembliesResolver. + } + internal AggregationBinder(ODataQuerySettings settings, IWebApiAssembliesResolver assembliesResolver, Type elementType, IEdmModel model, TransformationNode transformation) : base(model, assembliesResolver, settings) @@ -140,12 +167,16 @@ public Type ResultClrType get; private set; } - public IEdmTypeReference ResultType + internal IEdmTypeReference ResultType { get; private set; } - public IQueryable Bind(IQueryable query) + /// + /// Applies aggregate or groupby transformations of $apply query option to the given . + /// + /// The original . + public virtual IQueryable Bind(IQueryable query) { Contract.Assert(query != null); @@ -353,8 +384,16 @@ private Expression CreateAggregationExpression(ParameterExpression accum, Aggreg } } - private Expression CreateEntitySetAggregateExpression( - ParameterExpression accum, EntitySetAggregateExpression expression, Type baseType) + /// + /// Binds a to create a LINQ that + /// represents the semantics of the . + /// + /// + /// The node to bind. + /// + /// The LINQ created. + protected virtual Expression CreateEntitySetAggregateExpression( + ParameterExpression accumulativeParameter, EntitySetAggregateExpression expression, Type baseType) { // Should return following expression // $it => $it.AsQueryable() @@ -371,7 +410,7 @@ private Expression CreateEntitySetAggregateExpression( List wrapperTypeMemberAssignments = new List(); var asQueryableMethod = ExpressionHelperMethods.QueryableAsQueryable.MakeGenericMethod(baseType); - Expression asQueryableExpression = Expression.Call(null, asQueryableMethod, accum); + Expression asQueryableExpression = Expression.Call(null, asQueryableMethod, accumulativeParameter); // Create lambda to access the entity set from expression var source = BindAccessor(expression.Expression.Source); @@ -433,7 +472,15 @@ MethodInfo selectManyMethod return Expression.Call(null, selectMethod, groupedEntitySet, selectLambda); } - private Expression CreatePropertyAggregateExpression(ParameterExpression accum, AggregateExpression expression, Type baseType) + /// + /// Binds a to create a LINQ that + /// represents the semantics of the . + /// + /// + /// The node to bind. + /// + /// The LINQ created. + protected virtual Expression CreatePropertyAggregateExpression(ParameterExpression accumulativeParameter, AggregateExpression expression, Type baseType) { // accum type is IGrouping<,baseType> that implements IEnumerable // we need cast it to IEnumerable during expression building (IEnumerable)$it @@ -442,12 +489,12 @@ private Expression CreatePropertyAggregateExpression(ParameterExpression accum, if (_classicEF) { var asQuerableMethod = ExpressionHelperMethods.QueryableAsQueryable.MakeGenericMethod(baseType); - asQuerableExpression = Expression.Call(null, asQuerableMethod, accum); + asQuerableExpression = Expression.Call(null, asQuerableMethod, accumulativeParameter); } else { var queryableType = typeof(IEnumerable<>).MakeGenericType(baseType); - asQuerableExpression = Expression.Convert(accum, queryableType); + asQuerableExpression = Expression.Convert(accumulativeParameter, queryableType); } // $count is a virtual property, so there's not a propertyLambda to create. @@ -639,9 +686,27 @@ private Expression BindAccessor(QueryNode node, Expression baseElement = null) } } - private Expression CreatePropertyAccessExpression(Expression source, IEdmProperty property, string propertyPath = null) + /// + /// Returns an that represents access to . + /// + /// The EDM property which access expression to return. + /// The source that contains the . + /// The property access . + protected virtual Expression CreatePropertyAccessExpression(Expression source, IEdmProperty edmProperty) + { + return CreatePropertyAccessExpression(source, edmProperty, null); + } + + /// + /// Returns an that represents access to . + /// + /// The EDM property which access expression to return. + /// The source that contains the . + /// + /// The property access . + protected virtual Expression CreatePropertyAccessExpression(Expression source, IEdmProperty edmProperty, string propertyPath) { - string propertyName = EdmLibHelpers.GetClrPropertyName(property, Model); + string propertyName = EdmLibHelpers.GetClrPropertyName(edmProperty, Model); propertyPath = propertyPath ?? propertyName; if (QuerySettings.HandleNullPropagation == HandleNullPropagationOption.True && IsNullable(source.Type) && source != this._lambdaParameter) @@ -666,7 +731,13 @@ private Expression CreatePropertyAccessExpression(Expression source, IEdmPropert } } - private Expression CreateOpenPropertyAccessExpression(SingleValueOpenPropertyAccessNode openNode) + /// + /// Binds a to create a LINQ that + /// represents the semantics of the . + /// + /// The node to bind. + /// The LINQ created. + protected virtual Expression CreateOpenPropertyAccessExpression(SingleValueOpenPropertyAccessNode openNode) { Expression sourceAccessor = BindAccessor(openNode.Source); diff --git a/src/Microsoft.AspNet.OData.Shared/Query/Expressions/DefaultODataBinderProvider.cs b/src/Microsoft.AspNet.OData.Shared/Query/Expressions/DefaultODataBinderProvider.cs index 8174ca08e5..414f217a92 100644 --- a/src/Microsoft.AspNet.OData.Shared/Query/Expressions/DefaultODataBinderProvider.cs +++ b/src/Microsoft.AspNet.OData.Shared/Query/Expressions/DefaultODataBinderProvider.cs @@ -1,6 +1,10 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. +using System; +using Microsoft.OData.Edm; +using Microsoft.OData.UriParser.Aggregation; + namespace Microsoft.AspNet.OData.Query.Expressions { /// @@ -13,5 +17,12 @@ public override SelectExpandBinder GetSelectExpandBinder(ODataQuerySettings sett { return new SelectExpandBinder(settings, selectExpandQuery); } + + /// + public override AggregationBinder GetAggregationBinder(ODataQuerySettings settings, IServiceProvider requestContainer, Type elementType, + IEdmModel model, TransformationNode transformation) + { + return new AggregationBinder(settings, requestContainer, elementType, model, transformation); + } } } diff --git a/src/Microsoft.AspNet.OData.Shared/Query/Expressions/ODataBinderProvider.cs b/src/Microsoft.AspNet.OData.Shared/Query/Expressions/ODataBinderProvider.cs index 00f464dd8a..1ed79160aa 100644 --- a/src/Microsoft.AspNet.OData.Shared/Query/Expressions/ODataBinderProvider.cs +++ b/src/Microsoft.AspNet.OData.Shared/Query/Expressions/ODataBinderProvider.cs @@ -1,6 +1,10 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. +using System; +using Microsoft.OData.Edm; +using Microsoft.OData.UriParser.Aggregation; + namespace Microsoft.AspNet.OData.Query.Expressions { /// @@ -16,5 +20,17 @@ public abstract class ODataBinderProvider /// The . public abstract SelectExpandBinder GetSelectExpandBinder(ODataQuerySettings settings, SelectExpandQueryOption selectExpandQuery); + + /// + /// Gets a . + /// + /// The to use during binding. + /// ClrType for result of transformations. + /// The request container. + /// The EDM model. + /// The transformation node. + /// The . + public abstract AggregationBinder GetAggregationBinder(ODataQuerySettings settings, IServiceProvider requestContainer, + Type elementType, IEdmModel model, TransformationNode transformation); } } diff --git a/test/UnitTest/Microsoft.AspNet.OData.Test/PublicApi/Microsoft.AspNet.OData.PublicApi.bsl b/test/UnitTest/Microsoft.AspNet.OData.Test/PublicApi/Microsoft.AspNet.OData.PublicApi.bsl index b8ef99761a..824959d20e 100644 --- a/test/UnitTest/Microsoft.AspNet.OData.Test/PublicApi/Microsoft.AspNet.OData.PublicApi.bsl +++ b/test/UnitTest/Microsoft.AspNet.OData.Test/PublicApi/Microsoft.AspNet.OData.PublicApi.bsl @@ -3358,12 +3358,28 @@ public abstract class Microsoft.AspNet.OData.Query.Expressions.ExpressionBinderB public abstract class Microsoft.AspNet.OData.Query.Expressions.ODataBinderProvider { protected ODataBinderProvider () + public abstract AggregationBinder GetAggregationBinder (ODataQuerySettings settings, System.IServiceProvider requestContainer, System.Type elementType, Microsoft.OData.Edm.IEdmModel model, Microsoft.OData.UriParser.Aggregation.TransformationNode transformation) public abstract SelectExpandBinder GetSelectExpandBinder (ODataQuerySettings settings, SelectExpandQueryOption selectExpandQuery) } +public class Microsoft.AspNet.OData.Query.Expressions.AggregationBinder : ExpressionBinderBase { + protected AggregationBinder (ODataQuerySettings settings, System.IServiceProvider requestContainer, System.Type elementType, Microsoft.OData.Edm.IEdmModel model, Microsoft.OData.UriParser.Aggregation.TransformationNode transformation) + + System.Type ResultClrType { public get; } + + public virtual System.Linq.IQueryable Bind (System.Linq.IQueryable query) + protected virtual System.Linq.Expressions.Expression CreateEntitySetAggregateExpression (System.Linq.Expressions.ParameterExpression accumulativeParameter, Microsoft.OData.UriParser.Aggregation.EntitySetAggregateExpression expression, System.Type baseType) + protected virtual System.Linq.Expressions.Expression CreateOpenPropertyAccessExpression (Microsoft.OData.UriParser.SingleValueOpenPropertyAccessNode openNode) + protected virtual System.Linq.Expressions.Expression CreatePropertyAccessExpression (System.Linq.Expressions.Expression source, Microsoft.OData.Edm.IEdmProperty edmProperty) + protected virtual System.Linq.Expressions.Expression CreatePropertyAccessExpression (System.Linq.Expressions.Expression source, Microsoft.OData.Edm.IEdmProperty edmProperty, string propertyPath) + protected virtual System.Linq.Expressions.Expression CreatePropertyAggregateExpression (System.Linq.Expressions.ParameterExpression accumulativeParameter, Microsoft.OData.UriParser.Aggregation.AggregateExpression expression, System.Type baseType) + internal virtual bool IsClassicEF (System.Linq.IQueryable query) +} + public class Microsoft.AspNet.OData.Query.Expressions.DefaultODataBinderProvider : ODataBinderProvider { public DefaultODataBinderProvider () + public virtual AggregationBinder GetAggregationBinder (ODataQuerySettings settings, System.IServiceProvider requestContainer, System.Type elementType, Microsoft.OData.Edm.IEdmModel model, Microsoft.OData.UriParser.Aggregation.TransformationNode transformation) public virtual SelectExpandBinder GetSelectExpandBinder (ODataQuerySettings settings, SelectExpandQueryOption selectExpandQuery) } diff --git a/test/UnitTest/Microsoft.AspNetCore.OData.Test/PublicApi/Microsoft.AspNetCore.OData.PublicApi.bsl b/test/UnitTest/Microsoft.AspNetCore.OData.Test/PublicApi/Microsoft.AspNetCore.OData.PublicApi.bsl index c23402bd9f..a3531a3e29 100644 --- a/test/UnitTest/Microsoft.AspNetCore.OData.Test/PublicApi/Microsoft.AspNetCore.OData.PublicApi.bsl +++ b/test/UnitTest/Microsoft.AspNetCore.OData.Test/PublicApi/Microsoft.AspNetCore.OData.PublicApi.bsl @@ -3493,12 +3493,28 @@ public abstract class Microsoft.AspNet.OData.Query.Expressions.ExpressionBinderB public abstract class Microsoft.AspNet.OData.Query.Expressions.ODataBinderProvider { protected ODataBinderProvider () + public abstract AggregationBinder GetAggregationBinder (ODataQuerySettings settings, System.IServiceProvider requestContainer, System.Type elementType, Microsoft.OData.Edm.IEdmModel model, Microsoft.OData.UriParser.Aggregation.TransformationNode transformation) public abstract SelectExpandBinder GetSelectExpandBinder (ODataQuerySettings settings, SelectExpandQueryOption selectExpandQuery) } +public class Microsoft.AspNet.OData.Query.Expressions.AggregationBinder : ExpressionBinderBase { + protected AggregationBinder (ODataQuerySettings settings, System.IServiceProvider requestContainer, System.Type elementType, Microsoft.OData.Edm.IEdmModel model, Microsoft.OData.UriParser.Aggregation.TransformationNode transformation) + + System.Type ResultClrType { public get; } + + public virtual System.Linq.IQueryable Bind (System.Linq.IQueryable query) + protected virtual System.Linq.Expressions.Expression CreateEntitySetAggregateExpression (System.Linq.Expressions.ParameterExpression accumulativeParameter, Microsoft.OData.UriParser.Aggregation.EntitySetAggregateExpression expression, System.Type baseType) + protected virtual System.Linq.Expressions.Expression CreateOpenPropertyAccessExpression (Microsoft.OData.UriParser.SingleValueOpenPropertyAccessNode openNode) + protected virtual System.Linq.Expressions.Expression CreatePropertyAccessExpression (System.Linq.Expressions.Expression source, Microsoft.OData.Edm.IEdmProperty edmProperty) + protected virtual System.Linq.Expressions.Expression CreatePropertyAccessExpression (System.Linq.Expressions.Expression source, Microsoft.OData.Edm.IEdmProperty edmProperty, string propertyPath) + protected virtual System.Linq.Expressions.Expression CreatePropertyAggregateExpression (System.Linq.Expressions.ParameterExpression accumulativeParameter, Microsoft.OData.UriParser.Aggregation.AggregateExpression expression, System.Type baseType) + internal virtual bool IsClassicEF (System.Linq.IQueryable query) +} + public class Microsoft.AspNet.OData.Query.Expressions.DefaultODataBinderProvider : ODataBinderProvider { public DefaultODataBinderProvider () + public virtual AggregationBinder GetAggregationBinder (ODataQuerySettings settings, System.IServiceProvider requestContainer, System.Type elementType, Microsoft.OData.Edm.IEdmModel model, Microsoft.OData.UriParser.Aggregation.TransformationNode transformation) public virtual SelectExpandBinder GetSelectExpandBinder (ODataQuerySettings settings, SelectExpandQueryOption selectExpandQuery) } From 2e88f29570c117c91428cfcb1d4f4bc9fe750d4c Mon Sep 17 00:00:00 2001 From: Sergey Saveliev Date: Fri, 4 Oct 2019 10:25:08 +0300 Subject: [PATCH 3/3] Code style fix: curly brackets for if statements --- src/Microsoft.AspNet.OData.Shared/Query/ApplyQueryOption.cs | 2 ++ .../Query/SelectExpandQueryOption.cs | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/src/Microsoft.AspNet.OData.Shared/Query/ApplyQueryOption.cs b/src/Microsoft.AspNet.OData.Shared/Query/ApplyQueryOption.cs index 82931be96b..4db2f12a60 100644 --- a/src/Microsoft.AspNet.OData.Shared/Query/ApplyQueryOption.cs +++ b/src/Microsoft.AspNet.OData.Shared/Query/ApplyQueryOption.cs @@ -56,7 +56,9 @@ public ApplyQueryOption(string rawValue, ODataQueryContext context, ODataQueryOp _queryOptionParser = queryOptionParser; ResultClrType = Context.ElementClrType; if (context.RequestContainer != null) + { _binderProvider = context.RequestContainer.GetRequiredService(); + } _binderProvider = _binderProvider ?? new DefaultODataBinderProvider(); } diff --git a/src/Microsoft.AspNet.OData.Shared/Query/SelectExpandQueryOption.cs b/src/Microsoft.AspNet.OData.Shared/Query/SelectExpandQueryOption.cs index 0d5a2004e9..23cef82843 100644 --- a/src/Microsoft.AspNet.OData.Shared/Query/SelectExpandQueryOption.cs +++ b/src/Microsoft.AspNet.OData.Shared/Query/SelectExpandQueryOption.cs @@ -66,7 +66,9 @@ public SelectExpandQueryOption(string select, string expand, ODataQueryContext c _queryOptionParser = queryOptionParser; if (context.RequestContainer != null) + { _binderProvider = context.RequestContainer.GetRequiredService(); + } _binderProvider = _binderProvider ?? new DefaultODataBinderProvider(); } @@ -110,7 +112,9 @@ internal SelectExpandQueryOption(string select, string expand, ODataQueryContext context.RequestContainer); if (context.RequestContainer != null) + { _binderProvider = context.RequestContainer.GetRequiredService(); + } _binderProvider = _binderProvider ?? new DefaultODataBinderProvider(); }