diff --git a/src/Microsoft.AspNetCore.OData/Microsoft.AspNetCore.OData.xml b/src/Microsoft.AspNetCore.OData/Microsoft.AspNetCore.OData.xml index 5e9fd87f3..390df02a6 100644 --- a/src/Microsoft.AspNetCore.OData/Microsoft.AspNetCore.OData.xml +++ b/src/Microsoft.AspNetCore.OData/Microsoft.AspNetCore.OData.xml @@ -1102,20 +1102,6 @@ The type to test. True if the type is a DateTime; false otherwise. - - - Determine if a type is a . - - The type to test. - True if the type is a DateOnly; false otherwise. - - - - Determine if a type is a . - - The type to test. - True if the type is a TimeOnly; false otherwise. - Determine if a type is a TimeSpan. @@ -13000,6 +12986,14 @@ + + + Tests whether to apply $count on the . + + The entity set to test. + The route options. + True/false to identify whether to apply $count. + The convention for . @@ -13108,6 +13102,16 @@ The declaring name (out). The http method name or null. + + + OData spec: To request only the number of items of a collection of entities or items of a collection-valued property, + the client issues a GET request with /$count appended to the resource path of the collection. + + The property to test. + The http method. + The route options. + True/false to identify whether to apply $count. + A context object for . @@ -13280,6 +13284,35 @@ + + + Tests whether to apply routings for the given property. + + The property to test. + The http method. + The route options. + True/false to identify whether to apply routings for the given property. + + + + OData spec: To retrieve the raw value of a primitive type property, the client sends a GET request to the property value URL. + So, let's apply $value for the "Get" and non-collection primitive property + + The property to test. + The http method. + The route options. + True/false to identify whether to apply $value. + + + + OData spec: To request only the number of items of a collection of entities or items of a collection-valued property, + the client issues a GET request with /$count appended to the resource path of the collection. + + The property to test. + The http method. + The route options. + True/false to identify whether to apply $count. + An implementation of that handles entity reference manipulations. @@ -13656,6 +13689,16 @@ Initializes a new instance of the class. + + + Gets/sets a value indicating whether to enable $count in conventional routing. + + + + + Gets/sets a value indicating whether to enable $value in conventional routing. + + Gets/sets a value indicating whether to enable case insensitive for the action name in conventional routing. @@ -14933,3 +14976,19 @@ +ummary> + The value segment. + + + + Gets the value segment. + + + + + + + + + + diff --git a/src/Microsoft.AspNetCore.OData/PublicAPI.Unshipped.txt b/src/Microsoft.AspNetCore.OData/PublicAPI.Unshipped.txt index 699ca163f..96497723a 100644 --- a/src/Microsoft.AspNetCore.OData/PublicAPI.Unshipped.txt +++ b/src/Microsoft.AspNetCore.OData/PublicAPI.Unshipped.txt @@ -1331,6 +1331,12 @@ Microsoft.AspNetCore.OData.Routing.ODataRouteOptions.EnableActionNameCaseInsensi Microsoft.AspNetCore.OData.Routing.ODataRouteOptions.EnableActionNameCaseInsensitive.set -> void Microsoft.AspNetCore.OData.Routing.ODataRouteOptions.EnableControllerNameCaseInsensitive.get -> bool Microsoft.AspNetCore.OData.Routing.ODataRouteOptions.EnableControllerNameCaseInsensitive.set -> void +Microsoft.AspNetCore.OData.Routing.ODataRouteOptions.EnableConventionalRoutingIfODataRoutingPresent.get -> bool +Microsoft.AspNetCore.OData.Routing.ODataRouteOptions.EnableConventionalRoutingIfODataRoutingPresent.set -> void +Microsoft.AspNetCore.OData.Routing.ODataRouteOptions.EnableDollarCountRouting.get -> bool +Microsoft.AspNetCore.OData.Routing.ODataRouteOptions.EnableDollarCountRouting.set -> void +Microsoft.AspNetCore.OData.Routing.ODataRouteOptions.EnableDollarValueRouting.get -> bool +Microsoft.AspNetCore.OData.Routing.ODataRouteOptions.EnableDollarValueRouting.set -> void Microsoft.AspNetCore.OData.Routing.ODataRouteOptions.EnableKeyAsSegment.get -> bool Microsoft.AspNetCore.OData.Routing.ODataRouteOptions.EnableKeyAsSegment.set -> void Microsoft.AspNetCore.OData.Routing.ODataRouteOptions.EnableKeyInParenthesis.get -> bool @@ -1978,12 +1984,14 @@ virtual Microsoft.AspNetCore.OData.Routing.Conventions.EntityRoutingConvention.A virtual Microsoft.AspNetCore.OData.Routing.Conventions.EntityRoutingConvention.Order.get -> int virtual Microsoft.AspNetCore.OData.Routing.Conventions.EntitySetRoutingConvention.AppliesToAction(Microsoft.AspNetCore.OData.Routing.Conventions.ODataControllerActionContext context) -> bool virtual Microsoft.AspNetCore.OData.Routing.Conventions.EntitySetRoutingConvention.AppliesToController(Microsoft.AspNetCore.OData.Routing.Conventions.ODataControllerActionContext context) -> bool +virtual Microsoft.AspNetCore.OData.Routing.Conventions.EntitySetRoutingConvention.CanApplyDollarCount(Microsoft.OData.Edm.IEdmEntitySet entitySet, Microsoft.AspNetCore.OData.Routing.ODataRouteOptions routeOptions) -> bool virtual Microsoft.AspNetCore.OData.Routing.Conventions.EntitySetRoutingConvention.Order.get -> int virtual Microsoft.AspNetCore.OData.Routing.Conventions.MetadataRoutingConvention.AppliesToAction(Microsoft.AspNetCore.OData.Routing.Conventions.ODataControllerActionContext context) -> bool virtual Microsoft.AspNetCore.OData.Routing.Conventions.MetadataRoutingConvention.AppliesToController(Microsoft.AspNetCore.OData.Routing.Conventions.ODataControllerActionContext context) -> bool virtual Microsoft.AspNetCore.OData.Routing.Conventions.MetadataRoutingConvention.Order.get -> int virtual Microsoft.AspNetCore.OData.Routing.Conventions.NavigationRoutingConvention.AppliesToAction(Microsoft.AspNetCore.OData.Routing.Conventions.ODataControllerActionContext context) -> bool virtual Microsoft.AspNetCore.OData.Routing.Conventions.NavigationRoutingConvention.AppliesToController(Microsoft.AspNetCore.OData.Routing.Conventions.ODataControllerActionContext context) -> bool +virtual Microsoft.AspNetCore.OData.Routing.Conventions.NavigationRoutingConvention.CanApplyDollarCount(Microsoft.OData.Edm.IEdmNavigationProperty edmProperty, string method, Microsoft.AspNetCore.OData.Routing.ODataRouteOptions routeOptions) -> bool virtual Microsoft.AspNetCore.OData.Routing.Conventions.NavigationRoutingConvention.Order.get -> int virtual Microsoft.AspNetCore.OData.Routing.Conventions.OperationImportRoutingConvention.AppliesToAction(Microsoft.AspNetCore.OData.Routing.Conventions.ODataControllerActionContext context) -> bool virtual Microsoft.AspNetCore.OData.Routing.Conventions.OperationImportRoutingConvention.AppliesToController(Microsoft.AspNetCore.OData.Routing.Conventions.ODataControllerActionContext context) -> bool @@ -1991,6 +1999,9 @@ virtual Microsoft.AspNetCore.OData.Routing.Conventions.OperationImportRoutingCon virtual Microsoft.AspNetCore.OData.Routing.Conventions.OperationRoutingConvention.AppliesToController(Microsoft.AspNetCore.OData.Routing.Conventions.ODataControllerActionContext context) -> bool virtual Microsoft.AspNetCore.OData.Routing.Conventions.PropertyRoutingConvention.AppliesToAction(Microsoft.AspNetCore.OData.Routing.Conventions.ODataControllerActionContext context) -> bool virtual Microsoft.AspNetCore.OData.Routing.Conventions.PropertyRoutingConvention.AppliesToController(Microsoft.AspNetCore.OData.Routing.Conventions.ODataControllerActionContext context) -> bool +virtual Microsoft.AspNetCore.OData.Routing.Conventions.PropertyRoutingConvention.CanApply(Microsoft.OData.Edm.IEdmProperty edmProperty, string method, Microsoft.AspNetCore.OData.Routing.ODataRouteOptions routeOptions) -> bool +virtual Microsoft.AspNetCore.OData.Routing.Conventions.PropertyRoutingConvention.CanApplyDollarCount(Microsoft.OData.Edm.IEdmProperty edmProperty, string method, Microsoft.AspNetCore.OData.Routing.ODataRouteOptions routeOptions) -> bool +virtual Microsoft.AspNetCore.OData.Routing.Conventions.PropertyRoutingConvention.CanApplyDollarValue(Microsoft.OData.Edm.IEdmProperty edmProperty, string method, Microsoft.AspNetCore.OData.Routing.ODataRouteOptions routeOptions) -> bool virtual Microsoft.AspNetCore.OData.Routing.Conventions.PropertyRoutingConvention.Order.get -> int virtual Microsoft.AspNetCore.OData.Routing.Conventions.RefRoutingConvention.AppliesToController(Microsoft.AspNetCore.OData.Routing.Conventions.ODataControllerActionContext context) -> bool virtual Microsoft.AspNetCore.OData.Routing.Conventions.RefRoutingConvention.Order.get -> int diff --git a/src/Microsoft.AspNetCore.OData/Routing/Conventions/EntitySetRoutingConvention.cs b/src/Microsoft.AspNetCore.OData/Routing/Conventions/EntitySetRoutingConvention.cs index 53c7563a4..158430c94 100644 --- a/src/Microsoft.AspNetCore.OData/Routing/Conventions/EntitySetRoutingConvention.cs +++ b/src/Microsoft.AspNetCore.OData/Routing/Conventions/EntitySetRoutingConvention.cs @@ -97,7 +97,7 @@ public virtual bool AppliesToAction(ODataControllerActionContext context) return ProcessEntitySetAction(actionPrefix, entitySet, castType, context, action); } - private static bool ProcessEntitySetAction(string actionName, IEdmEntitySet entitySet, IEdmStructuredType castType, + private bool ProcessEntitySetAction(string actionName, IEdmEntitySet entitySet, IEdmStructuredType castType, ODataControllerActionContext context, ActionModel action) { StringComparison actionNameComparison = context.Options?.RouteOptions?.EnableActionNameCaseInsensitive == true ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal; @@ -126,21 +126,25 @@ private static bool ProcessEntitySetAction(string actionName, IEdmEntitySet enti ODataPathTemplate template = new ODataPathTemplate(segments); action.AddSelector("Get", context.Prefix, context.Model, template, context.Options?.RouteOptions); - // GET ~/Customers/$count or GET ~/Customers/Ns.VipCustomer/$count - segments = new List + if (CanApplyDollarCount(entitySet, context.Options?.RouteOptions)) { - new EntitySetSegmentTemplate(entitySet) - }; + // GET ~/Customers/$count or GET ~/Customers/Ns.VipCustomer/$count + segments = new List + { + new EntitySetSegmentTemplate(entitySet) + }; - if (castType != null) - { - segments.Add(new CastSegmentTemplate(castCollectionType, entityCollectionType, entitySet)); - } + if (castType != null) + { + segments.Add(new CastSegmentTemplate(castCollectionType, entityCollectionType, entitySet)); + } - segments.Add(CountSegmentTemplate.Instance); + segments.Add(CountSegmentTemplate.Instance); + + template = new ODataPathTemplate(segments); + action.AddSelector("Get", context.Prefix, context.Model, template, context.Options?.RouteOptions); + } - template = new ODataPathTemplate(segments); - action.AddSelector("Get", context.Prefix, context.Model, template, context.Options?.RouteOptions); return true; } else if (actionName.Equals("Post", actionNameComparison) || actionName.Equals($"Post{entitySet.EntityType().Name}", actionNameComparison)) @@ -183,5 +187,14 @@ private static bool ProcessEntitySetAction(string actionName, IEdmEntitySet enti return false; } + + /// + /// Tests whether to apply $count on the . + /// + /// The entity set to test. + /// The route options. + /// True/false to identify whether to apply $count. + protected virtual bool CanApplyDollarCount(IEdmEntitySet entitySet, ODataRouteOptions routeOptions) + => routeOptions != null ? routeOptions.EnableDollarCountRouting : false; } } diff --git a/src/Microsoft.AspNetCore.OData/Routing/Conventions/NavigationRoutingConvention.cs b/src/Microsoft.AspNetCore.OData/Routing/Conventions/NavigationRoutingConvention.cs index ca21605a2..42a9d9b7a 100644 --- a/src/Microsoft.AspNetCore.OData/Routing/Conventions/NavigationRoutingConvention.cs +++ b/src/Microsoft.AspNetCore.OData/Routing/Conventions/NavigationRoutingConvention.cs @@ -141,7 +141,7 @@ public virtual bool AppliesToAction(ODataControllerActionContext context) AddSelector(method, context, action, navigationSource, declared, declaringEntityType, navigationProperty, hasKeyParameter, false); - if (CanApplyDollarCount(navigationProperty, method)) + if (CanApplyDollarCount(navigationProperty, method, context.Options?.RouteOptions)) { AddSelector(method, context, action, navigationSource, declared, declaringEntityType, navigationProperty, hasKeyParameter, true); } @@ -244,11 +244,25 @@ internal static string SplitActionName(string actionName, out string property, o return method; } - // OData spec: To request only the number of items of a collection of entities or items of a collection-valued property, - // the client issues a GET request with /$count appended to the resource path of the collection. - private static bool CanApplyDollarCount(IEdmNavigationProperty edmProperty, string method) + /// + /// OData spec: To request only the number of items of a collection of entities or items of a collection-valued property, + /// the client issues a GET request with /$count appended to the resource path of the collection. + /// + /// The property to test. + /// The http method. + /// The route options. + /// True/false to identify whether to apply $count. + protected virtual bool CanApplyDollarCount(IEdmNavigationProperty edmProperty, string method, ODataRouteOptions routeOptions) { - Contract.Assert(edmProperty != null); + if(edmProperty == null) + { + throw Error.ArgumentNull(nameof(edmProperty)); + } + + if (routeOptions != null && !routeOptions.EnableDollarCountRouting) + { + return false; + } return method == "Get" && edmProperty.Type.IsCollection(); } diff --git a/src/Microsoft.AspNetCore.OData/Routing/Conventions/PropertyRoutingConvention.cs b/src/Microsoft.AspNetCore.OData/Routing/Conventions/PropertyRoutingConvention.cs index bc6134263..cc1824fcc 100644 --- a/src/Microsoft.AspNetCore.OData/Routing/Conventions/PropertyRoutingConvention.cs +++ b/src/Microsoft.AspNetCore.OData/Routing/Conventions/PropertyRoutingConvention.cs @@ -113,7 +113,7 @@ public virtual bool AppliesToAction(ODataControllerActionContext context) return false; } - if (!CanApply(edmProperty, method)) + if (!CanApply(edmProperty, method, context.Options?.RouteOptions)) { return false; } @@ -166,12 +166,12 @@ public virtual bool AppliesToAction(ODataControllerActionContext context) AddSelector(method, context, action, navigationSource, (IEdmStructuralProperty)edmProperty, castComplexType, declaringEntityType, false, false); - if (CanApplyDollarCount(edmProperty, method)) + if (CanApplyDollarCount(edmProperty, method, context.Options?.RouteOptions)) { AddSelector(method, context, action, navigationSource, (IEdmStructuralProperty)edmProperty, castComplexType, declaringEntityType, false, true); } - if (CanApplyDollarValue(edmProperty, method)) + if (CanApplyDollarValue(edmProperty, method, context.Options?.RouteOptions)) { AddSelector(method, context, action, navigationSource, (IEdmStructuralProperty)edmProperty, castComplexType, declaringEntityType, true, false); } @@ -282,9 +282,19 @@ private static string Match(string text, out string declared) return text; } - private static bool CanApply(IEdmProperty edmProperty, string method) + /// + /// Tests whether to apply routings for the given property. + /// + /// The property to test. + /// The http method. + /// The route options. + /// True/false to identify whether to apply routings for the given property. + protected virtual bool CanApply(IEdmProperty edmProperty, string method, ODataRouteOptions routeOptions) { - Contract.Assert(edmProperty != null); + if (edmProperty == null) + { + throw Error.ArgumentNull(nameof(edmProperty)); + } bool isCollection = edmProperty.Type.IsCollection(); @@ -311,20 +321,48 @@ private static bool CanApply(IEdmProperty edmProperty, string method) return true; } - // OData spec: To retrieve the raw value of a primitive type property, the client sends a GET request to the property value URL. - // So, let's apply $value for the "Get" and non-collection primitive property - private static bool CanApplyDollarValue(IEdmProperty edmProperty, string method) + /// + /// OData spec: To retrieve the raw value of a primitive type property, the client sends a GET request to the property value URL. + /// So, let's apply $value for the "Get" and non-collection primitive property + /// + /// The property to test. + /// The http method. + /// The route options. + /// True/false to identify whether to apply $value. + protected virtual bool CanApplyDollarValue(IEdmProperty edmProperty, string method, ODataRouteOptions routeOptions) { - Contract.Assert(edmProperty != null); + if (edmProperty == null) + { + throw Error.ArgumentNull(nameof(edmProperty)); + } + + if (routeOptions != null && !routeOptions.EnableDollarValueRouting) + { + return false; + } return method == "Get" && !edmProperty.Type.IsCollection() && (edmProperty.Type.IsPrimitive() || edmProperty.Type.IsEnum()); } - // OData spec: To request only the number of items of a collection of entities or items of a collection-valued property, - // the client issues a GET request with /$count appended to the resource path of the collection. - private static bool CanApplyDollarCount(IEdmProperty edmProperty, string method) + /// + /// OData spec: To request only the number of items of a collection of entities or items of a collection-valued property, + /// the client issues a GET request with /$count appended to the resource path of the collection. + /// + /// The property to test. + /// The http method. + /// The route options. + /// True/false to identify whether to apply $count. + protected virtual bool CanApplyDollarCount(IEdmProperty edmProperty, string method, ODataRouteOptions routeOptions) { - Contract.Assert(edmProperty != null); + if (edmProperty == null) + { + throw Error.ArgumentNull(nameof(edmProperty)); + } + + if (routeOptions != null && !routeOptions.EnableDollarCountRouting) + { + return false; + } return method == "Get" && edmProperty.Type.IsCollection(); } diff --git a/src/Microsoft.AspNetCore.OData/Routing/ODataRouteOptions.cs b/src/Microsoft.AspNetCore.OData/Routing/ODataRouteOptions.cs index eeaad4308..28c2e1b32 100644 --- a/src/Microsoft.AspNetCore.OData/Routing/ODataRouteOptions.cs +++ b/src/Microsoft.AspNetCore.OData/Routing/ODataRouteOptions.cs @@ -33,6 +33,16 @@ public ODataRouteOptions() _enableUnqualifiedOperationCall = true; } + /// + /// Gets/sets a value indicating whether to enable $count in conventional routing. + /// + public bool EnableDollarCountRouting { get; set; } = true; + + /// + /// Gets/sets a value indicating whether to enable $value in conventional routing. + /// + public bool EnableDollarValueRouting { get; set; } = true; + /// /// Gets/sets a value indicating whether to enable case insensitive for the action name in conventional routing. /// diff --git a/test/Microsoft.AspNetCore.OData.Tests/PublicApi/Microsoft.AspNetCore.OData.PublicApi.Net6.bsl b/test/Microsoft.AspNetCore.OData.Tests/PublicApi/Microsoft.AspNetCore.OData.PublicApi.Net6.bsl index 44364d55d..6eb2500c8 100644 --- a/test/Microsoft.AspNetCore.OData.Tests/PublicApi/Microsoft.AspNetCore.OData.PublicApi.Net6.bsl +++ b/test/Microsoft.AspNetCore.OData.Tests/PublicApi/Microsoft.AspNetCore.OData.PublicApi.Net6.bsl @@ -1860,6 +1860,8 @@ public class Microsoft.AspNetCore.OData.Routing.ODataRouteOptions { bool EnableActionNameCaseInsensitive { public get; public set; } bool EnableControllerNameCaseInsensitive { public get; public set; } + bool EnableDollarCountRouting { public get; public set; } + bool EnableDollarValueRouting { public get; public set; } bool EnableKeyAsSegment { public get; public set; } bool EnableKeyInParenthesis { public get; public set; } bool EnableNonParenthesisForEmptyParameterFunction { public get; public set; } @@ -3159,6 +3161,7 @@ public class Microsoft.AspNetCore.OData.Routing.Conventions.EntitySetRoutingConv public virtual bool AppliesToAction (Microsoft.AspNetCore.OData.Routing.Conventions.ODataControllerActionContext context) public virtual bool AppliesToController (Microsoft.AspNetCore.OData.Routing.Conventions.ODataControllerActionContext context) + protected virtual bool CanApplyDollarCount (Microsoft.OData.Edm.IEdmEntitySet entitySet, Microsoft.AspNetCore.OData.Routing.ODataRouteOptions routeOptions) } public class Microsoft.AspNetCore.OData.Routing.Conventions.FunctionRoutingConvention : Microsoft.AspNetCore.OData.Routing.Conventions.OperationRoutingConvention, IODataControllerActionConvention { @@ -3186,6 +3189,7 @@ public class Microsoft.AspNetCore.OData.Routing.Conventions.NavigationRoutingCon public virtual bool AppliesToAction (Microsoft.AspNetCore.OData.Routing.Conventions.ODataControllerActionContext context) public virtual bool AppliesToController (Microsoft.AspNetCore.OData.Routing.Conventions.ODataControllerActionContext context) + protected virtual bool CanApplyDollarCount (Microsoft.OData.Edm.IEdmNavigationProperty edmProperty, string method, Microsoft.AspNetCore.OData.Routing.ODataRouteOptions routeOptions) } public class Microsoft.AspNetCore.OData.Routing.Conventions.ODataControllerActionContext { @@ -3218,6 +3222,9 @@ public class Microsoft.AspNetCore.OData.Routing.Conventions.PropertyRoutingConve public virtual bool AppliesToAction (Microsoft.AspNetCore.OData.Routing.Conventions.ODataControllerActionContext context) public virtual bool AppliesToController (Microsoft.AspNetCore.OData.Routing.Conventions.ODataControllerActionContext context) + protected virtual bool CanApply (Microsoft.OData.Edm.IEdmProperty edmProperty, string method, Microsoft.AspNetCore.OData.Routing.ODataRouteOptions routeOptions) + protected virtual bool CanApplyDollarCount (Microsoft.OData.Edm.IEdmProperty edmProperty, string method, Microsoft.AspNetCore.OData.Routing.ODataRouteOptions routeOptions) + protected virtual bool CanApplyDollarValue (Microsoft.OData.Edm.IEdmProperty edmProperty, string method, Microsoft.AspNetCore.OData.Routing.ODataRouteOptions routeOptions) } public class Microsoft.AspNetCore.OData.Routing.Conventions.RefRoutingConvention : IODataControllerActionConvention { 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 44364d55d..6eb2500c8 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 @@ -1860,6 +1860,8 @@ public class Microsoft.AspNetCore.OData.Routing.ODataRouteOptions { bool EnableActionNameCaseInsensitive { public get; public set; } bool EnableControllerNameCaseInsensitive { public get; public set; } + bool EnableDollarCountRouting { public get; public set; } + bool EnableDollarValueRouting { public get; public set; } bool EnableKeyAsSegment { public get; public set; } bool EnableKeyInParenthesis { public get; public set; } bool EnableNonParenthesisForEmptyParameterFunction { public get; public set; } @@ -3159,6 +3161,7 @@ public class Microsoft.AspNetCore.OData.Routing.Conventions.EntitySetRoutingConv public virtual bool AppliesToAction (Microsoft.AspNetCore.OData.Routing.Conventions.ODataControllerActionContext context) public virtual bool AppliesToController (Microsoft.AspNetCore.OData.Routing.Conventions.ODataControllerActionContext context) + protected virtual bool CanApplyDollarCount (Microsoft.OData.Edm.IEdmEntitySet entitySet, Microsoft.AspNetCore.OData.Routing.ODataRouteOptions routeOptions) } public class Microsoft.AspNetCore.OData.Routing.Conventions.FunctionRoutingConvention : Microsoft.AspNetCore.OData.Routing.Conventions.OperationRoutingConvention, IODataControllerActionConvention { @@ -3186,6 +3189,7 @@ public class Microsoft.AspNetCore.OData.Routing.Conventions.NavigationRoutingCon public virtual bool AppliesToAction (Microsoft.AspNetCore.OData.Routing.Conventions.ODataControllerActionContext context) public virtual bool AppliesToController (Microsoft.AspNetCore.OData.Routing.Conventions.ODataControllerActionContext context) + protected virtual bool CanApplyDollarCount (Microsoft.OData.Edm.IEdmNavigationProperty edmProperty, string method, Microsoft.AspNetCore.OData.Routing.ODataRouteOptions routeOptions) } public class Microsoft.AspNetCore.OData.Routing.Conventions.ODataControllerActionContext { @@ -3218,6 +3222,9 @@ public class Microsoft.AspNetCore.OData.Routing.Conventions.PropertyRoutingConve public virtual bool AppliesToAction (Microsoft.AspNetCore.OData.Routing.Conventions.ODataControllerActionContext context) public virtual bool AppliesToController (Microsoft.AspNetCore.OData.Routing.Conventions.ODataControllerActionContext context) + protected virtual bool CanApply (Microsoft.OData.Edm.IEdmProperty edmProperty, string method, Microsoft.AspNetCore.OData.Routing.ODataRouteOptions routeOptions) + protected virtual bool CanApplyDollarCount (Microsoft.OData.Edm.IEdmProperty edmProperty, string method, Microsoft.AspNetCore.OData.Routing.ODataRouteOptions routeOptions) + protected virtual bool CanApplyDollarValue (Microsoft.OData.Edm.IEdmProperty edmProperty, string method, Microsoft.AspNetCore.OData.Routing.ODataRouteOptions routeOptions) } public class Microsoft.AspNetCore.OData.Routing.Conventions.RefRoutingConvention : IODataControllerActionConvention { diff --git a/test/Microsoft.AspNetCore.OData.Tests/Routing/Conventions/EntitySetRoutingConventionTests.cs b/test/Microsoft.AspNetCore.OData.Tests/Routing/Conventions/EntitySetRoutingConventionTests.cs index 0a4d474e7..4f5ea539f 100644 --- a/test/Microsoft.AspNetCore.OData.Tests/Routing/Conventions/EntitySetRoutingConventionTests.cs +++ b/test/Microsoft.AspNetCore.OData.Tests/Routing/Conventions/EntitySetRoutingConventionTests.cs @@ -80,6 +80,39 @@ public void AppliesToActionForGetActionWorksAsExpected(Type controllerType, stri action.Selectors.Select(s => s.AttributeRouteModel.Template)); } + [Theory] + [InlineData(typeof(CustomersController), "Get", 1, false)] + [InlineData(typeof(CustomersController), "Get", 2, true)] + [InlineData(typeof(CustomersController), "GetCustomersFromVipCustomer", 1, false)] + [InlineData(typeof(CustomersController), "GetCustomersFromVipCustomer", 2, true)] + public void AppliesToActionForGetActionAddDollarCountAsExpectedBasedOnConfiguration(Type controllerType, string actionName, int expectCount, bool enableDollarCount) + { + // Arrange + ControllerModel controller = ControllerModelHelpers.BuildControllerModel(controllerType, actionName); + ActionModel action = controller.Actions.First(); + + ODataControllerActionContext context = ODataControllerActionContextHelpers.BuildContext(string.Empty, EdmModel, controller); + context.Action = action; + context.Options.RouteOptions.EnableDollarCountRouting = enableDollarCount; + + EntitySetRoutingConvention entitySetConvention = ConventionHelpers.CreateConvention(); + + // Act + bool returnValue = entitySetConvention.AppliesToAction(context); + Assert.True(returnValue); + + // Assert + Assert.Equal(expectCount, action.Selectors.Count); + if (enableDollarCount) + { + Assert.Contains("/$count", string.Join(",", action.Selectors.Select(s => s.AttributeRouteModel.Template))); + } + else + { + Assert.DoesNotContain("/$count", string.Join(",", action.Selectors.Select(s => s.AttributeRouteModel.Template))); + } + } + [Theory] [InlineData(typeof(CustomersController), "Post", "/Customers", false)] [InlineData(typeof(CustomersController), "PostFromVipCustomer", "/Customers/NS.VipCustomer", false)] diff --git a/test/Microsoft.AspNetCore.OData.Tests/Routing/Conventions/NavigationRoutingConventionTests.cs b/test/Microsoft.AspNetCore.OData.Tests/Routing/Conventions/NavigationRoutingConventionTests.cs index 504e656c0..1976a789b 100644 --- a/test/Microsoft.AspNetCore.OData.Tests/Routing/Conventions/NavigationRoutingConventionTests.cs +++ b/test/Microsoft.AspNetCore.OData.Tests/Routing/Conventions/NavigationRoutingConventionTests.cs @@ -144,6 +144,37 @@ public void NavigationRoutingConventionTestDataRunsAsExpected(Type controllerTyp Assert.Equal(templates, action.Selectors.Select(s => s.AttributeRouteModel.Template)); } + [Theory] + [InlineData(typeof(CustomersController), "GetOrders", 2, false)] + [InlineData(typeof(CustomersController), "GetOrders", 4, true)] + [InlineData(typeof(CustomersController), "GetSubOrdersFromVipCustomer", 2, false)] + [InlineData(typeof(CustomersController), "GetSubOrdersFromVipCustomer", 4, true)] + public void NavigationRoutingConventionAddDollarCountAsExpectedBasedOnConfiguration(Type controllerType, string actionName, int expectCount, bool enableDollarCount) + { + // Arrange + ControllerModel controller = ControllerModelHelpers.BuildControllerModel(controllerType, actionName); + ActionModel action = controller.Actions.First(); + + ODataControllerActionContext context = ODataControllerActionContextHelpers.BuildContext(string.Empty, EdmModel, controller); + context.Action = action; + context.Options.RouteOptions.EnableDollarCountRouting = enableDollarCount; + + // Act + bool returnValue = NavigationConvention.AppliesToAction(context); + Assert.True(returnValue); + + // Assert + Assert.Equal(expectCount, action.Selectors.Count); + if (enableDollarCount) + { + Assert.Contains("/$count", string.Join(",", action.Selectors.Select(s => s.AttributeRouteModel.Template))); + } + else + { + Assert.DoesNotContain("/$count", string.Join(",", action.Selectors.Select(s => s.AttributeRouteModel.Template))); + } + } + [Theory] [InlineData("PostToName")] [InlineData("Get")] diff --git a/test/Microsoft.AspNetCore.OData.Tests/Routing/Conventions/PropertyRoutingConventionTests.cs b/test/Microsoft.AspNetCore.OData.Tests/Routing/Conventions/PropertyRoutingConventionTests.cs index 872022ff0..cd21b3381 100644 --- a/test/Microsoft.AspNetCore.OData.Tests/Routing/Conventions/PropertyRoutingConventionTests.cs +++ b/test/Microsoft.AspNetCore.OData.Tests/Routing/Conventions/PropertyRoutingConventionTests.cs @@ -188,6 +188,37 @@ public void PropertyRoutingConventionTestDataRunsAsExpected(Type controllerType, Assert.Equal(templates, action.Selectors.Select(s => s.AttributeRouteModel.Template)); } + [Theory] + [InlineData(typeof(MeController), "GetEmails", 1, false)] + [InlineData(typeof(MeController), "GetEmails", 2, true)] + [InlineData(typeof(MeController), "GetSubLocationsOfUsAddressFromVipCustomer", 1, false)] + [InlineData(typeof(MeController), "GetSubLocationsOfUsAddressFromVipCustomer", 2, true)] + public void PropertyRoutingConventionAddDollarCountAsExpectedBasedOnConfiguration(Type controllerType, string actionName, int expectCount, bool enableDollarCount) + { + // Arrange + ControllerModel controller = ControllerModelHelpers.BuildControllerModel(controllerType, actionName); + ActionModel action = controller.Actions.First(); + + ODataControllerActionContext context = ODataControllerActionContextHelpers.BuildContext(string.Empty, EdmModel, controller); + context.Action = action; + context.Options.RouteOptions.EnableDollarCountRouting = enableDollarCount; + + // Act + bool returnValue = PropertyConvention.AppliesToAction(context); + Assert.True(returnValue); + + // Assert + Assert.Equal(expectCount, action.Selectors.Count); + if (enableDollarCount) + { + Assert.Contains("/$count", string.Join(",", action.Selectors.Select(s => s.AttributeRouteModel.Template))); + } + else + { + Assert.DoesNotContain("/$count", string.Join(",", action.Selectors.Select(s => s.AttributeRouteModel.Template))); + } + } + [Theory] [InlineData("PostToName")] [InlineData("Get")]