diff --git a/src/Microsoft.AspNetCore.OData/Microsoft.AspNetCore.OData.xml b/src/Microsoft.AspNetCore.OData/Microsoft.AspNetCore.OData.xml
index b5aa272f8..b6c01f6a4 100644
--- a/src/Microsoft.AspNetCore.OData/Microsoft.AspNetCore.OData.xml
+++ b/src/Microsoft.AspNetCore.OData/Microsoft.AspNetCore.OData.xml
@@ -12427,13 +12427,14 @@
The Edm entity type.
The Edm navigation source.
-
+
Split the action based on supporting pattern.
The input action name.
The out of cast type name.
The out of collection binding flag.
+ The case comparision flag.
The operation name.
diff --git a/src/Microsoft.AspNetCore.OData/Routing/Conventions/OperationRoutingConvention.cs b/src/Microsoft.AspNetCore.OData/Routing/Conventions/OperationRoutingConvention.cs
index 4fe40cfb1..a31a81851 100644
--- a/src/Microsoft.AspNetCore.OData/Routing/Conventions/OperationRoutingConvention.cs
+++ b/src/Microsoft.AspNetCore.OData/Routing/Conventions/OperationRoutingConvention.cs
@@ -65,31 +65,17 @@ protected void ProcessOperations(ODataControllerActionContext context, IEdmEntit
return;
}
- // OperationNameOnCollectionOfEntityType
- string operationName = SplitActionName(actionName, out string cast, out bool isOnCollection);
-
+ bool isOnCollection = false;
IEdmEntityType castTypeFromActionName = null;
- if (cast != null)
- {
- if (cast.Length == 0)
- {
- // Early return for the following cases:
- // - {OperationName}On
- // - {OperationName}OnCollectionOf
- return;
- }
- castTypeFromActionName = entityType.FindTypeInInheritance(context.Model, cast, context.Options?.RouteOptions?.EnableActionNameCaseInsensitive == true) as IEdmEntityType;
- if (castTypeFromActionName == null)
- {
- return;
- }
+ IEdmOperation[] candidates = FindCandidates(context, actionName);
+ if (candidates.Length == 0)
+ {
+ // If we can't find any Edm operation using the action name directly,
+ // Let's split the action name and use part of it to search again.
+ candidates = FindCandidates(context, entityType, actionName, out castTypeFromActionName, out isOnCollection);
}
- // TODO: refactor here
- // If we have multiple same function defined, we should match the best one?
- StringComparison actionNameComparison = context.Options?.RouteOptions?.EnableActionNameCaseInsensitive == true ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal;
- IEnumerable candidates = context.Model.SchemaElements.OfType().Where(f => f.IsBound && f.Name.Equals(operationName, actionNameComparison));
foreach (IEdmOperation edmOperation in candidates)
{
IEdmOperationParameter bindingParameter = edmOperation.Parameters.FirstOrDefault();
@@ -172,14 +158,62 @@ protected void ProcessOperations(ODataControllerActionContext context, IEdmEntit
}
}
+ private static IEdmOperation[] FindCandidates(ODataControllerActionContext context, string operationName)
+ {
+ // TODO: refactor here
+ // If we have multiple same function defined, we should match the best one?
+
+ StringComparison actionNameComparison = context.Options?.RouteOptions?.EnableActionNameCaseInsensitive == true ?
+ StringComparison.OrdinalIgnoreCase :
+ StringComparison.Ordinal;
+
+ return context.Model.SchemaElements
+ .OfType()
+ .Where(f => f.IsBound && f.Name.Equals(operationName, actionNameComparison))
+ .ToArray();
+ }
+
+ private static IEdmOperation[] FindCandidates(ODataControllerActionContext context, IEdmEntityType entityType, string actionName,
+ out IEdmEntityType castTypeFromActionName, out bool isOnCollection)
+ {
+ // OperationNameOnCollectionOfEntityType
+ StringComparison caseComparision = context.Options?.RouteOptions?.EnableActionNameCaseInsensitive == true ?
+ StringComparison.OrdinalIgnoreCase :
+ StringComparison.Ordinal;
+
+ string operationName = SplitActionName(actionName, out string cast, out isOnCollection, caseComparision);
+
+ castTypeFromActionName = null;
+ if (cast != null)
+ {
+ if (cast.Length == 0)
+ {
+ // Early return for the following cases:
+ // - {OperationName}On
+ // - {OperationName}OnCollectionOf
+ return Array.Empty();
+ }
+
+ castTypeFromActionName = entityType.FindTypeInInheritance(context.Model, cast, context.Options?.RouteOptions?.EnableActionNameCaseInsensitive == true) as IEdmEntityType;
+ if (castTypeFromActionName == null)
+ {
+ return Array.Empty();
+ }
+ }
+
+ return FindCandidates(context, operationName);
+ }
+
///
/// Split the action based on supporting pattern.
///
/// The input action name.
/// The out of cast type name.
/// The out of collection binding flag.
+ /// The case comparision flag.
/// The operation name.
- internal static string SplitActionName(string actionName, out string cast, out bool isOnCollection)
+ internal static string SplitActionName(string actionName, out string cast, out bool isOnCollection,
+ StringComparison comparison = StringComparison.Ordinal)
{
Contract.Assert(actionName != null);
@@ -190,7 +224,7 @@ internal static string SplitActionName(string actionName, out string cast, out b
cast = null;
isOnCollection = false;
string operation;
- int index = actionName.IndexOf("OnCollectionOf", StringComparison.Ordinal);
+ int index = actionName.LastIndexOf("OnCollectionOf", comparison);
if (index > 0)
{
operation = actionName.Substring(0, index);
@@ -199,7 +233,7 @@ internal static string SplitActionName(string actionName, out string cast, out b
return operation;
}
- index = actionName.IndexOf("On", StringComparison.Ordinal);
+ index = actionName.LastIndexOf("On", comparison);
if (index > 0)
{
operation = actionName.Substring(0, index);
diff --git a/test/Microsoft.AspNetCore.OData.Tests/Routing/Conventions/FunctionRoutingConventionTests.cs b/test/Microsoft.AspNetCore.OData.Tests/Routing/Conventions/FunctionRoutingConventionTests.cs
index 34d95d5a9..618d11fad 100644
--- a/test/Microsoft.AspNetCore.OData.Tests/Routing/Conventions/FunctionRoutingConventionTests.cs
+++ b/test/Microsoft.AspNetCore.OData.Tests/Routing/Conventions/FunctionRoutingConventionTests.cs
@@ -154,11 +154,47 @@ public static TheoryDataSet FunctionRoutingConventionTes
"/Customers/NS.GetWholeSalary(minSalary={minSalary},maxSalary={maxSalary},aveSalary={aveSalary})",
"/Customers/GetWholeSalary(minSalary={minSalary},maxSalary={maxSalary},aveSalary={aveSalary})"
}
+ },
+ {
+ typeof(CustomersController),
+ "GetStatusOnLineOfflineUser",
+ new[]
+ {
+ "/Customers/NS.GetStatusOnLineOfflineUser()",
+ "/Customers/GetStatusOnLineOfflineUser()"
+ }
+ },
+ {
+ typeof(CustomersController),
+ "GetStatusOnLineOfflineUser",
+ new[]
+ {
+ "/Customers/NS.GetStatusOnLineOfflineUser()",
+ "/Customers/GetStatusOnLineOfflineUser()"
+ }
+ },
+ {
+ typeof(CustomersController),
+ "StatusLineOfflineUserOn",
+ new[]
+ {
+ "/Customers/NS.StatusLineOfflineUserOn()",
+ "/Customers/StatusLineOfflineUserOn()"
+ }
+ },
+ {
+ typeof(CustomersController),
+ "GetStatusOnLineOfflineUserOnVipCustomer",
+ new[]
+ {
+ "/Customers/NS.VipCustomer/NS.GetStatusOnLineOfflineUser(param={param})",
+ "/Customers/NS.VipCustomer/GetStatusOnLineOfflineUser(param={param})"
+ }
}
};
}
- }
-
+ }
+
public static TheoryDataSet FunctionRoutingConventionCaseInsensitiveTestData
{
get
@@ -448,6 +484,19 @@ private static IEdmModel GetEdmModel()
getSalaray.AddOptionalParameter("aveSalary", intType, "129");
model.AddElement(getSalaray);
+ EdmFunction f = new EdmFunction("NS", "GetStatusOnLineOfflineUser", intType, isBound: true, entitySetPathExpression: null, isComposable: false);
+ f.AddParameter("entityset", new EdmCollectionTypeReference(new EdmCollectionType(new EdmEntityTypeReference(customer, false))));
+ model.AddElement(f);
+
+ EdmFunction f2 = new EdmFunction("NS", "StatusLineOfflineUserOn", intType, isBound: true, entitySetPathExpression: null, isComposable: false);
+ f2.AddParameter("entityset", new EdmCollectionTypeReference(new EdmCollectionType(new EdmEntityTypeReference(customer, false))));
+ model.AddElement(f2);
+
+ EdmFunction f3 = new EdmFunction("NS", "GetStatusOnLineOfflineUser", intType, isBound: true, entitySetPathExpression: null, isComposable: false);
+ f3.AddParameter("entityset", new EdmCollectionTypeReference(new EdmCollectionType(new EdmEntityTypeReference(vipCustomer, false))));
+ f3.AddParameter("param", intType);
+ model.AddElement(f3);
+
EdmEntityContainer container = new EdmEntityContainer("NS", "Default");
container.AddEntitySet("Customers", customer);
container.AddEntitySet("CustomersCaseInsensitive", customer);
@@ -461,6 +510,15 @@ private class CustomersController
public void Get()
{ }
+ [HttpGet]
+ public void GetStatusOnLineOfflineUser() { }
+
+ [HttpGet]
+ public void GetStatusOnLineOfflineUserOnVipCustomer(int param) { }
+
+ [HttpGet]
+ public void StatusLineOfflineUserOn() { }
+
[HttpGet]
public void IsBaseUpgraded(int key, CancellationToken cancellation)
{ }