ASP.NET MVC Core/6:多个提交按钮

Posted

技术标签:

【中文标题】ASP.NET MVC Core/6:多个提交按钮【英文标题】:ASP.NET MVC Core/6: Multiple submit buttons 【发布时间】:2016-08-02 00:04:19 【问题描述】:

我需要多个提交按钮来在控制器中执行不同的操作。

我在这里看到了一个优雅的解决方案:How do you handle multiple submit buttons in ASP.NET MVC Framework? 使用此解决方案,可以使用自定义属性装饰操作方法。处理路由时,此自定义属性的方法会检查属性的属性是否与单击的提交按钮的名称匹配。

但在 MVC Core (RC2 nightly build) 中,我没有找到 ActionNameSelectorAttribute(我还搜索了 Github 存储库)。我找到了一个类似的解决方案,它使用ActionMethodSelectorAttribute (http://www.dotnetcurry.com/aspnet-mvc/724/handle-multiple-submit-buttons-aspnet-mvc-action-methods)。

ActionMethodSelectorAttribute 可用,但方法 IsValidForRequest 具有不同的签名。有一个RouteContext 类型的参数。但是我在那里找不到帖子数据。所以我没有什么可以与我的自定义属性进行比较的。

在 MVC Core 中是否有类似以前的 MVC 版本中可用的优雅解决方案?

【问题讨论】:

根据您是否喜欢这样做,可能会起作用的是使用客户端(又名:JQuery)更改
元素的实际 URL。甚至在单击时,找出单击了哪个按钮,更改了
元素的 [action] 属性并提交()
.
【参考方案1】:

您可以为此使用 html5 @987654321@ 属性,而不是在服务器端路由它。

<form action="" method="post">
    <input type="submit" value="Option 1" formaction="DoWorkOne" />
    <input type="submit" value="Option 2" formaction="DoWorkTwo"/>
</form>

然后只需像这样的控制器操作:

[HttpPost]
public IActionResult DoWorkOne(TheModel model)  ... 

[HttpPost]
public IActionResult DoWorkTwo(TheModel model)  ... 

对于旧版浏览器来说,一个好的 polyfill 可以是 found here。

请记住...

    当用户按下回车键时,总是会选择第一个提交按钮。 如果发布的操作也出现错误 - ModelState 或其他错误,则需要将用户发送回正确的视图。 (不过,如果您通过 AJAX 发布,这不是问题。)

【讨论】:

完美!似乎我必须更详细地研究 HTML5。奇怪的是:我在 Index-Action 上有表格。所以:/AreaName/ControllerName。单击按钮后,我得到:/AreaName/ButtonFormActionName。控制器部分丢失。当我从/AreaName/ControllerName/ActionName 开始时,它可以工作。 啊,好收获!在这种情况下,您将需要使用 Url.Action(actionName, controllerName) 辅助方法为 formaction 属性生成正确的目标。 完美的解决方案,但有一个陷阱,如果我们使用你的方式,第一个动作是 /post/add ,第二个动作是 /post/addcontinue 。如果用户单击 addcontinue 按钮并且页面存在验证问题,那么我们将表单发回用户并要求他更正错误。但是由于我们返回 addcontinue 操作,页面 url 更改为 /post/addcontinue ,所以如果用户刷新页面,浏览器会向 /post/addcontinue 发出 get 请求,并且我们完成了 post/addcontinue 的任何 get 操作。在两个按钮中保持页面 url 与 /post/add 一致的最佳解决方案是什么。 @MohammadAkbari 这是具有多个提交按钮所固有的问题,不幸的是,MVC4 和 5 的先前答案也发生了。有几种不同的方法可以实现您正在寻找的东西,但这取决于用例。例如,AJAX 表单提交就是一种选择。 这适用于 ASP.NET Core 2.0 MVC。它使使用多个表单部分变得更加容易。它还简化了调用之间使用的 ModelView 数据的隐藏字段。做得好。我不知道按钮的formaction字段存在。我遇到了一个问题,即模型不同部分的表单中的模型字段被清除,因为帖子之间只保留了单个表单数据。【参考方案2】:

ASP.NET Core 1.1.0 具有创建 formaction 属性的 FormActionTagHelper

<form>
    <button asp-action="Login" asp-controller="Account">log in</button>
    <button asp-action="Register" asp-controller="Account">sign up</button>
</form>

呈现如下:

<button formaction="/Account/Login">log in</button>
<button formaction="/Account/Register">sign up</button>

它也适用于input 标签type="image"type="submit"

【讨论】:

嘿 suan,我注意到的一件事是,如果我验证模型并且它不正确,则 url 更改为动作名称。知道如何解决这个问题吗?类似于: ModelState.AddModelError(string.Empty, "我们在表单中的数据存在问题"); return View("Edit", editModel);【参考方案3】:

我之前和过去都这样做过,我会将表单发布到不同的控制器操作。问题是,在服务器端验证错误上,您要么陷入困境:

    return View(vm) 将帖子操作名称留在网址中……呸。 return Redirect(...) 需要使用TempData 来保存ModelState。也很糟糕。

这是我选择做的事情。

    使用按钮的name 绑定到POST 上的变量。 按钮value是一个枚举来区分提交动作。 Enum 是类型安全的,并且在 switch 语句中效果更好。 ;) POST 到与 GET 相同的操作名称。这样一来,您就不会在服务器端验证错误的 URL 中获得 POST 操作名称。 如果出现验证错误,请按照正确的 PGR 模式重建您的视图模型和 return View(viewModel)

