diff --git a/sample/ODataAlternateKeySample/Controllers/CustomersController.cs b/sample/ODataAlternateKeySample/Controllers/CustomersController.cs
index 84f93b415..8cd6c2874 100644
--- a/sample/ODataAlternateKeySample/Controllers/CustomersController.cs
+++ b/sample/ODataAlternateKeySample/Controllers/CustomersController.cs
@@ -54,6 +54,7 @@ public IActionResult Post([FromBody]Customer c)
[HttpGet("odata/Customers(CoreSN={ssn})")] // use core alternate key
public IActionResult GetCustomerBySSN(string ssn)
{
+ ssn = ssn.Replace("%", "%25");
var c = _repository.GetCustomers().FirstOrDefault(c => c.SSN == ssn);
if (c == null)
{
diff --git a/sample/ODataAlternateKeySample/readme.md b/sample/ODataAlternateKeySample/readme.md
index 593db7598..f40f436c3 100644
--- a/sample/ODataAlternateKeySample/readme.md
+++ b/sample/ODataAlternateKeySample/readme.md
@@ -207,8 +207,8 @@ Send one of the following requests
```C#
GET http://localhost:5219/odata/Customers(2)
-GET http://localhost:5219/odata/Customers(SSN='SSN-2-102')
-GET http://localhost:5219/odata/Customers(CoreSN='SSN-2-102')
+GET http://localhost:5219/odata/Customers(SSN='SSN-%25-2-102')
+GET http://localhost:5219/odata/Customers(CoreSN='SSN-%25-2-102')
```
you can get the following result:
@@ -217,7 +217,7 @@ you can get the following result:
"@odata.context": "http://localhost:5219/odata/$metadata#Customers/$entity",
"Id": 2,
"Name": "Jerry",
- "SSN": "SSN-2-102",
+ "SSN": "SSN-%25-2-102",
"Titles": [
"abc",
null,
diff --git a/src/Microsoft.AspNetCore.OData/Extensions/ActionModelExtensions.cs b/src/Microsoft.AspNetCore.OData/Extensions/ActionModelExtensions.cs
index 788b177ca..ddf4b3d8c 100644
--- a/src/Microsoft.AspNetCore.OData/Extensions/ActionModelExtensions.cs
+++ b/src/Microsoft.AspNetCore.OData/Extensions/ActionModelExtensions.cs
@@ -215,6 +215,7 @@ public static void AddSelector(this ActionModel action, string httpMethods, stri
// OData convention route template doesn't get combined with the route template applied to the controller.
// Route templates applied to an action that begin with / or ~/ don't get combined with route templates applied to the controller.
Template = $"/{templateStr}",
+ Order = options?.Order,
Name = templateStr // do we need this?
};
diff --git a/src/Microsoft.AspNetCore.OData/Microsoft.AspNetCore.OData.xml b/src/Microsoft.AspNetCore.OData/Microsoft.AspNetCore.OData.xml
index 90f8e731c..7c2c27502 100644
--- a/src/Microsoft.AspNetCore.OData/Microsoft.AspNetCore.OData.xml
+++ b/src/Microsoft.AspNetCore.OData/Microsoft.AspNetCore.OData.xml
@@ -13953,6 +13953,12 @@
Initializes a new instance of the class.
+
+
+ Gets or sets the route order in conventional routing.
+ By default, move the conventional routing later as much as possible.
+
+
Gets/sets a value indicating whether to enable $count in conventional routing.
diff --git a/src/Microsoft.AspNetCore.OData/PublicAPI.Unshipped.txt b/src/Microsoft.AspNetCore.OData/PublicAPI.Unshipped.txt
index f918ce0cf..54790de77 100644
--- a/src/Microsoft.AspNetCore.OData/PublicAPI.Unshipped.txt
+++ b/src/Microsoft.AspNetCore.OData/PublicAPI.Unshipped.txt
@@ -1353,6 +1353,8 @@ Microsoft.AspNetCore.OData.Routing.ODataRouteOptions.EnableQualifiedOperationCal
Microsoft.AspNetCore.OData.Routing.ODataRouteOptions.EnableUnqualifiedOperationCall.get -> bool
Microsoft.AspNetCore.OData.Routing.ODataRouteOptions.EnableUnqualifiedOperationCall.set -> void
Microsoft.AspNetCore.OData.Routing.ODataRouteOptions.ODataRouteOptions() -> void
+Microsoft.AspNetCore.OData.Routing.ODataRouteOptions.Order.get -> int?
+Microsoft.AspNetCore.OData.Routing.ODataRouteOptions.Order.set -> void
Microsoft.AspNetCore.OData.Routing.ODataRoutingMetadata
Microsoft.AspNetCore.OData.Routing.ODataRoutingMetadata.IsConventional.get -> bool
Microsoft.AspNetCore.OData.Routing.ODataRoutingMetadata.IsConventional.set -> void
diff --git a/src/Microsoft.AspNetCore.OData/Routing/ODataRouteOptions.cs b/src/Microsoft.AspNetCore.OData/Routing/ODataRouteOptions.cs
index 28c2e1b32..e5ecc9fb5 100644
--- a/src/Microsoft.AspNetCore.OData/Routing/ODataRouteOptions.cs
+++ b/src/Microsoft.AspNetCore.OData/Routing/ODataRouteOptions.cs
@@ -33,6 +33,11 @@ public ODataRouteOptions()
_enableUnqualifiedOperationCall = true;
}
+ ///
+ /// Gets or sets the route order in conventional routing.
+ ///
+ public int? Order { get; set; }
+
///
/// Gets/sets a value indicating whether to enable $count in conventional routing.
///
diff --git a/test/Microsoft.AspNetCore.OData.E2E.Tests/AlternateKeys/AlternateKeysController.cs b/test/Microsoft.AspNetCore.OData.E2E.Tests/AlternateKeys/AlternateKeysController.cs
index 021318e32..0886a9ccd 100644
--- a/test/Microsoft.AspNetCore.OData.E2E.Tests/AlternateKeys/AlternateKeysController.cs
+++ b/test/Microsoft.AspNetCore.OData.E2E.Tests/AlternateKeys/AlternateKeysController.cs
@@ -38,7 +38,11 @@ public IActionResult Get(int key)
}
// alternate key: SSN
- [HttpGet("Customers(SSN={ssn})", Order = 2)]
+ // why set Order = -2 (any number less than 0)? it is because 'Get' method has 'catch-all' template, we should move this template ahead
+ // Small order goes first.
+ // We can also leave order value unset, same as 'Get' method and 'PatchCustomerBySSN' method without setting the order value.
+ // Without setting the order value makes all routes with same order value and catch-all goes latter
+ [HttpGet("Customers(SSN={ssn})", Order = -2)]
public IActionResult GetCustomerBySSN(string ssn)
{
// for special test
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 d7a0a7edb..3c76e3065 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
@@ -1868,6 +1868,7 @@ public class Microsoft.AspNetCore.OData.Routing.ODataRouteOptions {
bool EnablePropertyNameCaseInsensitive { public get; public set; }
bool EnableQualifiedOperationCall { public get; public set; }
bool EnableUnqualifiedOperationCall { public get; public set; }
+ System.Nullable`1[[System.Int32]] Order { public get; public set; }
}
public sealed class Microsoft.AspNetCore.OData.Routing.ODataRoutingMetadata : IODataRoutingMetadata {
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 d7a0a7edb..3c76e3065 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
@@ -1868,6 +1868,7 @@ public class Microsoft.AspNetCore.OData.Routing.ODataRouteOptions {
bool EnablePropertyNameCaseInsensitive { public get; public set; }
bool EnableQualifiedOperationCall { public get; public set; }
bool EnableUnqualifiedOperationCall { public get; public set; }
+ System.Nullable`1[[System.Int32]] Order { public get; public set; }
}
public sealed class Microsoft.AspNetCore.OData.Routing.ODataRoutingMetadata : IODataRoutingMetadata {