In ASP.NET Core, using OData, How to use a Route that differs from the Controller name?

Viewed 21

Utterly confused as to OData v8.0 Routing.

  • For
    • security objectives (unecessarily avoiding exposing internal workings)
    • availability objectives (avoiding inadvertently introducing breaking apis when refactoring and renaming Controllers),
  • The objective is to use OData's AddRouteComponents() to register Controllers
    • with a path that does not match/expose the actual name of the Controller.

In a test rig, have developed 3 controllers:

  • PresentationLayerExampleAEntityOData01Controller with no RouteAttribute,
  • PresentationLayerExampleAEntityOData02Controller with [Route("PresentationLayerExampleAEntityOData02")] (matching name of Controller)
  • PresentationLayerExampleAEntityOData03Controller with a [Route("AnyOtherRouteName")] (not matching name of Controller)

At startup (skipping showing the reflection used) the process is pretty standard, in that an Entity model is registered, along with the Controller name to use, along with a prefix (api/odata/):

   blahblah...const string routePrefix = "api/odata/";
   edmBuilder...blahblah...
   iterate through found some types, each time invoking....define...blahblah...
   ....
   and where required, use an extension method to specify the controller name based on the controller Type name, or its route path attribute if it sees one....
   string controllerName = 
                typeof(PresentationLayerExampleAEntityOData01Controller)
                .GetODataControllerModelName();
   ...etc...
   var catwalkModel = edmBuidler.GetEdmBuilder();
   ...done:
   opt.AddRouteComponents( routePrefix, catwalkModel);

As for the Controllers, they inherit from a base class that in turn derives from ODataController...therefore are already decorated with [ODataAttributeRouting]

Call /Swagger, and the above controller shows up, is invokable, and the results comeback as OData (ie, under a value, alongside $odata.context).

Cool.

But it's not yet what we are after....

Case 1: Using Conventions

    //No Route [Route(ApiConstants.OdataApiRoutePrefix + "[controller]")]
    public class PresentationLayerExampleAEntityOData01Controller :
        ModuleQueryableODataControllerBase<ExampleAEntityDto>
    {
        [HttpGet("")]
        [HttpGet("Get")]
        [EnableQuery(PageSize = 100)]
        public IActionResult Get()
        {
            return Ok(_exampleEntityAService.Get());
        }     
    }

works fine in that one can Swagger finds them and:

  • /api/odata/PresentationLayerExampleAEntityOData01/Get or
  • /api/odata/PresentationLayerExampleAEntityOData01/

and the resuts will be a proper OData wrapped payload.

That said, Swagger is utterly confused and is showing the OData Route (works) but also an alternate route (which doesn't).

  • /PresentationLayerExampleAEntityOData01/Get or
  • /PresentationLayerExampleAEntityOData01/

Have no idea yet what's going on there and how to exclude the above.

But ...trucking on...

Case 2: Using Routing

Took me ages to figure out how to use the RoutingAttribute (I think), because for ages it was returning data, but not wrapped as an OData returnable...

The error I would see during build was something to the effect that it could not determine the Controller part from the Path template given. It appears that defining the Route within the prefix does not work:

// Does not work, and data comes back as non-wrapped data array:
    [Route(ApiConstants.RestApiRoutePrefix+"[controller]")]
    [ApiController]
    [ForDemoOnly]
    public class PresentationLayerExampleAEntityController : ControllerBase 

(and I was unable to figure out a way to use ODataRouteComponent to produce a better outcome) -- whereas the following does behave more correctly:

// remove the prefix and it seems happier:
// data DOES comes back as wrapped OData data:
    [Route(ApiConstants.RestApiRoutePrefix+"[controller]")]
    [ApiController]
    [ForDemoOnly]
    public class PresentationLayerExampleAEntityController : ControllerBase 

Case 3: Using Routing to Achieve the Desired Outcome

But although I've got the RouteAttribute to be accepted, I've gotten no closer to the objective -- using any other Route to get to the OData Controller. As in:

    [Route("AnyOtherRouteName")]
    public class PresentationLayerExampleAEntityOData03Controller :
        ModuleQueryableODataControllerBase<ExampleAEntityDto>
    {
    ...
    }

Except ... it doesn't work.

OData never found the controller, so doesn't offer it (Swagger, ever so cheerful and helpful does present some options but they are not prepended with api/odata/ ... and of course fails ... a false herring of useless documentation :-(

Conclusion

I don't have one. Except that after a day fighting all this, I do need some advice to achieve the outcome of case 3.

Thank you.

0 Answers
Related