所以说我们有一个aspnet core2控制器,它从另一个应用程序返回HttpPost对象。 我使用实体框架核心,两种方式都可以工作,但我只是对最佳实践和性能感到好奇,知道要使用什么?
[HttpPost]
public Message Post([FromBody] Message message)
{
// my old code
// var msg = new Message { Owner = message.Owner, Text = message.Text };
//db.Messages.AddAsync(msg)
var msgEntity = db.Messages.Add(message).Entity;
db.Messages.AddAsync(message);
db.SaveChangesAsync();
return message;
}
当我使用Entity时发生了一些奇怪的事情,它改变了自动递增的Id参数{Id:1002},当我添加它之前,下一个连续的Id被认为是{Id:11}
答案 0 :(得分:2)
您永远不应该直接从请求中保存任何内容。这是您的最佳做法。之前的代码是优越的,唯一的原因是您明确选择了实际持久化的帖子值,而不是盲目地保存用户决定直接发送到您的数据库的任何内容。
更好的方法是实际使用视图模型来接受用户输入。例如:
public class MessageViewModel
{
public int OwnerId { get; set; }
[Required]
public string Text { get; set; }
}
然后,您接受此作为参数,并将其映射到您的实体:
public async Task<IActionResult> Post([FromBody]MessageViewModel model)
{
if (ModelState.IsValid)
{
var message = new Message
{
Owner = db.Users.Find(model.OwnerId),
Text = model.Text
};
db.Messages.Add(message);
await db.SaveChangesAsync();
return Ok(message);
}
return BadRequest(ModelState);
}
我在上面的代码中做了一些重要的更改:
添加条件以检查ModelState.IsValid
是否为真,然后再继续保存。正如你现在所做的那样,你只是盲目地试图保存所传入的内容,即使这样做最终会引发数据库异常,因为缺少所需的值等等。
使用此条件,您现在需要处理出现问题的情况。更有可能的是,您希望返回某种错误对象,以帮助客户解决问题。实际上只返回{{1}}是常见的,它将序列化其验证错误列表,但您可能也想做其他事情。
由于现在需要返回两种不同的对象类型,因此操作签名已更改为返回ModelState
。通常最好只使用这种返回类型。它基本上是一个包罗万象,而像IActionResult
这样的特定回报很容易超过它的用处,要求你继续前进并稍后改变它。你应该只返回特定类型,当绝对没有任何类型的失败时,即响应总是 200 OK。但是,这种情况很少见。
视图模型只接受所有者的ID,而不是完整的(可能是Message
)对象。接受ApplicationUser
并直接保存它的同样问题也存在于此。恶意用户可以使用Message
对象属性进行捏造,从而可以更改各种不应更改的内容。通常,您应该始终允许用户操作尽可能少的数据。您公开的任何内容都应该故意完全理解其含义。既然,我们现在只接受id,那么我们需要查找具有该id的用户,以在该实体上设置Owner
属性。如果您碰巧有一个明确的外键属性,例如Owner
上的OwnerId
,您可以直接设置它。
将Message
更改为AddAsync
。根据文档,您几乎不应该使用Add
。它仅存在于您不太可能遇到的非常特定的目的。建议始终使用AddAsync
,除非您有充分的理由。
在Add
电话中添加了await
。始终等待异步操作,除非操作完成对任何事情都没有影响。绝对不是这样的。 SaveChangesAsync
可以引发异常,应用程序需要处理的异常。不等待它,基本上只是吞下这些并让代码愉快地继续,好像没有问题。虽然这听起来像是积极的,但绝对不是。还有其他问题可以由不等待引起,例如上下文可能会在通话结束前被处理掉。
由于我们现在正在等待SaveChangesAsync
,因此必须将SaveChangesAsync
添加到方法签名中,并且您必须返回async
。