steventnorris
steventnorris

Reputation: 5876

.NET API Update Includes ID

I come from a Ruby on Rails API background, but am currently working on a .NET C# WebAPI. I am familiar with C# and .NET webforms.

I'm attempting to set up a PUT request that will update a record in the db. The scaffolded method overwrites all the fields, whereas I want to simply update just the fields passed through the PUT. I attempted with the below code:

// PUT: api/Users/5
        [ResponseType(typeof(void))]
        public IHttpActionResult PutUser(string id, User user)
        {

            if (!ModelState.IsValid)
            {
                return BadRequest(ModelState);
            }

            User userToUpdate = db.Users.Where(u => u.id == id).FirstOrDefault();
            if (userToUpdate == null)
            {
                return NotFound();
            }

            db.Entry(userToUpdate).CurrentValues.SetValues(user);

            try
            {
                db.SaveChanges();
            }
            catch (DbUpdateException)
            {
                if (UserExists(id))
                {
                    return Conflict();
                }
                else
                {
                    throw;
                }
            }

            return StatusCode(HttpStatusCode.NoContent);
        }

However, the method fails on db.SaveChanges() as it cannot overwrite 'id', which is a key. The put request is of content-type: application/json and the json is below:

{
    "preferredEmailUpdates":"true"
}

This needs to work for all fields and accept null entries. So the below is valid as well, and should update the field phone with null.

{
    "preferredEmailUpdates":"true",
    "phone":null
}

How do I update these values, while not updating the key?

Upvotes: 3

Views: 721

Answers (2)

Mark Cidade
Mark Cidade

Reputation: 99957

You can consider using the PATCH HTTP verb and a Delta<T> object.

    [AcceptVerbs("Patch"), ResponseType(typeof(void))]
    public IHttpActionResult PatchUser(string id, Delta<Team> changes)
    {
        User userToUpdate = db.Users.Where(u => u.id == id).FirstOrDefault();
        if (userToUpdate == null) 
           return NotFound();

        changes.Patch(userToUpdate);

        try
        {                
            db.SaveChanges()
        }
        catch (DbUpdateException)
        {
           ...
        }

        return StatusCode(HttpStatusCode.NoContent);
    }

See also Easy ASP.NET Web API resource updates with Delta.

Upvotes: 2

joozek
joozek

Reputation: 2191

Your model for updates (class that user request is bound to) should be separated from your DB model (because it represents different concept). It would have nullable / maybe fields to represent possible changes. It would also hide fields you don't want users to change (like timestamps). You could then use reflection to update only modified fields. Consider using ready-to-use libraries like AutoMapper

using System;
using System.Reflection;
using WhateverNamespaceIsNeededForWebApiAndEF;

public class UserUpdate
{
  public String Name {get; set;}
  public bool? PreferredEmailUpdates {get; set;}
}
public class User
{
  public int Id {get; set;}
  public String Name {get; set;}
  public bool PreferredEmailUpdates {get; set;}
  public DateTimeOffset CreationDate {get; set;}
}

[ResponseType(typeof(void))]
public IHttpActionResult PutUser(string id, UserUpdate command)
{

    if (!ModelState.IsValid)
    {
        return BadRequest(ModelState);
    }

    User userToUpdate = db.Users.Where(u => u.id == id).FirstOrDefault();
    if (userToUpdate == null)
    {
        return NotFound();
    }

    // THIS IS THE IMPORTANT CODE
    PropertyInfo[] propInfos = command.GetType().GetProperties(BindingFlags.Public|BindingFlags.Instance);
    foreach(var p in propInfos)
    {
        object value = p.GetValue(command);
        if(value != null)
        {
            userToUpdate.GetType().GetProperty(p.Name).SetValue(userToUpdate, value);
        }
    }
    // END IMPORTANT CODE

    try
    {
        db.SaveChanges();
    }
    catch (DbUpdateException)
    {
        if (UserExists(id))
        {
            return Conflict();
        }
        else
        {
            throw;
        }
    }

    return StatusCode(HttpStatusCode.NoContent);
}

Another case is - why would you want to update individual fields? It's normal to have ability to update email preferences without updating password, but is it normal to update each field of said preferences separately? I doubt it. Consider splitting your API to chunks just like you would split big interface in OOP.

Upvotes: 0

Related Questions