Skip to content

[Endpoint routing review]First draft to enable endpoint routing using dynamic controller - #2035

Merged
xuzhg merged 9 commits into
OData:masterfrom
xuzhg:EnableEndpointRouting
Feb 27, 2020
Merged

[Endpoint routing review]First draft to enable endpoint routing using dynamic controller#2035
xuzhg merged 9 commits into
OData:masterfrom
xuzhg:EnableEndpointRouting

Conversation

@xuzhg

@xuzhg xuzhg commented Jan 29, 2020

Copy link
Copy Markdown
Member

Issues

This pull request fixes issue #xxx.

Description

  • Enable Endpoint routing in ASP.NET Core OData

Checklist (Uncheck if it is not completed)

  • Test cases added
  • Build and test with one-click build and test script passed

Additional work necessary

If documentation update is needed, please add "Docs Needed" label to the issue and provide details about the required document change in the issue.

@xuzhg xuzhg added the Ready for review Use this label if a pull request is ready to be reviewed label Feb 4, 2020
@xuzhg
xuzhg force-pushed the EnableEndpointRouting branch from 831209c to 8d9c27b Compare February 5, 2020 00:59
}

querySettings = defaultQuerySettings;
}

@KanishManuja-MS KanishManuja-MS Feb 6, 2020

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How does this even work? #Closed

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Synced offline, right.


In reply to: 375972141 [](ancestors = 375972141)


querySettings = defaultQuerySettings;
builder.ServiceProvider.SetDefaultQuerySettings(defaultQuerySettings);
return builder;

@KanishManuja-MS KanishManuja-MS Feb 6, 2020

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the reason behind this change? Extension method for service provider is not intuitive. May be you want to create a private static methods rather than extension methods. #WontFix

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Synced offline, right?


In reply to: 375976380 [](ancestors = 375976380)

using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.AspNetCore.Routing;
#if NETCOREAPP3_1
using Microsoft.AspNetCore.Routing.Matching;

@KanishManuja-MS KanishManuja-MS Feb 6, 2020

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Once we upgrade to higher versions of net core, this would need to be changed right? Is there a conditional compile that specifies greater than? #Resolved

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Revert.


In reply to: 375983113 [](ancestors = 375983113)

@@ -39,23 +38,7 @@ public static IRouteBuilder SetDefaultQuerySettings(this IRouteBuilder builder,
throw Error.ArgumentNull("builder");
}

@KanishManuja-MS KanishManuja-MS Feb 6, 2020

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now I understand the reason for this change. I still feel extension method may not be right. Just a shared static method with a normal parameter may be better. #WontFix

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extension method, we can call the method using Fluent way.

builder.Fun1().Func2().Func3()....

with static method. we can't do like this.


In reply to: 375990865 [](ancestors = 375990865)

