UPDATE 2012-07-19
另外一篇文章有更多改进的方法
http://www.cnblogs.com/wuchang/archive/2010/01/29/1658916.html
适用场景:
假如你要设计一个注册页面,上面有几个填写注册信息的textbox,一个用于提交注册信息的按钮和一个用于返回首页的按钮
可有如下选择方案:
方案1:每个按钮都会提交表单,但给按钮分配不同的value,用于逻辑中进行判断提交事件由谁触发
~/Views/Account/Register.aspx
1: <% using (Html.BeginForm()) { %>2: <div>3: <fieldset>4: <legend>Account Information</legend>5: <p>6: <label for="username">Username:</label>7: <%= Html.TextBox("username") %>8: <%= Html.ValidationMessage("username") %>9: </p>10: <p>11: <label for="email">Email:</label>12: <%= Html.TextBox("email") %>13: <%= Html.ValidationMessage("email") %>14: </p>15: <p>16: <label for="password">Password:</label>17: <%= Html.Password("password") %>18: <%= Html.ValidationMessage("password") %>19: </p>20: <p>21: <label for="confirmPassword">Confirm password:</label>22: <%= Html.Password("confirmPassword") %>23: <%= Html.ValidationMessage("confirmPassword") %>24: </p>25: <button name="button" value="register">Register</button>26: <button name="button" value="cancel">Cancel</button>27: </p>28: </fieldset>29: </div>30: <% } %>
~/Controllers/AccountController.cs
1: [AcceptVerbs(HttpVerbs.Post)]2: public ActionResult Register(string button, string userName, string email, string password, string confirmPassword)3: {4: if (button == "cancel")5: return RedirectToAction("Index", "Home");6:7: ViewData["PasswordLength"] = MembershipService.MinPasswordLength;
8:9: if (ValidateRegistration(userName, email, password, confirmPassword))
10: {11: // Attempt to register the user
12: MembershipCreateStatus createStatus = MembershipService.CreateUser(userName, password, email);13:14: if (createStatus == MembershipCreateStatus.Success)
15: {16: FormsAuth.SignIn(userName, false /* createPersistentCookie */);17: return RedirectToAction("Index", "Home");18: }19: else
20: {21: ModelState.AddModelError("_FORM", ErrorCodeToString(createStatus));
22: }23: }24:25: // If we got this far, something failed, redisplay form
26: return View();
27: }
这个方案的副作用就是你不得不在controller添加条件判断的逻辑,并且两个按钮都提交表单内容然后由服务端进行判断跳转。
为了让controller看起来稍微那么优雅点儿,可以通过自定义ActionMethodSelectorAttribute的方法实现,如下:
1: public class AcceptParameterAttribute : ActionMethodSelectorAttribute2: {3: public string Name { get; set; }4: public string Value { get; set; }5:6: public override bool IsValidForRequest(ControllerContext controllerContext, MethodInfo methodInfo)7: {8: var req = controllerContext.RequestContext.HttpContext.Request;9: return req.Form[this.Name] == this.Value;10: }11: }12:
Now I can split into two action methods like this:
1: [ActionName("Register")]
2: [AcceptVerbs(HttpVerbs.Post)]3: [AcceptParameter(Name="button", Value="cancel")]4: public ActionResult Register_Cancel()
5: {6: return RedirectToAction("Index", "Home");7: }8:9: [AcceptVerbs(HttpVerbs.Post)]10: [AcceptParameter(Name="button", Value="register")]11: public ActionResult Register(string userName, string email, string password, string confirmPassword)12: {13: // process registration
14: }
同样,这个也不是最有效的方法,但它毕竟让你用两个controller的方法分别响应两个不同的按钮
方案2:另一个form
1: <% using (Html.BeginForm()) { %>2: <div>3: <fieldset>4: <legend>Account Information</legend>5: <p>6: <label for="username">Username:</label>7: <%= Html.TextBox("username") %>8: <%= Html.ValidationMessage("username") %>9: </p>10: <p>11: <label for="email">Email:</label>12: <%= Html.TextBox("email") %>13: <%= Html.ValidationMessage("email") %>14: </p>15: <p>16: <label for="password">Password:</label>17: <%= Html.Password("password") %>18: <%= Html.ValidationMessage("password") %>19: </p>20: <p>21: <label for="confirmPassword">Confirm password:</label>22: <%= Html.Password("confirmPassword") %>23: <%= Html.ValidationMessage("confirmPassword") %>24: </p>25: <p>26: <button name="button">Register</button>27: <button name="button" type="button" onclick="$('#cancelForm').submit()">Cancel</button>28: </p>29: </fieldset>30: </div>31: <% } %>32: <% using (Html.BeginForm("Register_Cancel", "Account", FormMethod.Post, new { id="cancelForm" })) {} %>33:
我所做的就是在注册表单下面添加一个新的form,表单提交内容的处理指向另外一个action方法;然后将取消按钮(cancel)的type设为"button",这样就可以通过为这个按钮添加一个onclick的客户端事件(使用jQuery)提交另外一个表单(cancelForm)。这个方法比第一个更有效率,它不会提交注册表单中的内容,但它依然不是最有效率的方案,因为它同样使用服务端控制跳转。
方案3:全部使用客户端脚本
1: <p>2: <button name="button">Register</button>3: <button name="button" type="button" onclick="document.location.href=$('#cancelUrl').attr('href')">Cancel</button>4: <a id="cancelUrl" href="<%= Html.AttributeEncode(Url.Action("Index", "Home")) %>" style="display:none;"></a>5: </p>
这是处理cancel按钮的最有效的方式,这里没有同服务端进行交互以便获得跳转的url。我在这里放了一个隐藏(display:none)的有url的<a>锚链,使用button配合客户端脚本,即可实现;如果把隐藏的<a>显示出来替代cancel按钮,同样可以实现。其实现原理是通过将<a>伪装成button在客户端进行跳转。
结论:
当我设计MVC应用时,有一个参考标准来选择什么样类型的button
- 如果button用于提交本身所在的表单,如 “Save”, “Update”, “Ok”, “Submit” ,那就用标准的button(<button/>)
- 如果button需要提交一些数据到服务端,如 “Delete” ,那就使用type="button"的button同时配合onclick客户端事件提交指定的form表单,详细见文章上述部分
- 如果button只是用来导航跳转,如 “Cancel”, “Back”, “Next”, “Prev” ,那就使用<a>来替代(把a装饰成button的样子),或者使用button配合脚本进行简单的跳转处理