如何使用模型绑定将值修补为空

时间:2018-11-24 04:56:12

标签: asp.net-core .net-core

如果我有这样的物体

public class Movie
{
    public long Id { get; set; }
    public string Name { get; set; }
    public long? Length { get; set; }
    ...
}

并有一个控制器来更新其值:

    [HttpPatch("{id}")]
    public IActionResult Patch(long id, [FromBody] Movie item)
    {

        Movie movie  = (....

        if (item.Length.HasValue)
        {
            movie.Length = item.Length;
            _context.Entry<Movie>(movie).Property(w => w.Length).IsModified = true;
        }

        if (item.Name.HasValue)
        {
            movie.Name = item.Length;
            _context.Entry<Movie>(movie).Property(w => w.Name).IsModified = true;
        }
    }

当提供了Length时,它可以工作,但是如果我想将其更新为undefined(空)怎么办? 如果在前端删除了该值,则PATCH数据为:

{"length":""}

在控制器上,模型绑定将其解释为null。

所以这个:

if (item.Length.HasValue)

使它什么也不做。

有一会儿我考虑删除if条件,因为如果传递了空字符串,则它将更新为null,但是,当然,如果修补了其他任何属性(例如Name),Length也将设置为null那就不好了 问题在于,控制器中没有方法(我可以看到)来确定Length为PATCHED但为空

3 个答案:

答案 0 :(得分:1)

您可以尝试使用id属性首先在数据库中获取某些电影项目,然后为该属性分配新值。

var movie = _context.Movies.SingleOrDefault(x => x.Id == id);

if (movie != null)
{
    // if "item.length" is null, "movie.Length" would be null, too
    // Otherwise, "movie.Length" will update new value based on "item.Length"
    movie.Length = item.Length; 

    // or only if "item.Length" is null
    if (item.Length == null)
    {
        movie.Length = null;
    }

    // update database synchronously
    _context.SaveChanges();

    // or update database asynchronously
    // await _context.SaveChangesAsync();
}

if (movie != null)
{
    if (!string.IsNullOrEmpty(item.Name) && item.Length?.HasValue)
    {
        movie.Name = item.Name;
        movie.Length = item.Length;
    }
    else
    {
        movie.Name = string.Empty;
        movie.Length = null;
    }

    // update database synchronously
    _context.SaveChanges();
}

答案 1 :(得分:1)

您正面临经典的Nullable值类型问题。 Nullable对象的问题是如何确定该值是否真的为null并因此提供,而不是何时为Null简化(因为调用者认为这意味着“未修改”)被省略了。

在定义这类API或接口时,我认为它不仅仅只是提供一种与代码进行交互的方式。在这些情况下,我不是必须声明性的,而是可以要求解释的,而是选择命令性的,而是要求用户具体说明其意图。

因此,在这种情况下,我将添加一个可以设置为开或关的烦人的布尔成员,然后我可以将其读入,直到DTO看起来像

public class Movie
{
    public long Id { get; set; }
    public string Name { get; set; }
    public long? Length { get; set; }
    public Boolean IsLengthUpdated{get; set;} = false;//or something that makes sense to describe the length prop has been set
}

然后我可以探究我的模型,这样我就不必问自己太多关于他们的意图的问题了

因此,在您的补丁操作中,您可以做类似的事情

您的API调用者将IsLengthUpdated设置为true或将其保留为false,在这种情况下,您将忽略对Length属性的所有评估。这样,您的控制器将类似于

 [HttpPatch("{id}")]
    public IActionResult Patch(long id, [FromBody] Movie item)
    {

        Movie movie  = (....

        if(item.IsLengthUpdated)
        {
            movie.Length = item.Length;//here either movie.Length is supplied one of it's three states {Null,Updated to new value or left the same}
            _context.Entry<Movie>(movie).Property(w => w.Length).IsModified = true;
        }
        ... the rest of your code here
    }

他们宁愿回来并说他们一直在更新值而不保留它们,所以我宁愿让他们打开相关的字段开关,否则他们可以保持原样,因为无论哪种方式,它始终默认为false。

答案 2 :(得分:0)

我认为这里不需要模型绑定,因为PATCH数据只是一个很小的json字符串,如下所示:

{"length":""}

所以我现在将其转换为不区分大小写的Dictionary,然后我仅使用发送的键和值:

[HttpPatch("{id}")]
public IActionResult Patch(long id, [FromBody] string patchstring)
{
    JObject json = (JObject)JsonConvert.DeserializeObject(patchstring);
    Dictionary<string, object> updatedict = new Dictionary<string, object>(json.ToObject<IDictionary<string, object>>(), StringComparer.CurrentCultureIgnoreCase);

    Movie movie  = (...);

    if (updatedict.ContainsKey("Length"))
    {
        if ((string)updatedict["Length"] == "")
            movie.Length = null;
        else
            movie.Length = Convert.ToInt64(updatedict["Length"]);
        _context.Entry<Movie>(movie).Property(w => w.Length).IsModified = true;
    }


    if (updatedict.ContainsKey("Rating"))
    {
        if ((string)updatedict["Rating"] == "")
            movie.Rating = null;
        else
            movie.Rating = Convert.ToInt64(updatedict["Rating"]);
        _context.Entry<Movie>(movie).Property(w => w.Rating).IsModified = true;
    }

    _context.SaveChanges();
    return new ObjectResult(movie); 
}