ASP.net Core RC2 Web API POST - 何时使用Create、CreatedAtAction和CreatedAtRoute?

82

这些函数的基本区别是什么?我知道的只有所有三个都会产生201,这对于成功的POST请求是合适的。

我只是跟随我在网上看到的例子,但他们并没有真正解释他们为什么要这样做。

我们应该为我们的GET(按ID获取1条记录)提供名称:

[HttpGet("{id}", Name="MyStuff")]
public async Task<IActionResult> GetAsync(int id)
{
     return new ObjectResult(new MyStuff(id));
}
除了可能需要下面的POST函数之外,给这个获取函数命名的目的是什么?

给这个获取函数命名的目的是什么,除了它“可能”被下面的POST函数所需?

[HttpPost]
public async Task<IActionResult> PostAsync([FromBody]MyStuff myStuff)
{
     // actual insertion code left out

     return CreatedAtRoute("MyStuff", new { id = myStuff.Id }, myStuff);
}

我注意到CreatedAtRoute还有一种不带路由名称的重载形式。

还有CreatedAtAction具有类似的参数。为什么会存在这种变体?

还有一个要求提供URL和要返回的对象的Created。我能否只使用这个变体并提供虚假URL并返回我想要的对象,以此完成任务?

我不确定为什么有这么多变体只是为了能够向客户端返回201。在大多数情况下,我想做的就是返回“应用分配”的(很可能来自数据库的)唯一ID或具有最少信息的实体版本。

我认为,最终,201响应应该创建一个具有新创建资源的URL的位置标头,我相信所有3种变体及其重载都会这样做。为什么我总是要返回位置标头呢?我的JavaScript客户端、本地移动和桌面应用程序从来没有使用过它。例如,如果我发出HTTP POST来创建账单并将其发送给用户,这样的位置URL将是什么?(对于这个问题,我没有深入挖掘互联网历史,找到答案,感到抱歉。)

为什么要为操作和路由创建名称?操作名称和路由名称之间有什么区别?

我对此感到困惑,所以我只好返回Ok(),它返回200,这对于POST是不合适的。

2个回答

80

这里其实有几个不同的问题,可能需要拆分一下,但我认为这个回答已经涵盖了你大部分的问题。

为什么要为动作和路由创建名称?动作名称和路由名称之间有什么区别?

首先,动作和路由非常不同。

一个动作存在于控制器中,而路由指定了一个完整的终点,包括一个控制器、一个动作以及其他可能的路由参数。

您可以给路由指定一个名称,这样您就可以在应用程序中引用它。例如:

routes.MapRoute(
  name: "MyRouteName",
  url: "SomePrefix/{action}/{id}",
  defaults: new { controller = "Section", action = "Index" }
);

这个问题涵盖了操作名称的原因:Purpose of ActionName

它允许您以数字开头或包含 .net 不允许的任何字符作为标识符来启动操作。 - 最常见的原因是允许您拥有两个具有相同签名的 Actions(请参见任何模板化控制器的 GET/POST 删除 actions)

这些功能有什么根本区别?

这三个函数本质上都执行相同的功能-返回一个 201 Created 响应,其中包含指向新创建响应的 url 的 Location 标头和对象本身。url 应该是获取对象 url 的 GET 请求的 url。这在 RESTful 系统中被认为是“正确”的行为。

对于您问题中的 Post 示例代码,实际上应该使用 CreatedAtAction

[HttpPost]
public async Task<IActionResult> PostAsync([FromBody]MyStuff myStuff)
{  
   // actual insertion code left out

   return CreatedAtAction("MyStuff", new { id = myStuff.Id }, myStuff);
}

假设您已配置默认路由,则这将添加一个Location标头,该标头指向同一控制器上的MyStuff操作。

如果您希望位置url指向特定路由(如我们之前定义的),则可以使用例如:

[HttpPost]
public async Task<IActionResult> PostAsync([FromBody]MyStuff myStuff)
{  
   // actual insertion code left out

   return CreatedAtRoute("MyRouteName", new { id = myStuff.Id }, myStuff);
}

我能不能只使用这个变体,提供一个虚假的URL,并返回我想要的对象,完成并结束它?

如果你真的不想使用CreatedResult,你可以使用简单的StatusCodeResult,它将返回201,但没有Location头或主体。

[HttpPost]
public async Task<IActionResult> PostAsync([FromBody]MyStuff myStuff)
{  
  // actual insertion code left out

  return StatusCode(201);
}

我不确定.NET Core是否仍存在Content<T>(HttpStatusCode, T content)方法以返回状态码和内容。这应该允许返回201和新创建的对象,而无需包含Location头。 - afrazier
2
那个过载似乎已经不存在了,但是实际上有一个类似的StatusCode过载可以使用:StatusCode(int httpStatusCode, object value) - Sock
3
似乎使用[HttpGet("{id}", Name = "GetUser",Order = 0)]创建路由的方法似乎不能与CreatedAtRoute()一起使用。 - Crob
返回一个带有Location头的响应,指向新创建响应的URL,并将对象本身放在响应体中。很抱歉,我无法理解这个。您能否请清楚地解释一下? - Ali Abbasifard

0

我相信这里有一个例子适合你。 请记住我正在使用.NET 6

    [HttpPost]
    public IActionResult CadastrarCerveja([FromBody] Cerveja cerveja)
    {
        using (var ctx = new CervejaContext())
        {
            ctx.Cervejas.Add(cerveja);
            ctx.SaveChanges();
        }

        return CreatedAtAction(
            nameof(LerCerveja),
            new { IdCerveja = cerveja.Id },
            cerveja);
    }

    [HttpGet("{IdCerveja}")]
    public IActionResult LerCerveja(int IdCerveja)
    {
        var ctx = new CervejaContext();
        var cerv = ctx.Cervejas.FirstOrDefault(c => c.Id == IdCerveja);

        if (cerv == null)
            return NotFound();
        else
            return Ok(cerv);
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接