.NET API 更新包括 ID

.NET API Update Includes ID

我来自 Ruby Rails API 背景,但目前正在研究 .NET C# WebAPI。我熟悉 C# 和 .NET webforms。

我正在尝试设置将更新数据库中记录的 PUT 请求。脚手架方法覆盖所有字段,而我只想更新通过 PUT 传递的字段。我尝试使用以下代码:

// 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);
        }

但是,该方法在 db.SaveChanges() 上失败,因为它无法覆盖 'id',这是一个键。 put 请求为 content-type: application/json,json 如下:

{
    "preferredEmailUpdates":"true"
}

这需要适用于所有字段并接受空条目。所以下面的也是有效的,并且应该用 null.

更新字段 phone
{
    "preferredEmailUpdates":"true",
    "phone":null
}

如何在不更新密钥的情况下更新这些值?

您的更新模型(class 用户请求绑定到)应该与您的数据库模型分开(因为它代表不同的概念)。它会有 nullable / maybe 字段来表示可能的更改。它还会隐藏您不希望用户更改的字段(如时间戳)。然后,您可以使用反射仅更新已修改的字段。考虑使用现成的库,如 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);
}

另一种情况是 - 为什么要更新个别字段?能够在不更新密码的情况下更新电子邮件首选项是正常的,但是单独更新所述首选项的每个字段是否正常?我对此表示怀疑。考虑将你的 API 拆分成块,就像你在 OOP 中拆分大接口一样。

您可以考虑使用 PATCH HTTP 动词和 Delta<T> 对象。

    [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);
    }

另见 Easy ASP.NET Web API resource updates with Delta