適用場景:
假如你要設計一個註冊頁面,上面有幾個填寫註冊信息的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 : ActionMethodSelectorAttribute
2: {
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配合腳本進行簡單的跳轉處理