-
Notifications
You must be signed in to change notification settings - Fork 712
Regression on default routing #738
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
I'm afraid your issue will require more information. This appears to be OData, but on which platform and version? There are no "default, built-in routes". Is that some OData example you are copying from or something else? A repro is always best, but the controller and application setup would be helpful. |
Built in route conventions are coming from https://docs.microsoft.com/en-us/odata/webapi/built-in-routing-conventions
While
allow accessing the controller below using https://localhost:44338/v1/ThirdPartyBases/AMI.ThirdPartyModel the version 5.0.0 trigger a 404 not found
|
Slightly insightful. So you intend to use the default OData conventions, but you are you using OData attributes (ex: This looks like the action maps to an OData function. It's hard to say without the controller definition, but this might be related to the naming of the controller. There was an optimization made that can cause this behavior. I would expect the controller to look something like: [ApiVersion("1.0")]
[ODataRoutePrefix("ThirdPartyBases")]
public class ThirdPartyBasesController : ODataController
{
[ODataRoute("ThirdPartyModel")] // ← attribute routing supersedes convention-based routing
[Produces("application/json")]
[ProducesResponseType(typeof(ODataValue<IEnumerable<ThirdPartyModel>>), Status200OK)]
[EnableQuery(AllowedQueryOptions = All, MaxTop = 100)]
public IQueryable<ThirdPartyModel> GetFromThirdPartyModel() => _Query.ThirdPartiesQueryProcessor.Get();
} OData now supports Endpoint Routing, which is generally the preferred approach, but you are not using that method. You can choose whichever method you prefer, but I recommend sticking to a single lane. The Endpoint Routing approach will allow interleaving partial sets of attributes, but they are the standard routing attributes (e.g. RouteAttribute, HttpGetAttribute, and so on). Based on your example URL, I presume your setup looks like: public void Configure( IApplicationBuilder app, VersionedODataModelBuilder modelBuilder )
{
app.UseRouting();
app.UseEndpoints(
endpoints =>
{
endpoints.MapControllers();
endpoints.MapVersionedODataRoute( "odata", "v{version:apiVersion}", modelBuilder );
} );
} The basic OData sample demonstrates how to use Endpoint Routing. I hope that helps you identify how your application is different from the expected setup. |
This is indeed my setup, but I am targeting a cast and not a function (https://localhost:44338/v1/ThirdPartyBases/AMI.ThirdPartyModel)
I only find a way to process this request using the Built in route conventions with end point name "GetFromThirdPartyModel". This was working until version 5.0.0. Is there a way to process this request using OData attributes ? |
I haven't seen this particular issue before. I've never found myself needing this OData capability, but it should work. OData has a history of making underlying changes so it's sometimes hard to tell the source of the issue. If you have a repro, that's always helpful. Do things work correctly if you remove API Versioning? There's a lot of open issues in OData. This link may be related. The convention of I guess technically you can all this a cast, but it's not the I recommend starting with that and I'll see if I'm able to determine anything else. The investigation queue is a bit backed up. If I'm not mistaken, the native attribute routing method for OData Endpoint Routing would look like: [ApiVersion("1.0")]
[Route("v{version:apiVersion}/[controller]")] // ← the prefix here has to match the endpoint registration
public class ThirdPartyBasesController : ODataController
{
[HttpGet("[action]")] // I've not tested if OData properly honors this built-in token; it 'should'
[Produces("application/json")]
[ProducesResponseType(typeof(ODataValue<IEnumerable<FirstPartyModel>>), Status200OK)]
[EnableQuery(AllowedQueryOptions = All, MaxTop = 100)]
public IQueryable<FirstPartyModel> FirstPartyModel() => _Query.FirstPartiesQueryProcessor.Get();
[HttpGet(nameof(SecondPartyModel))] // if [action] doesn't work, you can use an explicit name
[Produces("application/json")]
[ProducesResponseType(typeof(ODataValue<IEnumerable<SecondPartyModel>>), Status200OK)]
[EnableQuery(AllowedQueryOptions = All, MaxTop = 100)]
public IQueryable<SecondPartyModel> SecondPartyModel() => _Query.SecondPartiesQueryProcessor.Get();
[HttpGet("[action]")]
[Produces("application/json")]
[ProducesResponseType(typeof(ODataValue<IEnumerable<ThirdPartyModel>>), Status200OK)]
[EnableQuery(AllowedQueryOptions = All, MaxTop = 100)]
public IQueryable<ThirdPartyModel> ThirdPartyModel() => _Query.ThirdPartiesQueryProcessor.Get();
}
|
I see that that Microsoft.AspNetCore.OData.Versioning: 5.0.0 and 4.1.1 are tageting the same version of Microsoft.AspNetCore.OData 7.5.7. It is certainly not the root cause. |
Hi Chris, This is a good news. I will be able to upgrade as I was using the previous version for compatibility reason. |
Hi. I've attached an example project that can reproduce the issue. Steps to reproduce:
I expected the calls to be routed in the same way when versioning is not applied. I'm hoping for an in-place work-around because I'm unable to upgrade to OData 8 at the moment. Thank you! Sample requests which can be used directly with Visual Studio Code REST Client:
|
@icnocop Thanks for the comprehensive repro. I'll give it a gander. This issue might be resolved in the I don't intend to continue servicing OData 7.x long-term so I'd want it to be fixed. OData 7.x is a 🐶 and 🐴 show that still uses the legacy routing approach. OData 8.0 is yet another mulligan and I presume there is something unsupported or otherwise broke you in the reimplementation (I know the feeling). The routing has at least improved in OData 8.0. |
I was able to modify The commit can be found here and based on the Since aspnet-api-versioning no longer supports OData 7.x, I can't migrate to OData v8 yet, and the code fix cannot be applied in any of the aspnet-api-versioning hooks, I'll have to maintain a copy of the code in a separate repository. I was able to verify the code works as expected after migrating to OData v8 and using Asp.Versioning.OData.ApiExplorer 6.0.0-preview.3. Thank you. |
Coincidence... The Universe was listening. I'm literally debugging this very line of code and just figured it out it's in this spot. Effectively, the entity set is not being resolved. You saved me some time. I'll look at backporting your solution into the There's not a whole lot I can do or control with respect to OData's versioning strategy and policies. Every major version seems to introduce significant, breaking changes. While a major version should be able to do that by definition, it seems to almost always be the case with OData since On the positive side, OData 8.x should solve much of the routing template definition and matching process forevermore 🤞🏽. I'll double-check, but I'm highly confident that this issue also exists in ASP.NET Web API, even though no one has reported. There aren't as many as there once was, but there are still quite of few people using OData with Web API. Those users can benefit from this fix as well. 😄 |
Would you like me to submit a PR against the Will Thank you. |
The No matter what the official documentation, SO, or any source says, only the code tells the truth (as mentioned earlier). Looking at the source it seems the convention for a cast is any of the following:
It also seems that |
@icnocop I incorporated your implementation with a few modifications. It solved one problem, but now I'm seeing ambiguous matches between the cast and normal Calling it a night, but I'll pick this back up ASAP. If you didn't change the action selector, I'm not sure what else I missed. For your edification, I came up with this: var entity = entitySet.EntityType();
const string ActionMethod = "Post";
const string FunctionMethod = "Get";
if ( ( FunctionMethod.Equals( method, OrdinalIgnoreCase ) ||
ActionMethod.Equals( method, OrdinalIgnoreCase ) ) &&
actionName != ActionMethod )
{
foreach ( var derivedType in model.FindAllDerivedTypes( entity ).OfType<EdmEntityType>() )
{
var fromTypeName = "From" + derivedType.Name;
if ( actionName.StartsWith( method + fromTypeName, OrdinalIgnoreCase ) ||
actionName.StartsWith( method + entity.Name + fromTypeName, OrdinalIgnoreCase ) )
{
return true;
}
}
} And also: var httpMethods = GetHttpMethods( action );
if ( IsCast( EdmModel, EntitySet, action.ActionName, httpMethods ) )
{
return ODataRouteActionType.EntitySet;
}
else if ( IsActionOrFunction( EntitySet, Singleton, action.ActionName, httpMethods ) ) The HTTP methods need only be resolved once. If all that works for you and passes the test suite, I think it's the correct thing for a PR; otherwise, we can continue hashing out. I'll be sure to give you credit for the find and solution, regardless of where we land. Thanks. |
Thank you for the clarifications. For the This is different than the After I renamed the methods in my example this way, they started to work correctly without requiring changes to the action selector:
Thank you. |
…ng conventions Fixes dotnet#738
…ng conventions Fixes #738
Hi,
The default built in route "Products/NS.Book" targeting "GetFromBook" trigger a 404 Not Found.
Working fine in 4.1.1
Vincent
The text was updated successfully, but these errors were encountered: