我有一个具有多种操作方法的 ASP.NET Core 控制器。每个操作方法都有一个
{id}
路径参数,并在几乎每个请求中执行以下操作:
[HttpPost("action1/{id}")]
public async Task<ActionResult<JsonNode>> Action1Async(int id)
{
var model = await _dbService.FindModelAsync(id); // Entity Framework Core
if (model is null)
{
return NotFound();
}
// actual code for action1
}
[HttpPost("action2/{id}")]
public async Task<ActionResult<JsonNode>> Action2Async(int id)
{
var model = await _dbService.FindModelAsync(id); // Entity Framework Core
if (model is null)
{
return NotFound();
}
// actual code for action2
}
[HttpPost("action3/{id}")]
public async Task<ActionResult<JsonNode>> Action3Async(int id)
{
var model = await _dbService.FindModelAsync(id); // Entity Framework Core
if (model is null)
{
return NotFound();
}
// actual code for action3
}
如您所见,所有操作都执行完全相同的第一步:从
DbService
检索模型,如果未找到模型则返回 404 错误,否则执行一些特定逻辑。
对于我来说,这段代码非常重复。基本上,我唯一的目标就是:
我想要这样的东西:
// this will not execute if the model is not found.
[HttpPost("action1/{id}")]
public async Task<ActionResult<JsonNode>> Action1Async(Model model)
{
// actual code for action1
}
我目前正在探索过滤器/中间件来执行此操作,但它们似乎仅用于验证,而不是真正初始化数据并将它们传递给控制器操作,但我愿意探索所有方法。
我想要的可能吗?我们可以将 Entity Framework Core 模型直接绑定到控制器操作参数吗?
可能吗?是的。推荐吗?不会。将实体传入和传出视图的主要问题是,您发送来填充视图的实体实例与返回的实例不同。无论是表单 POST 还是 Ajax POST,返回的都是从 HTML 元素中剥离的值,然后 ASP.Net 构建一个新的、独立的实体实例。这意味着预期返回控制器的每个属性都必须呈现在 HTML 中的某个位置,例如隐藏的输入控件。您在 cshtml 中看到的
@model
对于客户端浏览器可以为 POST 发回的内容没有任何意义,如果值不在 HTML 或 JavaScript 中,则它不会返回。不建议在 JavaScript 中序列化模型,因为这会将整个实体结构暴露给客户端检查,并且最终可能导致延迟加载。
避免将这些实体发送到视图并将准实体发送回控制器的主要原因是:
这相当于每次操作发送的数据远多于您需要的数据。如果您希望将传入的实体视为行的完整表示,则它需要完整,否则会导致最终擦除状态或在未设置值或关系时出现空引用异常。基本上,任何接受实体的方法都应该接收完整的或可完成的实体,而不是可能不完整的存根。
当用于更新数据时,它会使您的系统遭受意外篡改。接受实体的控制器可能会倾向于使用诸如
context.Update(entity);
或 context.Attach(entity); context.Entry(entity).State = EntityState.Modified;
之类的东西,这使得系统容易被篡改。对服务器的调用可以在浏览器调试工具中拦截并更改以更新您不打算修改的“实体”中的值。如果服务器信任该准实体并使用它来更新行,则数据可能会受到损害。
使用独立的实体,即使经过验证和信任,也可能容易出错。反序列化数据为相同的相关数据创建单独的引用,因此从技术上讲,您必须遍历每个引用并处理 DbContext 实例可能正在跟踪该行的实例并需要替换它的事实。这会导致相当多的代码能够正确地完成任务,或者看似间歇性的运行时错误。