2010-01-21 38 views
8

Tôi có một trang mua hàng và tôi không muốn người dùng có thể làm mới trang và gửi lại biểu mẫu khi họ đến trang 'hoàn thành đơn đặt hàng' vì nó tự động đặt chúng trong hệ thống của chúng tôi thông qua các giá trị và phí cơ sở dữ liệu thẻ của họ thông qua paypal (chỉ muốn những điều này xảy ra ONCE) ... Tôi đã thấy một số trang web có nội dung 'Không nhấn làm mới hoặc bạn sẽ bị tính phí hai lần!' nhưng đó là khá lame để lại nó mở cửa cho khả năng, một cách tốt để chỉ cho phép nó được gửi một lần hoặc ngăn chặn chúng làm mới, vv là gì?Phương pháp tốt để ngăn người dùng gửi biểu mẫu hai lần là gì?

PS: Tôi thấy một số câu hỏi tương tự: PHP: Stop a Form from being accidentally reprocessed when Back is pressedHow do I stop the Back and Refresh buttons from resubmitting my form? nhưng không tìm thấy câu trả lời thỏa đáng ... Câu trả lời cụ thể của ASP.NET MVC cũng lý tưởng nếu có cơ chế cho việc này.

EDIT: Khi họ nhấp vào, hãy gửi POSTS đến bộ điều khiển của tôi và sau đó trình điều khiển thực hiện một số phép thuật và sau đó trả về một chế độ xem có thông báo hoàn chỉnh đơn đặt hàng, nhưng nếu tôi nhấp vào làm mới trên trình duyệt của mình, toàn bộ 'bạn có muốn gửi lại biểu mẫu này? ' đó là xấu ...

+0

Bạn có thể nhận được nhiều câu trả lời nếu bạn tag nó như là ngôn ngữ-agnostic, như nó không chỉ là ASP.NET MVC mà bị ảnh hưởng, nhưng tất cả các ngôn ngữ và khuôn khổ tương tác với các biểu mẫu web. (Tất nhiên, bạn có thể nhận được mã nếu bạn để lại thẻ ASP.NET MVC ở đó). – Macha

Trả lời

17

Giải pháp tiêu chuẩn cho điều này là mẫu POST/REDIRECT/GET. Mô hình này có thể được triển khai bằng cách sử dụng khá nhiều nền tảng phát triển web. Bạn sẽ thường:

  • Validate nộp sau khi POST
  • nếu nó thất bại lại làm cho mẫu đăng ký ban đầu với lỗi xác nhận hiển thị
  • nếu nó thành công, chuyển hướng đến một trang xác nhận, hoặc trang mà bạn tái hiển thị đầu vào - đây là phần GET
  • vì hành động cuối cùng là GET, nếu người dùng làm mới tại thời điểm này, sẽ không có biểu mẫu gửi lại nào xảy ra.
+6

Mà, trong ASP.Net MVC, được thực thi bằng cách đơn giản thực hiện một "return RedirectToAction()". – womp

+0

Làm cách nào để tránh hành động đó bị ảnh hưởng bởi bất kỳ điều gì khác ngoài chuyển hướng từ hành động xử lý yêu cầu biểu mẫu? – Maritim

+0

@Maritim Tôi chưa bao giờ phải làm điều này. Tôi thường quay lại trang xem chuẩn hoặc trang "cảm ơn" nhưng bạn có thể chuyển thông số chuỗi truy vấn mà trang mong đợi hoặc đặt biến phiên để cho yêu cầu tiếp theo. – RedFilter

2

Trong khi phục vụ trang xác nhận đơn đặt hàng, bạn có thể đặt mã thông báo mà bạn cũng lưu trữ trong DB/Cache. Tại lần xác nhận thứ tự đầu tiên, hãy kiểm tra sự tồn tại của mã thông báo này và xóa mã thông báo. Nếu được thực hiện với an toàn luồng, bạn sẽ không thể gửi đơn đặt hàng hai lần.

Đây chỉ là một trong nhiều cách tiếp cận có thể.

+0

Là phần thưởng thêm, giải pháp này cũng ngăn cản việc gửi biểu mẫu trùng lặp do người dùng nhấn nút gửi hai lần. – Lex

1

Cung cấp cho mỗi khách truy cập một biểu mẫu ID duy nhất khi trang được tải lần đầu tiên. Lưu ý ID khi biểu mẫu được gửi. Khi một biểu mẫu đã được gửi cùng với ID đó, không cho phép bất kỳ yêu cầu nào khác sử dụng nó. Nếu họ nhấp vào làm mới, cùng một ID sẽ được gửi.