使用这种技术,就不用TempData了!

在我的用例中,我有一个用户/详细信息页面,其中包含“添加角色”和“删除角色”操作。

这里是按钮。它们可以是按钮而不是输入标签... ;)

<button type="submit" class="btn btn-primary" name="SubmitAction" value="@UserDetailsSubmitAction.RemoveRole">Remove Role</button>
<button type="submit" class="btn btn-primary" name="SubmitAction" value="@UserDetailsSubmitAction.AddRole">Add Users to Role</button>

这里是控制器动作。我将 switch 代码块重构为它们自己的函数,以使它们更易于阅读。我必须发布到 2 个不同的视图模型,因此不会填充一个,但模型绑定器不在乎!

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Details(
    SelectedUserRoleViewModel removeRoleViewModel, 
    SelectedRoleViewModel addRoleViewModel,
    UserDetailsSubmitAction submitAction)

    switch (submitAction)
    
        case UserDetailsSubmitAction.AddRole:
        
            return await AddRole(addRoleViewModel);
        
        case UserDetailsSubmitAction.RemoveRole:
        
            return await RemoveRole(removeRoleViewModel);
        
        default:
            throw new ArgumentOutOfRangeException(nameof(submitAction), submitAction, null);
    


private async Task<IActionResult> RemoveRole(SelectedUserRoleViewModel removeRoleViewModel)

    if (!ModelState.IsValid)
    
        var viewModel = await _userService.GetDetailsViewModel(removeRoleViewModel.UserId);
        return View(viewModel);
    

    await _userRoleService.Remove(removeRoleViewModel.SelectedUserRoleId);

    return Redirect(Request.Headers["Referer"].ToString());


private async Task<IActionResult> AddRole(SelectedRoleViewModel addRoleViewModel)

    if (!ModelState.IsValid)
    
        var viewModel = await _userService.GetDetailsViewModel(addRoleViewModel.UserId);
        return View(viewModel);
    

    await _userRoleService.Add(addRoleViewModel);

    return Redirect(Request.Headers["Referer"].ToString());

您也可以post the form using AJAX。

【讨论】:

【参考方案4】:

更好的答案是使用jQuery Unobtrusive AJAX 并忘记所有的混乱。

    您可以为控制器操作指定语义名称。 您不必重定向或使用临时数据。 在服务器端验证错误的 URL 中没有发布操作名称。 在服务器端验证错误时,您可以返回表单或仅返回错误消息。

【讨论】:

以上是关于ASP.NET MVC Core/6:多个提交按钮的主要内容,如果未能解决你的问题,请参考以下文章

ASP.NET MVC 3以一种形式提交多个输入

ASP.NET Core 6框架揭秘实例演示[02]:基于路由MVC和gRPC的应用开发

《ASP.NET Core 6框架揭秘》实例演示[02]:基于路由MVC和gRPC的应用开发

C# ASP.NET MVC:提交按钮后不会存储数据

提交按钮未在 ASP.NET MVC Web 应用程序中触发

Jquery 验证单击提交按钮两次 ASP.Net 核心 MVC 项目