Return an object along with a 409 Conflict error in a Web API 2 POST call backed by Entity Framework?

删除回忆录丶 提交于 2019-12-09 08:05:18

问题


I have a C# Entity Framework Web API 2 controller. Currently when an attempt is made via the POST method to create an object with the same text for the main text field, I return a 409 Conflict error as an StatusCode result to indicate the addition is considered a duplicate.

What I'd like to do is return the server side object that triggered the duplicate error too. So I need something akin to the Ok() method but a variant that returns a 409 Conflict error as the HTTP status code instead of an HTTP OK status code.

Is there such a thing? How can I do this? If I can make this work the client doesn't have to do a subsequent Get call to the server to get the existing object after receiving a 409 Conflict error.

Here's the current POST method:

    public IHttpActionResult PostCanonical(Canonical canonical)
    {
        if (!ModelState.IsValid)
        {
            return BadRequest(ModelState);
        }

        // Check for duplicate Canonical text for the same app name.
        if (db.IsDuplicateCanonical(canonical.AppName, canonical.Text))
        {
            // It's a duplicate.  Return an HTTP 409 Conflict error to let the client know.
            return StatusCode(HttpStatusCode.Conflict);
        }

        db.CanonicalSentences.Add(canonical);
        db.SaveChanges();

        return CreatedAtRoute("DefaultApi", new { id = canonical.ID }, canonical);
    }

回答1:


EDIT: This solution is for WebApi prior v5, please see this answer if you are using v5 or above.

You could return a NegotiatedContentResult<T> that lets you specify the status code and an object to be put into the http message body.

Change your code to something like this:

public IHttpActionResult PostCanonical(Canonical canonical)
{
    if (!ModelState.IsValid)
    {
        return BadRequest(ModelState);
    }

    // Check for duplicate Canonical text for the same app name.
    if (db.IsDuplicateCanonical(canonical.AppName, canonical.Text))
    {
        // It's a duplicate.  Return an HTTP 409 Conflict error to let the client know.
        var original = db.CanonicalSentences.First(c => c.ID == canonical.ID);
        return new NegotiatedContentResult<T>(HttpStatusCode.Conflict, original, this);
    }

    db.CanonicalSentences.Add(canonical);
    db.SaveChanges();

    return CreatedAtRoute("DefaultApi", new { id = canonical.ID }, canonical);
}

Or maybe wrap it an extension method like this:

public static class HttpActionResultExtensions {
    public static IHttpActionResult StatusCodeWithContent<T>(this ApiController @this, HttpStatusCode statusCode, T content) {
        return new NegotiatedContentResult<T>(statusCode, content, @this);
    }
}

And then use the extension like this:

public IHttpActionResult PostCanonical(Canonical canonical)
{
    if (!ModelState.IsValid)
    {
        return BadRequest(ModelState);
    }

    // Check for duplicate Canonical text for the same app name.
    if (db.IsDuplicateCanonical(canonical.AppName, canonical.Text))
    {
        // It's a duplicate.  Return an HTTP 409 Conflict error to let the client know.
        var original = db.CanonicalSentences.First(c => c.ID == canonical.ID);
        return StatusCodeWithContent(HttpStatusCode.Conflict, original)
    }

    db.CanonicalSentences.Add(canonical);
    db.SaveChanges();

    return CreatedAtRoute("DefaultApi", new { id = canonical.ID }, canonical);
}



回答2:


You should return Content:

return Content(HttpStatusCode.Conflict, original);

Content is method on the ApiController class which will create a NegotiatedContentResult with the provided HttpStatusCode and content. There is no need to create your own extension method on the ApiController class like in the accepted answer.




回答3:


Arrived here looking for help with ASP.NET Core HTTP 409 - this is related, just the newer approach to solving this same problem.

Conflict ActionResult

 return Conflict(new { message = $"An existing record with the id '{id}' was already found."});



回答4:


I had a similar issue with ASP.NET Core 1.0 RC2, however I encountered a DbUpdateConcurrencyException, using optimistic concurrency I did not want to allow my user to update an object that was already updated.

I also wanted to return the updated object to the user making the call. I'm able to create a new CreatedAtAction and set the StatusCode to StatusCodes.Status409Conflict

context.Entry(user).State = EntityState.Modified;
try
{
    await context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
    if (!UserExists(id))
    {
        return NotFound();
    }
    else
    {
        //remove the user we just added, otherwise it will not goto 
        //the database to obtain the updated user
        context.Entry(user).State = EntityState.Detached;
        var updatedUser = await context.Users.SingleOrDefaultAsync(m => m.Id == id);

        if (updatedUser == null)
        {
            return NotFound();
        }
        var returnAction  = CreatedAtAction("PutUser", new { id = user.Id }, updatedUser);
        returnAction.StatusCode = StatusCodes.Status409Conflict;
        return returnAction;                    
    }
}


来源:https://stackoverflow.com/questions/31866418/return-an-object-along-with-a-409-conflict-error-in-a-web-api-2-post-call-backed

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!