AspNetCoreOData icon indicating copy to clipboard operation
AspNetCoreOData copied to clipboard

New routing selection mechanism makes writing custom routing conventions harder

Open SEnglishSMP opened this issue 4 years ago • 3 comments

The documentation for writing custom OData routing conventions for ASP.NET (not Core), shows that you override SelectAction in which you examine a given route and return the controller action to execute. As outlined in Routing in ASP.NET Core OData 8.0 Preview the SelectAction method is gone and one must now implement IODataControllerActionConvention and its AppliesToController and AppliesToAction methods. These methods, instead of asking what action should handle a route, ask if the convention can handle a given controller and action, with the convention adding the routes that it can map to the action. This is basically the reverse approach. Instead of starting with the route and asking different conventions until one can handle the route, it starts with each action, asking each convention in turn if it can give routes for the action. This has two key consequences:

  1. Two conventions cannot both provide routes for the same action, since once one convention says it applies to an action no additional conventions are interrogated for additional routes.
  2. Two different conventions could each supply the same route for different actions. When this happens, you get an AmbiguousMatchException with the message "The request matched multiple endpoints." While the endpoints are listed, the conventions that gave conflicting endpoints are not.

This makes writing custom conventions more difficult and limiting. Limiting because only one convention can specify a route for an action and difficult because if two conventions specify the same route for different actions, an exception occurs. A convention therefore needs to see if a given route is already mapped to some other action before applying it to the action in question.

At a minimum, the routing structure should ensure that:

  1. Two conventions can both provide different routes to the same action, and
  2. If two conventions both specify the same route for different actions, the convention with priority (lowest Order number) wins and the same route from other conventions is ignored.

SEnglishSMP avatar Jan 21 '21 22:01 SEnglishSMP

I have also some trouble with this new convention. I was trying to rewrite code from CustomPropertyRoutingConvention class to OData 8.0 Convention but I have a lot of trouble to understand how it works, and I am really confused. @SEnglishSMP @xuzhg Have you tried to do this in 8.0? Do you have any documentation on how it works?

MCKorgan avatar May 06 '21 13:05 MCKorgan

Any updates on this issue? Finding this is really hard to control the routing conventions.

stealth-17 avatar Sep 01 '22 23:09 stealth-17

Could you guys elaborate a bit more on your use case scenarios? Why are you having the need to create a custom convention?

julealgon avatar Sep 02 '22 12:09 julealgon

@julealgon In our case, we have 150+ simple entity sets that are read-only. We use IODataRoutingConvention to route these sets to single controller that returned the result (passing entity type to the controller via SelectAction). This saved us hundreds of files. I do not see how something like this could be done via IODataControllerActionConvention because new approach starts with controller and seems to allow only single entity set per controller and does not support "per request" routing evaluation.

We were also able to use IODataRoutingConvention to allow routing "~/entityset/key/property" and "~/entityset/key/navigation" to general method without having to implement separate method for each property/navigation. Is this possible with new approach? Am I missing something?

xsmerek avatar Oct 20 '22 13:10 xsmerek

Thanks for the explanation @xsmerek , interesting scenario you have there.

While I understand how you've been handling this before, have you ever considered changing your approach to use something like the new Source Generators? It seems like that could allow you to auto-generate all the boilerplate controller code for all the entities but still have the "standard" 1-controller-per-entity that OData natively supports without extension.

Not necessarily saying this would be the cleanest approach but could be an option that decouples your use case from limitations of the library.

julealgon avatar Oct 20 '22 15:10 julealgon

Thanks for the explanation @xsmerek , interesting scenario you have there.

While I understand how you've been handling this before, have you ever considered changing your approach to use something like the new Source Generators? It seems like that could allow you to auto-generate all the boilerplate controller code for all the entities but still have the "standard" 1-controller-per-entity that OData natively supports without extension.

Not necessarily saying this would be the cleanest approach but could be an option that decouples your use case from limitations of the library.

This is exactly what I did (using source generators). It works well, but it was quite a lot of effort to do (especially just learning how source generators work from scratch). It doesn't seem like a reasonable solution for most people just to get around some seemingly unnecessary limitations of the odata library.

nieldy avatar Aug 09 '23 14:08 nieldy