Webapi: Custom Routing 7.4.1+

Created on 10 Feb 2021  路  13Comments  路  Source: OData/WebApi

Custom routing doesn't work like it did in 7.4.1

I created a small sample app based upon

https://docs.microsoft.com/en-us/odata/webapi/custom-routing-convention

Am I missing something. Worked fine in 7.4.1 anyone have a working sample?

Assemblies affected

7.5.0+

Was fine in 7.4.1

Reproduce steps

  var conventions = new List<IODataRoutingConvention>();
  conventions.Insert(0, new CustomPropertyRoutingConvention());

  app.UseEndpoints(endpoints =>
  {
      endpoints.MapControllers();
      endpoints.EnableDependencyInjection();
      endpoints.Select().Filter().OrderBy().Count().Expand().MaxTop(100);
      endpoints.MapODataRoute("odata", "api", GetEdmModel(), new DefaultODataPathHandler(), conventions);
  });
 public class CustomPropertyRoutingConvention : NavigationSourceRoutingConvention
        {
            public override string SelectAction(
                RouteContext routeContext,
                SelectControllerResult controllerResult,
                IEnumerable<ControllerActionDescriptor> actionDescriptors)
            {
                return "GetName";
            }
        }

```

    IEdmModel GetEdmModel()
    {
        var odataBuilder = new ODataConventionModelBuilder();
        odataBuilder.EntitySet<Product>("Products");

        return odataBuilder.GetEdmModel();
    }
 ```
   public class ProductsController :ODataController

    {

        public string GetOrders([FromODataUri] int key)
        {
            return "test1";
        }

        public string GetAddress([FromODataUri] int key)
        {
            return "test1";
        }

        public string GetName([FromODataUri] int key)
        {
            return "test3";
        }
    }

Expected result

Trigger the GetName Action

Actual result

Produces an error.

AmbiguousMatchException: The request matched multiple endpoints. Matches:

OdataTest1.Controllers.ProductsController.GetOrders (OdataTest1)
OdataTest1.Controllers.ProductsController.GetAddress (OdataTest1)
OdataTest1.Controllers.ProductsController.GetName (OdataTest1)

follow up question

All 13 comments

Hi,
Can you please provide us the repro of the sample you created and also request url(s) you were trying

Thanks

It's exactly the steps discussed in
https://docs.microsoft.com/en-us/odata/webapi/custom-routing-convention
With the latest nuget packages

I will find the sample

@p6345uk could you share the endpoint that your request is hitting? Or does the error occur during startup before you even issue a request?

Its when your making a request

@p6345uk could you share the exact request you're making?

Is it GET /odata/Products(1)/Name? for example

It could be anything that triggers the
CustomPropertyRoutingConvention

It appears that
return "GetName";

Then triggers the error

In my example
api/products

Should trigger the SelectAction inside of the custom Routing - I believe

Which it does
but me specifying

return "GetName"; doesnt appear to work

Where it did in version 7.4.1

Added a sample

https://github.com/p6345uk/OdataTestRouting

if you move the versions between 7.4.1 and the latest you can see the differences

@p6345uk out of curiosity, why did you not use the built-in routing convention since routing to a property is supported by default. If you have a method called GetName(int key) in your ProductsController and make a request to /Products(1)/Name, the built-in routing will select the GetName(int key) method.

@p6345uk I tried your sample project and was able to reproduce your error.

The error seems to stem from the fact that you have a [Route("api/[controller]")] attribute on your controller. This probably causes conflicts with the OData WebAPI routing mechanics.

If you remove [Route] attribute, then the error does not occur.

However, when you remove the [Route] attribute, your sample will return a 404 error when you visit the endpoint /api/Products. This is due to details of how the ODataActionSelector works:

If your action method has parameters, e.g. GetName(int key), then those parameters need to be added as keys in the route context's RouteData. (some parameters are exempt from this rule, such ODataQueryOptions, cancellation tokens, parameters that are bound to the body of POST/PUT/PATCH requests, etc.)

So if you want your custom routing convention to correctly route to an action that has parameters, then add those parameters to the route data, e.g. to properly route to the GetName(int key) method, then you should have an entry for the key parameter in the route data values.

c# public class CustomPropertyRoutingConvention : NavigationSourceRoutingConvention { public override string SelectAction( RouteContext routeContext, SelectControllerResult controllerResult, IEnumerable<ControllerActionDescriptor> actionDescriptors) { routeContext.RouteData.Values.Add("key", 1); // you can retrieve the key from query options, route segments or other sources return "GetName"; } }

@p6345uk out of curiosity, why did you not use the built-in routing convention since routing to a property is supported by default. If you have a method called GetName(int key) in your ProductsController and make a request to /Products(1)/Name, the built-in routing will select the GetName(int key) method.

In my specific case we have a myriad of types you can specify in the route
/{Type}

Which I am setting up OData Actions to achieve however i am using the Routing conventions to direct them to different actions on the controller

@p6345uk I tried your sample project and was able to reproduce your error.

The error seems to stem from the fact that you have a [Route("api/[controller]")] attribute on your controller. This probably causes conflicts with the OData WebAPI routing mechanics.

If you remove [Route] attribute, then the error does not occur.

However, when you remove the [Route] attribute, your sample will return a 404 error when you visit the endpoint /api/Products. This is due to details of how the ODataActionSelector works:

If your action method has parameters, e.g. GetName(int key), then those parameters need to be added as keys in the route context's RouteData. (some parameters are exempt from this rule, such ODataQueryOptions, cancellation tokens, parameters that are bound to the body of POST/PUT/PATCH requests, etc.)

So if you want your custom routing convention to correctly route to an action that has parameters, then add those parameters to the route data, e.g.

public class CustomPropertyRoutingConvention : NavigationSourceRoutingConvention
 {
            public override string SelectAction(
                RouteContext routeContext,
                SelectControllerResult controllerResult,
                IEnumerable<ControllerActionDescriptor> actionDescriptors)
            {
               routeContext.RouteData.Values.Add("key", 1); // you can retrieve the key from query options, route segments or other sources
                return "GetName";
            }
 }

Will give that a try :)

Was this page helpful?
0 / 5 - 0 ratings

Related issues

NetTecture picture NetTecture  路  4Comments

kpko picture kpko  路  3Comments

ilya-chumakov picture ilya-chumakov  路  5Comments

VikingsFan picture VikingsFan  路  5Comments

suadev picture suadev  路  3Comments