Webapi: Support SingleResult on POST action (OData v4)

Created on 21 May 2015  路  3Comments  路  Source: OData/WebApi

Hi,

currently we have the ability to use query options like $expand, $select, ... on the result of an update action (PUT) on an ODataController. We use this is several situations where we want to expand properties when updating foreign keys of an entity.

This is implemented via SingleResult and Updated():

// PUT: api/Contacts(5)
[EnableQuery]
public async Task<IHttpActionResult> Put([FromODataUri]int key, TEntity entity)
{
    // ...
    var queryable = entities.Where(e => e.Id == key);
    return Updated(SingleResult.Create(queryable));
}

When using Prefer: return=representation header, it's possible to use $expand on the result of the PUT operation.

This doesn't work on an insert operation (POST) though:

// POST: api/entities
[EnableQuery]
public async Task<IHttpActionResult> Post(TEntity entity)
{
    // ...
    var queryable = entities.Where(e => e.Id == entity.Id);
    return Created(SingleResult.Create(queryable));
}

This throws an exception:

Cannot find the entity type 'System.Web.Http.SingleResult`1[[full qualified name of model]]' in the model.",

We currently work around this by using

    return Content(HttpStatusCode.Created, SingleResult.Create(queryable));

and this works just fine, though we lose the added benefit of ODataController::Created() at this point.

This might be because Created() acts differently than Updated() when returning a SingleResult.

I think the OData v4 Spec itself doesn't speak against this:

Is it possible to align the implementation of Created() to match the implementation of Updated(), so we can use SingleResult and Prefer: return=representation on an insert operation?

P3

Most helpful comment

Hi,

I have to use SingleResult, because otherwise options like $expand do not work at the result.

My controller method looks like this:

``` c#
[EnableQuery]
public virtual async Task Post(TEntity entity)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}

await service.Insert(entity);

var queryable = service.Entities.Where(e => e.Id == entity.Id);
return Content(HttpStatusCode.Created, SingleResult.Create(queryable));

}

where service.Entities is a pre-filtered IQueryable (for things like row-level-security, soft deletion...)

I can't just return Ok(entity), because $expand, $select would not be applied this way. I do insertions like this:

POST http://.../odata/Addresses?$expand=Contact 
{
  "Description": "whatever",
  "ContactId": 1
}

and I want to get the created address including the contact entity. With my method (Content and SingleResult), the $expand gets applied successfully:

``` json
{
    "@odata.context": "http://.../odata/$metadata#Addresses/$entity",
    "Description": "whatever",
    "ContactId": 1,
    "Id": 7,
    "IsDeleted": false,
    "CreatedBy": "user",
    "CreatedAt": "2015-07-17T14:33:26.40611+02:00",
    "ModifiedBy": null,
    "ModifiedAt": null,
    "Contact": {
        ...
    }
}

All 3 comments

@kpko Thanks for reporting this! Updated and Craeted's implement is different, you can find form there, can you share more detail with your controller Post method or tell us your scenario? why not just return return Created(entity) ?

Hi,

I have to use SingleResult, because otherwise options like $expand do not work at the result.

My controller method looks like this:

``` c#
[EnableQuery]
public virtual async Task Post(TEntity entity)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}

await service.Insert(entity);

var queryable = service.Entities.Where(e => e.Id == entity.Id);
return Content(HttpStatusCode.Created, SingleResult.Create(queryable));

}

where service.Entities is a pre-filtered IQueryable (for things like row-level-security, soft deletion...)

I can't just return Ok(entity), because $expand, $select would not be applied this way. I do insertions like this:

POST http://.../odata/Addresses?$expand=Contact 
{
  "Description": "whatever",
  "ContactId": 1
}

and I want to get the created address including the contact entity. With my method (Content and SingleResult), the $expand gets applied successfully:

``` json
{
    "@odata.context": "http://.../odata/$metadata#Addresses/$entity",
    "Description": "whatever",
    "ContactId": 1,
    "Id": 7,
    "IsDeleted": false,
    "CreatedBy": "user",
    "CreatedAt": "2015-07-17T14:33:26.40611+02:00",
    "ModifiedBy": null,
    "ModifiedAt": null,
    "Contact": {
        ...
    }
}

馃憤

Was this page helpful?
0 / 5 - 0 ratings