2016-07-26 95 views
1

我們目前正在爲具有基本CRUD功能的每個數據表構建Web API和控制器。我們遇到的問題是更新。我們創建了自定義綁定模型來引入我們需要的數據,然後將綁定模型轉換爲對象,並將其傳遞給我們的更新函數。Web Api通過綁定模型更新特定屬性

我們遇到的問題是,當客戶端通過POST發送數據時,我們的綁定模型會收到它並填充它們使用值設置的字段,並填充爲空的所有內容。因此,當我們將其轉換爲數據對象並將其發送給Update函數時,它將覆蓋未從客戶端設置爲空的字段。

這顯然會導致問題,因爲我們不希望用戶意外刪除信息。

這裏是我們如何運行的東西與客戶端,結合模型,及更新的例子,

團隊綁定模型

/// <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 = "[email protected]", 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調用,然後修改對象來獲取整個對象,但如果客戶端不遵循該協議,則可能會非常危險地清除敏感數據。

+0

是客戶端的MVC網站? –

+0

爲什麼不在回購層更新之前進行獲取併合並兩個對象模型和實體,然後將該合併對象傳遞給更新方法。通過這種方式,您將確保只發送udpated值並且其他任何東西保持不變。 – Prashant

回答

1

我已經實現了一個靜態類,它將採用enity對象並僅更新實體的髒屬性。這允許最終用戶在需要時顯式地將值設置爲空。

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