-1

Tắt đầu của tôi, tạo một System.Guid trong trường ẩn trên yêu cầu GET của trang và liên kết nó với thanh toán/thanh toán của bạn. Chỉ cần kiểm tra và hiển thị thông báo cho biết 'Thanh toán đã được xử lý'. hoặc như vậy.

0

Chỉ cần thực hiện chuyển hướng từ trang thực hiện tất cả nội dung khó chịu với trang "Cảm ơn bạn đã đặt hàng". Sau khi thực hiện điều đó, người dùng có thể nhấn làm mới bao nhiêu lần tùy thích.

-1

Kazi Manzur Rashid đã viết về this (cùng với các thực hành tốt nhất khác về asp.net mvc). Ông đề xuất sử dụng hai bộ lọc để xử lý truyền dữ liệu giữa POST và follwing GET bằng cách sử dụng TempData.

12

Tôi 100% đồng ý với câu trả lời chung của RedFilter, nhưng muốn đăng một số mã có liên quan cho ASP.NET MVC cụ thể.

Bạn có thể sử dụng số Post/Redirect/Get (PRG) Pattern để giải quyết vấn đề hậu kép.

Dưới đây là một minh hoạ đồ họa của vấn đề:

Diagram

gì sẽ xảy ra là khi các số truy cập sử dụng làm mới, trình duyệt sẽ gửi lại yêu cầu cuối cùng nó thực hiện. Nếu yêu cầu cuối cùng là một bài đăng, trình duyệt sẽ cố gắng thực hiện điều đó.

Hầu hết các trình duyệt biết rằng đây không phải là điển hình mà người dùng muốn làm, như vậy sẽ tự động hỏi:

Chrome - Các trang mà bạn đang tìm kiếm thông tin đã qua sử dụng mà bạn đã nhập. Quay lại trang đó có thể khiến bạn phải lặp lại bất kỳ hành động nào. Bạn có muốn tiếp tục không?
Firefox - Để hiển thị trang này, Firefox phải gửi thông tin lặp lại bất kỳ hành động nào (chẳng hạn như tìm kiếm hoặc xác nhận đơn đặt hàng) đã được thực hiện trước đó.
Safari - Bạn có chắc chắn muốn gửi lại biểu mẫu không? Để mở lại trang này, Safari phải gửi lại biểu mẫu. Điều này có thể dẫn đến mua hàng trùng lặp, nhận xét hoặc các hành động khác.
Internet Explorer - Để hiển thị lại trang web, trình duyệt web cần phải gửi lại thông tin bạn đã gửi trước đó. Nếu bạn đã mua hàng, bạn nên nhấp vào Hủy để tránh giao dịch trùng lặp. Nếu không, hãy nhấp vào Thử lại để hiển thị lại trang web.

Nhưng mẫu PRG giúp tránh điều này hoàn toàn bằng cách gửi cho khách hàng thông báo chuyển hướng để cuối cùng trang xuất hiện, yêu cầu cuối cùng mà trình duyệt đã thực hiện là yêu cầu GET cho tài nguyên mới.

Đây là một great article on PRG cung cấp triển khai mẫu cho MVC. Điều quan trọng cần lưu ý là bạn chỉ muốn sử dụng chuyển hướng khi hành động không phải là idempotent được thực hiện trên máy chủ. Nói cách khác, nếu bạn có một mô hình hợp lệ và đã thực sự lưu giữ dữ liệu theo một cách nào đó, thì điều quan trọng là phải đảm bảo yêu cầu không được gửi ngẫu nhiên hai lần. Nhưng nếu mô hình không hợp lệ, trang và mô hình hiện tại phải được trả lại để người dùng có thể thực hiện bất kỳ sửa đổi cần thiết nào.

Dưới đây là một điều khiển dụ:

[HttpGet] 
public ActionResult Edit(int id) { 
    var model = new EditModel(); 
    //... 
    return View(model); 
} 

[HttpPost] 
public ActionResult Edit(EditModel model) { 
    if (ModelState.IsValid) { 
     product = repository.SaveOrUpdate(model); 
     return RedirectToAction("Details", new { id = product.Id }); 
    } 
    return View(model); 
} 

[HttpGet] 
public ActionResult Details(int id) { 
    var model = new DetailModel(); 
    //... 
    return View(model); 
} 
1

