MVC3. Ajax Confirmation comes twice

2019-08-25 01:21发布

问题:

I have a list of Products. Each line has 'Delete' action. When I try to delete any row everything is true, but after second deleting ajax confirmation comes twice. Please help.

There are product list view.

@model IEnumerable<Domain.Entities.Product>
@{
ViewBag.Title = "Products";
Layout = "~/Views/Shared/_AdminLayout.cshtml";
}
<h1>Products</h1>
<table class="Grid">
<tr>
    <th>Name</th>
    <th>Description</th>
    <th>Price</th>
</tr>
@foreach (var item in Model) {
    <tr>
        <td>@item.Name</td>
        <td>@item.Description</td>
        <td>@item.Price</td>
        <td>
            @using (Ajax.BeginForm("DeleteProduct", "Admin", 
                    new AjaxOptions { 
                                      Confirm="Product was deleted!", 
                                      UpdateTargetId="DeleteProduct"
                                    }))
            {
                @Html.Hidden("Id", item.Id)
                <input type="image" src="../Images/Icons/DeleteIcon.jpg" />
            }
        </td>
    </tr>
}
</table>

There are AdminController

[Authorize]
public class AdminController : Controller
{
    private IProductRepository productRepository;

    public AdminController(IProductRepository productRepository)
    {
        this.productRepository= productRepository;
    }

    public ViewResult Products()
    {
        return View(productRepository.Products);
    }

    [HttpPost]
    public ActionResult DeleteProduct(int id)
    {
        Product prod = productRepository.Products.FirstOrDefault(p => p.Id == id);
        if (prod != null)
        {
            productRepository.DeleteProduct(prod);
            TempData["message"] = string.Format("{0} was deleted", prod.Name);
        }
        return RedirectToAction("Products");
    }
}

And finally _AdminLayout.cshtml

<html>
<head>
<title>@ViewBag.Title</title>
<link href="@Url.Content("~/Content/Admin.css")" rel="stylesheet" type="text/css" />
<script src="@Url.Content("~/Scripts/jquery-1.4.4.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.unobtrusive-ajax.js")" type="text/javascript"></script>
</head>
<body>
<div id="DeleteProduct">
    @if (TempData["message"] != null) {
        <div class="Message">@TempData["message"]</div>
    }
    @RenderBody()
</div>
</body>
</html>

回答1:

The problem here is that you are calling the DeleteProduct action with AJAX and this action is performing a Redirect to the Products action. Except that the Products action is returning a full HTML instead of a partial. So you get the jquery.unobtrusive-ajax.js injected twice into your DOM. So you get 2 confirmations on the second delete, 3 on the third and so on.

So start by defining a partial containing the table records (~/Views/Admin/_Products.cshtml):

@model IEnumerable<Domain.Entities.Product>

<div>@ViewData["message"]</div>

<table class="Grid">
    <thead>
        <tr>
            <th>Name</th>
            <th>Description</th>
            <th>Price</th>
        </tr>
    </thead>
    <tbody>
        @foreach (var item in Model) 
        {
            <tr>
                <td>@item.Name</td>
                <td>@item.Description</td>
                <td>@item.Price</td>
                <td>
                    @using (Ajax.BeginForm("DeleteProduct", "Admin", 
                            new AjaxOptions { 
                                Confirm = "Are you sure you wanna delete this product?", 
                                UpdateTargetId = "products"
                            })
                    )
                    {
                        @Html.Hidden("Id", item.Id)
                        <input type="image" src="@Url.Content("~/Images/Icons/DeleteIcon.jpg")" alt="Delete" />
                    }
                </td>
            </tr>
        }
    </tbody>
</table>

and then modify your main view so that it uses this partial:

@model IEnumerable<Product>

@{
    ViewBag.Title = "Products";
    Layout = "~/Views/Shared/_AdminLayout.cshtml";
}

<h1>Products</h1>
<div id="products">
    @Html.Partial("_Products")
</div>

and finally modify your DeleteProduct controller action so that it no longer does any redirects but returns the partial instead after deleting the record:

[HttpPost]
public ActionResult DeleteProduct(int id)
{
    Product prod = productRepository.Products.FirstOrDefault(p => p.Id == id);
    if (prod != null)
    {
        productRepository.DeleteProduct(prod);
        ViewData["message"] = string.Format("{0} was deleted", prod.Name);
    }

    return PartialView("_Products", productRepository.Products);
}


回答2:

You have everything funneled through a single AJAX operation, so the click of delete is finding two items to delete on the second click. The way to handle this is work some magic on resetting the bound items so either a) the deleted item is set up to show it is already deleted once confirmed or b) rebinding the entire set of items after a delete to get rid of the item that has been deleted.

As long as you continue to have an item that the client believes has not been deleted, you will continue to "delete both" each time you click.