【问题标题】:Web Api Updating only specific properties through a binding modelWeb Api 通过绑定模型仅更新特定属性
【发布时间】:2016-07-26 18:40:17
【问题描述】:

我们目前正在为每个具有基本 CRUD 功能的数据表构建 Web api 和控制器。我们遇到的问题是更新。我们创建了自定义绑定模型以仅引入我们需要的数据,然后将该绑定模型转换为对象,并将其传递给我们的更新函数。

我们遇到的问题是,当客户端通过 POST 发送数据时,我们的绑定模型接收它并使用值填充他们设置的字段,并且它填充为 null 的所有其他内容。因此,当我们将其转换为数据对象并将其发送到 Update 函数时,它会覆盖未从客户端设置为 null 的字段。

这显然会导致问题,因为我们不希望用户意外删除信息。

这是我们如何使用客户端、绑定模型和更新运行事物的示例,

团队绑定模型

/// <summary>A Binding Model representing the essential elements of the Team table</summary>
public class TeamBindingModel
{
    /// <summary>The Id of the team</summary>
    [Required(ErrorMessage = "An ID is required")]
    public int ID { get; set; }

    /// <summary>The name of the team</summary>
    [Required(ErrorMessage = "A Team Name is required")]
    [Display(Name = "Team Name")]
    [StringLength(35)]
    public string Team1 { get; set; }

    /// <summary>The email associated with the team</summary>
    [StringLength(120)]
    [DataType(DataType.EmailAddress)]
    public string Email { get; set; }

    public bool ShowDDL { get; set; }
}

UpdateTeam CRUD 方法

// PUT: api/Team
/// <summary>
/// Attempt to update a team with a given existing ID
/// </summary>
/// <param name="team">TeamBindingModel - The binding model which needs an Id and a Team name</param>
/// <returns>IHttpActionResult that formats as an HttpResponseCode string</returns>
[HttpPut]
[Authorize(Roles = "SystemAdmin.Teams.Update")]
public async Task<IHttpActionResult> UpdateTeam(TeamBindingModel team)
{
    if (!ModelState.IsValid)
    {
        return BadRequest(ModelState);
    }

    try
    {
        // Convert the binding model to the Data object
        Team teamObject = team.ToObject();

        unitOfWork.TeamRepository.Update(teamObject);
        await unitOfWork.Save();
    }
    catch (DbUpdateConcurrencyException)
    {
        return BadRequest();
    }
    catch (Exception ex)
    {
        return BadRequest(ex.Message);
    }

    return Ok();
}

ToObject 函数

/// <summary>Takes the Team Binding model and converts it to a Team object</summary>
/// <returns>Team Object</returns>
public virtual Team ToObject()
{
    // Setup the data object
    Team newObject = new Team();

    // Instantiate the basic property fields
    newObject.ID = this.ID;
    newObject.Team1 = this.Team1;
    newObject.Email = this.Email;
    newObject.ShowDDL = this.ShowDDL;

    return newObject;
}

更新功能

public virtual void Update(TEntity entityToUpdate)
{
    try
    {
        dbSet.Attach(entityToUpdate);
        dbContext.Entry(entityToUpdate).State = EntityState.Modified;
    }
    catch (Exception ex)
    {
        throw ex;
    }
}

保存功能

public async Task Save()
{
    await dbContext.SaveChangesAsync();
}

客户调用/测试/错误

// Add team to update and remove
var db = new genericDatabase();
var teamDB = new Team { Team1 = "testTeam", Email = "test@email.com", ShowDDL = true};

db.Teams.Add(teamDB);
db.SaveChanges();

// Look for items in the database
var originalTeamInQuestion = (from b in db.Teams
                                where b.Team1 == "testTeam"
                                select b).FirstOrDefault();

// Create Team object with the some changes
var team = new
{
    ID = originalTeamInQuestion.ID,
    Team1 = "changedTestTeam",
    ShowDDL = false,
};

// This is the API call which sends a PUT with only the parameters from team
var teamToUpdate = team.PutToJObject(baseUrl + apiCall, userAccount.token);

// Look for items in the database
var changedTeamInQuestion = (from b in db.Teams
                                where b.Team1 == "changedTestTeam"
                                select b).FirstOrDefault();

// This Assert succeeds and shows that changes have taken place
Assert.AreEqual(team.Team1, changedTeamInQuestion.Team1);

// This Assert is failing since no Email information is being sent
// and the binding model assigns it to Null since it didn't get that 
// as part of the PUT and overrides the object on update.
Assert.AreEqual(originalTeamInQuestion.Email, changedTeamInQuestion.Email);

对一些替代方法有什么想法吗?我们曾考虑要求客户端首先通过对 API 进行 GET 调用来获取整个对象,然后更改对象,但如果客户端不遵循该协议,他们可能会非常危险地清除敏感数据。

【问题讨论】:

  • 客户端是 MVC 站点吗?
  • 为什么不在 repo 层更新之前做一个 get 并合并两个对象模型和实体,然后将合并的对象传递给更新方法。这样,您将确保只发送更新的值,而其他任何东西都保持不变。

标签: c# json asp.net-web-api crud


【解决方案1】:

我已经实现了一个静态类,它将获取实体对象并仅更新实体的脏属性。这允许最终用户在需要时将值显式设置为 null。

public static class DirtyProperties
{
    public static T ToUpdatedObject<T>(T entityObject)
    {
        return UpdateObject(entityObject,GetDirtyProperties());
    }

    private static Dictionary<string,object>GetDirtyProperties()
    {
        //Inspects the JSON payload for properties explicitly set.
        return JsonConvert.DeserializeObject<Dictionary<string, object>>(new StreamReader(HttpContext.Current.Request.InputStream).ReadToEnd());
    }

    private static T UpdateObject<T>(T entityObject, Dictionary<string, object> properties)
    {

        //Loop through each changed properties and update the entity object with new values
        foreach (var prop in properties)
        {
            var updateProperty = entityObject.GetType().GetProperty(prop.Key);// Try and get property

            if (updateProperty != null)
            {
                SetValue(updateProperty, entityObject, prop.Value);
            }
        }

        return entityObject;
    }

    private static void SetValue(PropertyInfo property, object entity, object newValue)
    {
        //This method is used to convert binding model properties to entity properties and set the new value
        Type t = Nullable.GetUnderlyingType(property.PropertyType) ?? property.PropertyType;
        object safeVal = (newValue == null) ? null : Convert.ChangeType(newValue, t);

        property.SetValue(entity, safeVal);
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-07-14
    • 1970-01-01
    • 2016-06-16
    • 2018-06-21
    • 2016-12-21
    • 1970-01-01
    相关资源
    最近更新 更多