Lưu ý rằng mô hình PRG không hoàn toàn bảo vệ chống lại nhiều hình thức đệ trình, như là post nhiều yêu cầu có thể được bắn ra ngay cả trước khi chuyển hướng đơn đã diễn ra - điều này có thể dẫn để gửi biểu mẫu của bạn không phải là idempotent.

Bạn lưu ý các câu trả lời đã được cung cấp here, cung cấp một workaround cho vấn đề này, mà tôi trích dẫn dưới đây để tiện:

Nếu bạn sử dụng một mã thông báo chống giả mạo ẩn trong bạn biểu mẫu (như bạn nên), bạn có thể lưu bộ nhớ cache mã thông báo chống giả mạo vào lần gửi đầu tiên và xóa mã thông báo khỏi bộ nhớ cache nếu được yêu cầu hoặc hết hạn mục nhập đã lưu trong bộ nhớ cache sau khoảng thời gian đã đặt.

Sau đó, bạn sẽ có thể kiểm tra với từng yêu cầu đối với bộ nhớ cache cho dù biểu mẫu cụ thể đã được gửi và từ chối nó nếu có.

Bạn không cần tạo GUID của riêng mình vì điều này đã được thực hiện khi tạo mã thông báo chống giả mạo.

0

Nếu bạn không thích chuyển hướng người dùng đến trang khác, sau đó bằng cách sử dụng cách của tôi bạn liều không cần Post/Redirect/Nhận (PRG) Pattern và người dùng vẫn còn trên trang hiện tại mà không sợ các tác động tiêu cực của việc gửi lại biểu mẫu!

tôi sử dụng một mục TempDataHidden field (một tài sản trong ViewModel của form) để giữ cùng một Guid ở cả hai bên (Server/Client) và nó là dấu hiệu của để phát hiện nếu mẫu được gửi lại lần bởi refresh hay không.

mặt cuối cùng của mã trông giống như rất ngắn và đơn giản:

Hành động:

[HttpPost] 
public virtual ActionResult Order(OrderViewModel vModel) 
{ 
    if (this.IsResubmit(vModel)) // << Check Resubmit 
    { 
     ViewBag.ErrorMsg = "Form is Resubmitting"; 
    } 
    else 
    { 
     // .... Post codes here without any changes... 
    } 

    this.PreventResubmit(vModel);// << Fill TempData & ViewModel PreventResubmit Property 

    return View(vModel) 
} 

Trong Xem:

@if (!string.IsNullOrEmpty(ViewBag.ErrorMsg)) 
{ 
    <div>ViewBag.ErrorMsg</div> 
} 

@using (Html.BeginForm(...)){ 

    @Html.HiddenFor(x=>x.PreventResubmit) // << Put this Hidden Field in the form 

    // Others codes of the form without any changes 
} 

Trong Xem mẫu:

public class OrderViewModel: NoResubmitAbstract // << Inherit from NoResubmitAbstract 
{ 
    // Without any changes! 
} 

Bạn nghĩ sao?


tôi làm cho nó đơn giản bằng cách viết 2 lớp:

  • NoResubmitAbstract lớp trừu tượng
  • ControllerExtentions lớp tĩnh (Một lớp Extension for System.Web.Mvc.ControllerBase)

ControllerExtentions:

public static class ControllerExtentions 
{ 
    [NonAction] 
    public static bool IsResubmit (this System.Web.Mvc.ControllerBase controller, NoResubmitAbstract vModel) 
    { 
     return (Guid)controller.TempData["PreventResubmit"]!= vModel.PreventResubmit; 
    } 

    [NonAction] 
    public static void PreventResubmit(this System.Web.Mvc.ControllerBase controller, params NoResubmitAbstract[] vModels) 
    { 
     var preventResubmitGuid = Guid.NewGuid(); 
     controller.TempData["PreventResubmit"] = preventResubmitGuid ; 
     foreach (var vm in vModels) 
     { 
      vm.SetPreventResubmit(preventResubmitGuid); 
     } 
    } 
} 

NoResubmitAbstract:

public abstract class NoResubmitAbstract 
{ 
    public Guid PreventResubmit { get; set; } 

    public void SetPreventResubmit(Guid prs) 
    { 
     PreventResubmit = prs; 
    } 
} 

Chỉ cần đặt chúng trong dự án MVC của bạn và chạy nó ...;)

Các vấn đề liên quan