在ASP.NET MVC中使用Post Redirect Get模式和Restful URL进行验证

17

我有一个用于编辑页面的RESTful URL。在控制器上,这被实现为一个Edit方法。该方法接受GET请求和一个接受POST请求的Edit方法。

这意味着您可以访问Edit URL,它将显示一个适用于GET的表单或保存适用于POST的表单。

[HttpGet]
public ActionResult Edit(int id) {
    ...
}

[HttpPost]
public ActionResult Edit(EditModel model) {
    ...
}
Post-Redirect-Get (PRG) 模式看起来很简单,因为它基本上将每个 POST 请求重定向到一个 GET 操作。然而,我需要确信这样做是正确的。
我的计划是,在 POST 操作中,如果模型有效,我将使用 Post-Redirect-Get 模式将用户发送到合理的位置(可能是 Index 或 Details 操作)。
然而,如果存在模型验证问题,我仍然希望仅显示视图。我不想重定向用户,因为这意味着将模型和 ModelState 存入临时数据并重定向到 GET 操作,然后添加逻辑到 GET 操作以处理临时数据。通过简单地显示视图,我可以避免所有这些问题。
是的,如果用户按 F5,它会重新提交表单,然后他们将会看到“重新提交警告”,但是这时会呈现相同的页面(要求他们修复验证错误)。然而,他们按 F5 的可能性似乎很小,并且也没有双重提交的危险,因为表单将再次失败验证。
如果验证通过,用户将被重定向,他们将免于双重提交的危险。
那么,我应该实现额外的代码并将数据存储到临时数据中以严格遵循 PRG 模式,还是在表单有效且数据正在存储时使用 PRG 模式更合理呢?
3个回答

21

如果表单信息有效,您应该只进行重定向;在提交错误的情况下,从同一编辑方法返回视图。

以这种方式做法是符合PRG的,因为如果模型无效,则不允许对服务器上的对象状态进行任何更改。 PRG的主要设计目的是防止多次提交,这可能会以不可预测的方式更改服务器对象的状态(例如业务对象、数据库表等);但在您的验证示例中,用户可以随意点击重新提交按钮,每次都会返回具有验证错误的初始视图--服务器上的任何内容都没有改变。因此,您需要正确处理:仅当您的模型通过呈现层的验证时才发出重定向。


1
这将损坏 PRG,而且很糟糕。如果您的服务器有请求配额(比如5分钟或重置),那么您所说的服务器上没有更改是错误的。此外,页面刷新应该将页面设置为初始状态,而按照您的建议,您最终会重新显示错误和状态。假设只有在主体发生更改时才引入服务器更改不是设计运行在具有更多关于标头、动词等信息的协议上的应用程序的好方法。您必须始终假定提交不是幂等的,如果您想实现良好的HTTP请求/响应流程。 - Bart Calixto

10

尽管Ken's answer确实强调了一个重要事实——PRG并不意味着"在发布时盲目返回重定向"——但有时仍然需要重定向并保留模型状态。

处理这种情况最简单的方法是使用操作筛选器将模型状态导出到会话中(在重定向之前),然后导入模型状态(在执行新操作之前)。Kazi Manzur Rashid撰写了几篇关于ASP.NET MVC最佳实践的博客文章(Part 1 Part 2)。它们已经相当古老,但其中许多提示仍然非常适用。第一篇文章中的第13条提示正是您要寻找的。


1
我使用了Kazi Manzur的修改版解决方案来处理验证失败,现在我非常喜欢“strict-PRG”。我知道这需要更多的努力,但是strict-PRG确实为用户提供了非常好的体验。 - Scott Rippey
我找不到任何可靠来源支持 Kazi Manzur 的“解决方案”建议的方法。他所链接的三篇博客文章中,其中一篇不再加载任何内容,另外两篇实际上主张使用 PRG(Post/Redirect/Get)模式,在成功时仅重定向并在失败时返回视图。这是一种极其糟糕的方法,因为它需要状态,而一个好的 HTTP 应用程序,也就是一个符合 RESTful 应用程序的标准,不应该需要状态。 - Chris Pratt
@ChrisPratt:我不知道你指的是哪些帖子无法加载 - 在第13个提示下他的所有(四个)链接都可以为我加载。这些博客文章(以及答案和问题...)非常古老,因此被认为是“最佳实践”的东西可能已经发生了变化 - 今天,显示无效帖子后显示表单的最佳方法很可能已经不是最佳方法。然而,我仍然认为这是有价值的知识 - 使用ActionFilter这种方式是一种锤子,但并不是所有东西都是匹配的钉子。像所有事情一样,明智地使用它,并在适当的地方使用,而不是盲目地和总是使用。 - Tomas Aschan
@Endri:这篇文章是2011年的,如果当前版本框架的最佳实践不同,我会感到非常惊讶。 - Tomas Aschan
是的,我同意...但这就是我从阅读处理破损链接时不知道该如何编辑它们所得到的。然而,这仍然是一个有价值的答案...最好编辑一下以删除文章的引用。 - Endri

-1

PRG 是正确的做法。

你可以向 action 发送 POST 请求,如果 modelstate 无效,只需将 modelstate 数据“导出”到一个变量中并重定向到 get action。

与接受的答案相比,这样做的好处是您不需要在 [Post] 操作上重新编写代码以重新创建视图。

在 get action 上,您可以加载从 post action 导出的 ModelState。

TempData 是执行此操作的绝佳位置,代码将类似于以下内容:

[HttpGet]
public ActionResult Edit(int id) {
    // import model state from tempdata
    ...
}

[HttpPost]
public ActionResult Edit(EditModel model) {
    // if modelstate is invalid
    // save modelstate in tempdata
    // redirect to Edit/{id}
    // else 
    ...
    RedirectToAction("List")
}

这可以使用AttributeFilters自动化处理,@ben-foster在此处创建了一个优秀的帖子:

模型状态的自动验证


2
我认为你在这里制造了一个草人论点:“相对于被接受的答案,这有好处,因为你不需要在 [Post] 操作上重写代码来重新创建视图。” 根据被接受的答案,你不需要重写任何代码,只需重新显示视图:“return View(model);” - Fenton
如果视图需要像访问数据库获取一些值或者做一些处理来创建视图模型,那么你应该在这两个方法(GET和POST)中获取数据。 - Bart Calixto
2
有其他避免代码重复的方法 - 尽管在这种情况下,模型被提交回来(请注意签名),因此不需要访问数据库。 - Fenton
我不知道如何避免那种代码重复,所以你在这一点上可能是正确的。关于模型发布,如果我清楚地理解了你所说的,人们会认为发布的视图模型没有重建视图所需的所有数据。例如,发布的选择列表只包括所选项的值,而不是实际的选择列表。 - Bart Calixto
使用TempData来处理表单提交是一种非常糟糕的方式,因为它使用了会话。PRG意味着在成功时重定向,而不是在失败时。失败时应该返回视图。这才是正确的REST。你现在所做的一切都与REST背道而驰,因为REST不支持状态,而TempData则需要状态。 - Chris Pratt
显示剩余3条评论

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