asp.net-web-apiodataodata-v4odatalibodatacontroller

OData attribute routing for deleting relationship between m:m entities


The scenario is an application that uses OData v4, server-side API based on an Entity Framework model using ODataLib, client-side using the OData client code generator extension for Visual Studio

I'm failing to get OData attribute routing working for deleting relationships for entities in m:m relationships, for which the client generates DELETE requests in the form:

http://developer4:8080/odata/tblTestRestaurant(241)/tblTestDishes/$ref?$id=http://developer4:8080/odata/tblTestDish(1)

Attribute routing for POST for creating a link works just fine - the related entity identifier is encoded in the body, and the following controller action method declaration works (the controller itself has [ODataRoutePrefix("tblTestRestaurant")]):

    [ODataRoute("({pRestaurantID})/tblTestDishes/$ref")]
    [HttpPost]
    [EnableQuery(AllowedQueryOptions = AllowedQueryOptions.All)]
    public async Task<IHttpActionResult> PostAttachtblTestDishes([FromODataUri] int pRestaurantID,
        [FromBody] Uri uri) { ... }

But I can't get something similar working for DELETE where the ID of the related entity is specified using the $ref?id=... syntax in the URL.

I have tried the following using the ODataRoute attribute:

    [ODataRoute("({pRestaurantID})/tblTestDishes/$ref")]
    [HttpDelete]
    public async Task<IHttpActionResult> TestRemoveRef1([FromODataUri] int pRestaurantID,
        [FromODataUri] Uri relatedUri)
    {
        throw new NotImplementedException();
    }
    [ODataRoute("({pRestaurantID})/tblTestDishes/$ref")]
    [HttpDelete]
    public async Task<IHttpActionResult> TestRemoveRef2([FromODataUri] int pRestaurantID,
        [FromODataUri] string relatedUri)
    {
        throw new NotImplementedException();
    }
    [ODataRoute("({pRestaurantID})/tblTestDishes/$ref?$id={pRelated}")]
    [HttpDelete]
    public async Task<IHttpActionResult> TestRemoveRef3([FromODataUri] int pRestaurantID,
        [FromODataUri] string pRelated)
    {
        throw new NotImplementedException();
    }
    [ODataRoute("({pRestaurantID})/tblTestDishes/$ref?$id={pRelated}")]
    [HttpDelete]
    public async Task<IHttpActionResult> TestRemoveRef4([FromODataUri] int pRestaurantID,
        [FromODataUri] Uri pRelated)
    {
        throw new NotImplementedException();
    }

But none of the above controller actions get hit in response to a DELETE request to http://developer4:8080/odata/tblTestRestaurant(241)/tblTestDishes/$ref?$id=http://developer4:8080/odata/tblTestDish(1).

The only way I can get it working is not to use attribute routing but instead to rely on the OData routing conventions, ie

    [HttpDelete]
    public async Task<IHttpActionResult> DeleteRef([FromODataUri] int key,
        [FromODataUri] string relatedKey, string navigationProperty)
    {
        throw new NotImplementedException();
    }

This method relies on testing the string navigationProperty to work out which collection navigation property on the entity to modify - instead I would prefer to use attribute routing and have a separate action method in my controller for each collection navigation property.

I've used a number of tutorials and documentation in particular https://damienbod.wordpress.com/2014/06/10/getting-started-with-web-api-and-odata-v4/

I have also been through some of the OData WebApi test cases, particularly this one which uses a mixture of attribute routing and OData routing conventions - but doesn't contain an example for attribute routing for deleting links.

So my question is - what ODataRoute attribute syntax and method parameters should I be using, assuming that the ODataRoute attribute does support this ...$ref?id=... syntax in the URL for deletes; and if it doesn't then what alternatives are there?


Solution

  • Web API OData parse the Uri in $Id to create a key segment appended to the origin path segments. So, If you change the template as below, it should work:

    [ODataRoute("({pRestaurantID})/tblTestDishes({pRelated})/$ref")]
    [HttpDelete]
    public async Task<IHttpActionResult> TestRemoveRef([FromODataUri] int pRestaurantID, [FromODataUri] int pRelated)
    {
            ...
    }
    

    You can refer to my sample project here. Hope it can help you. Thanks.