/// <param name="model">The EDM model to use for parsing OData paths.</param>
/// <param name="batchHandler">The <see cref="ODataBatchHandler"/>.</param>
/// <returns>The <see cref="IEndpointRouteBuilder"/>.</returns>
public static IEndpointRouteBuilder MapODataRoute(this IEndpointRouteBuilder builder,

@KanishManuja-MS KanishManuja-MS Feb 6, 2020

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MapODataRoute [](start = 44, length = 13)

You should use MapODataServiceRoute to maintain parity. #Resolved

/// The OData routing conventions to use for controller and action selection.
/// </param>
/// <returns>The <see cref="IEndpointRouteBuilder"/>.</returns>
public static IEndpointRouteBuilder MapODataRoute(this IEndpointRouteBuilder builder,

@KanishManuja-MS KanishManuja-MS Feb 6, 2020

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MapODataRoute [](start = 44, length = 13)

Same for other. Change the name. #Resolved

<VersionMinor Condition="'$(VersionMinor)' == ''">4</VersionMinor>
<VersionBuild Condition="'$(VersionBuild)' == ''">0</VersionBuild>
<VersionRelease Condition="'$(VersionRelease)' == ''"></VersionRelease>
</PropertyGroup>

@KanishManuja-MS KanishManuja-MS Feb 6, 2020

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add beta here. #Resolved

if (start == -1 || end == -1)
{
break;
}

@KanishManuja-MS KanishManuja-MS Feb 6, 2020

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am missing something may be. It seems that the if prefix has '{' and '}', this will loop indefinitely as prefix never changes. #WontFix

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, see my test cases for more information.


In reply to: 376002351 [](ancestors = 376002351)

foreach (var item in templates)
{
string variable = item.Substring(1, item.Length - 2); // remove { and }

@KanishManuja-MS KanishManuja-MS Feb 6, 2020

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why substring again? We should add to templates after striping the braces already. #WontFix

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We only replace the string within "{" and "}". and keep the other as it. for example:

prefix: "data{data}" and we have {"data": 1} in route value, the return of this method should be "data1", not "11". See more from my added test cases.


In reply to: 376002864 [](ancestors = 376002864)

private static string UriEncode(string str)
{
string escape = Uri.EscapeUriString(str);
escape = escape.Replace("#", _escapedHashMark);

@KanishManuja-MS KanishManuja-MS Feb 6, 2020

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"#" [](start = 36, length = 3)

Declare these strings as constants? It may be better to use the char overload instead of the string overload for better performance. #Pending

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replace doesn't have a overload to replace a char in a string. and for better readability, I'd like to use "#" directly here. The LinkGenerator mayeb change later.


In reply to: 376006516 [](ancestors = 376006516)


/// <inheritdoc/>
public override Task SelectAsync(HttpContext httpContext, CandidateSet candidateSet)
{

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What are the other things that can happen in the pipeline while this runs asynchronously?

// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

#if !NETSTANDARD2_0

@KanishManuja-MS KanishManuja-MS Feb 6, 2020

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

!NETSTANDARD2_0 [](start = 4, length = 15)

This conditional is different from the other conditional. Can you explain the reason? #Resolved

@KanishManuja-MS

KanishManuja-MS commented Feb 6, 2020

Copy link
Copy Markdown
Contributor

I see the sample but there are no test cases. Can you please add test cases for endpoint routing? Specially for the ones where controller methods are overloaded? #Resolved

/// </summary>
internal class ODataEndpointSelector : EndpointSelector
{
private EndpointSelector _innerSelector;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_innerSelector [](start = 33, length = 14)

I am unclear on what does the coding style guidelines say about using _ for private members. It was the old naming convention and I see that not all classes use this. We should discuss what is the recommendation, since this convention is different in ODL as well.

@mikepizzo mikepizzo added this to the 7.4 Beta milestone Feb 11, 2020
@xuzhg
xuzhg force-pushed the EnableEndpointRouting branch 3 times, most recently from 5229452 to 59b98be Compare February 25, 2020 00:01
/// <param name="generator">The inner Link generator</param>
public ODataEndpointLinkGenerator(LinkGenerator generator)
{
_innerGenerator = generator;

@gathogojr gathogojr Feb 26, 2020

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@xuzhg Is there a chance of this constructor being called with a null value for the LinkGenerator parameter? That would most certainly precipitate chaos when the overridden methods that reference _innerGenerator are called #Resolved

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Theoretically, no. But, let me add the verification code.


In reply to: 384365505 [](ancestors = 384365505)

{
if (routeName == null)
{
throw Error.ArgumentNull("routeName");

@gathogojr gathogojr Feb 26, 2020

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@xuzhg Better to use nameof here #Resolved

{
if (comparer == null)
{
throw new ArgumentNullException(nameof(comparer));

@gathogojr gathogojr Feb 26, 2020

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@xuzhg Maybe use throw Error.ArgumentNull(...) for consistency #WontFix

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This class is removed and replaced using "ODataEndpointSelectorPolicy"


In reply to: 384446213 [](ancestors = 384446213)

{
if (serviceProvider == null)
{
throw Error.ArgumentNull("serviceProvider");

@gathogojr gathogojr Feb 26, 2020

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@xuzhg Better use nameof here #Resolved

{
if (serviceProvider == null)
{
throw Error.ArgumentNull("serviceProvider");

@gathogojr gathogojr Feb 26, 2020

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@xuzhg Better use nameof here #Resolved


DefaultQuerySettings defaultQuerySettings = builder.GetDefaultQuerySettings();
defaultQuerySettings.EnableCount = true;
builder.ServiceProvider.Count();

@gathogojr gathogojr Feb 26, 2020

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@xuzhg Do we need the variant of Count that accepts a QueryOptionSetting parameter? That calls the overload here #Resolved

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. I forgot to change that Count(...) method. Fixed.


In reply to: 384452658 [](ancestors = 384452658)

throw Error.ArgumentNull("routeName");
}

IList<IODataRoutingConvention> routingConventions = CreateDefault();

@gathogojr gathogojr Feb 26, 2020

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@xuzhg The 4 lines here are a replica of these others here. Maybe the existing CreateDefaultWithAttributeRouting overload could call this new one like this return CreateDefaultWithAttributeRouting(routeName, builder.ServiceProvider) - in which case the check for null routeName wouldn't be required in the existing overload, or consider adding a helper private method that both overloads call #Resolved

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍


In reply to: 384475452 [](ancestors = 384475452)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed.

Originally, i'd like to change the exiting public to the new one. However, it's a breaking change. So, i add a new one.


In reply to: 384715327 [](ancestors = 384715327,384475452)

@mikepizzo

mikepizzo commented Feb 26, 2020

Copy link
Copy Markdown
Contributor
public interface IODataFeature

Given that we are adding to a public interface, I assume we don't expect anyone to have their own custom implementations of this interface that would now break? #Pending


Refers to: src/Microsoft.AspNetCore.OData/Interfaces/IODataFeature.cs:20 in 59b98be. [](commit_id = 59b98be, deletion_comment = False)

@@ -11,6 +11,20 @@ namespace Microsoft.AspNet.OData
/// </summary>
public interface IPerRouteContainer

@mikepizzo mikepizzo Feb 26, 2020

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

public [](start = 4, length = 6)

I assume there are no custom implementations that would break by our adding members to this interface? #Pending

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as the comment in "IODataFeature".


In reply to: 384697141 [](ancestors = 384697141)

// : this((EndpointDataSource)dataSource)
// {
// }
//}

@mikepizzo mikepizzo Feb 26, 2020

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clean this up?
#Resolved

@mikepizzo mikepizzo left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:shipit:

Added a few minor comments to those that others had already posted, but otherwise looks good.

@xuzhg

xuzhg commented Feb 26, 2020

Copy link
Copy Markdown
Member Author

Added the test cases


In reply to: 583046981 [](ancestors = 583046981)

@xuzhg

xuzhg commented Feb 26, 2020

Copy link
Copy Markdown
Member Author
public interface IODataFeature

We have been discussing about to add the public interface for a long time. It seems it's not a breaking change to add new items to a public interface. However, you are right. We don't expect any customer to create their own custom "IODataFeature" implementation.


In reply to: 591585939 [](ancestors = 591585939)


Refers to: src/Microsoft.AspNetCore.OData/Interfaces/IODataFeature.cs:20 in 59b98be. [](commit_id = 59b98be, deletion_comment = False)

@xuzhg
xuzhg force-pushed the EnableEndpointRouting branch from c1089ca to 3c9aa37 Compare February 27, 2020 02:22
@xuzhg

xuzhg commented Feb 27, 2020

Copy link
Copy Markdown
Member Author

@xuzhg
xuzhg merged commit 0204e70 into OData:master Feb 27, 2020
xuzhg added a commit that referenced this pull request May 29, 2026
… dynamic controller (#2035)

* Enable endpoint routing using dynamic controller

* Update to 7.4.0

* Update the PublicAPI test cases

* Add the E2E test cases

* Change the startup to test the per-route configuration

* Some test codes about policy

* Use MatcherPolicy Instead of EndpointSelector to resolve the ambiguity

* Address the comments and add LinkGenerator test cases

* Revert the EF Core version to 2.2.0 to make Aggregation E2E test pass
pull Bot pushed a commit to ehtick/WebApi that referenced this pull request May 29, 2026
… dynamic controller (OData#2035)

* Enable endpoint routing using dynamic controller

* Update to 7.4.0

* Update the PublicAPI test cases

* Add the E2E test cases

* Change the startup to test the per-route configuration

* Some test codes about policy

* Use MatcherPolicy Instead of EndpointSelector to resolve the ambiguity

* Address the comments and add LinkGenerator test cases

* Revert the EF Core version to 2.2.0 to make Aggregation E2E test pass
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Ready for review Use this label if a pull request is ready to be reviewed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants