MVC3. Ajax Confirmation comes twice - asp.net-mvc-3

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>

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);
}

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.

Related

EditorTemplate + Model not being returned on POST

I can't seem to figure this out, it is driving me crazy!
Essentially, I have a list of rows I need to display with one drop down list per row.
I have a view model:
public class UserMembershipViewModel:BaseViewModel
{
public List<ProgramMembership> ProgramMembership { get; set; }
}
In my parent view I have, as you can see I am using an editor template which is located in "/Views/Shared/EditorTemplates/ProgramMembership.cshtml":
#using AcnCS.Model
#model AcnCS.Model.ViewModels.User.UserMembershipViewModel
#{
ViewBag.PageHeader = "Membership for " + Model.User.FullName;
ViewBag.PageTitle = "Membership for " + #Model.User.FullName;
ViewBag.HideNav = true;
}
#if (Model.ProgramMembership != null)
{
<div class="row-fluid">
<div class="span12">
<div id="permissions">
#using (Html.BeginForm())
{
<table class="table table-bordered">
<thead>
<tr>
<td>Program</td>
<td>Effective Membership?</td>
<td>Permission Type</td>
</tr>
</thead>
#Html.EditorFor(m => Model.ProgramMembership, "ProgramMembership")
</table>
<input type="submit" class="btn btn-primary" value="Save Changes"/>
}
</div>
</div>
</div>
}
My Editor template (ProgramMembership.cshtml) is:
#using AcnCS.Model
#model List<AcnCS.Model.ProgramMembership>
#foreach(ProgramMembership membership in Model)
{
<tr>
<td>#membership.ProgramName</td>
<td>
#if (membership.IsMember)
{
<span class="label label-success">#membership.IsMember</span>
}
else
{
#membership.IsMember
}
</td>
<td>#Html.DropDownListFor(x => membership.PermissionType, membership.PermissionTypes)</td>
</tr>
}
Everything is being displayed properly, but when I submit, my model object is null, even the ProgramMembership property in the model is null:
[HttpPost]
public ActionResult Membership(UserMembershipViewModel model)
{
// model IS NULL!!
return View(model);
}
Any help would be greatly appreciated!
I would pluralize the Property name since it is a collection, for better readability
public class UserMembershipViewModel:BaseViewModel
{
public List<ProgramMembership> ProgramMemberships { get; set; }
}
and you dont need a Loop inside your EditorTemplate file
#model AcnCS.Model.ProgramMembership
<tr>
<td>#membership.ProgramName</td>
<td>
#if (membership.IsMember)
{
<span class="label label-success">#membership.IsMember</span>
}
else
{
#membership.IsMember
}
</td>
<td>#Html.DropDownListFor(x => membership.PermissionType, membership.PermissionTypes)</td>
</tr>
In your main view,call your EditorTemplate like this
#Html.EditorFor(m=>m.ProgramMemberships)

How to pass a list of objects instead of one object to a POST action method

I have the following GET and POST action methods:-
public ActionResult Create(int visitid)
{
VisitLabResult vlr = new VisitLabResult();
vlr.DateTaken = DateTime.Now;
ViewBag.LabTestID = new SelectList(repository.FindAllLabTest(), "LabTestID", "Description");
return View();
}
//
// POST: /VisitLabResult/Create
[HttpPost]
public ActionResult Create(VisitLabResult visitlabresult, int visitid)
{
try
{
if (ModelState.IsValid)
{
visitlabresult.VisitID = visitid;
repository.AddVisitLabResult(visitlabresult);
repository.Save();
return RedirectToAction("Edit", "Visit", new { id = visitid });
}
}
catch (DbUpdateException) {
ModelState.AddModelError(string.Empty, "The Same test Type might have been already created,, go back to the Visit page to see the avilalbe Lab Tests");
}
ViewBag.LabTestID = new SelectList(repository.FindAllLabTest(), "LabTestID", "Description", visitlabresult.LabTestID);
return View(visitlabresult);
}
Currently the view display the associated fields to create only one object,, but how i can define list of objects instead of one object to be able to quickly add for example 10 objects at the same “Create” request.
My Create view look like:-
#model Medical.Models.VisitLabResult
#{
ViewBag.Title = "Create";
}
<h2>Create</h2>
#section scripts{
<script src="#Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
}
#using (Html.BeginForm())
{
#Html.ValidationSummary(true)
<fieldset>
<legend>VisitLabResult</legend>
<div class="editor-label">
#Html.LabelFor(model => model.LabTestID, "LabTest")
</div>
<div class="editor-field">
#Html.DropDownList("LabTestID", String.Empty)
Your viewModel
public class LabResult
{
public int ResultId { get; set; }
public string Name { get; set; }
//rest of the properties
}
Your controller
public class LabController : Controller
{
//
// GET: /Lab/ns
public ActionResult Index()
{
var lst = new List<LabResult>();
lst.Add(new LabResult() { Name = "Pravin", ResultId = 1 });
lst.Add(new LabResult() { Name = "Pradeep", ResultId = 2 });
return View(lst);
}
[HttpPost]
public ActionResult EditAll(ICollection<LabResult> results)
{
//savr results here
return RedirectToAction("Index");
}
}
Your view
#model IList<MvcApplication2.Models.LabResult>
#using (Html.BeginForm("EditAll", "Lab", FormMethod.Post))
{
<table>
<tr>
<th>
ResultId
</th>
<th>
Name
</th>
</tr>
#for (int item = 0; item < Model.Count(); item++)
{
<tr>
<td>
#Html.TextBoxFor(modelItem => Model[item].ResultId)
</td>
<td>
#Html.TextBoxFor(modelItem => Model[item].Name)
</td>
</tr>
}
</table>
<input type="submit" value="Edit All" />
}
Your view will be rendered as follows, this array based naming convention makes it possible for Defaultbinder to convert it into ICollection as a first parameter of action EditAll
<tr>
<td>
<input name="[0].ResultId" type="text" value="1" />
</td>
<td>
<input name="[0].Name" type="text" value="Pravin" />
</td>
</tr>
<tr>
<td>
<input name="[1].ResultId" type="text" value="2" />
</td>
<td>
<input name="[1].Name" type="text" value="Pradeep" />
</td>
</tr>
If I understand your question correctly,
you want to change your view to be a list of your model object #model List, then using a loop or however you wish to do it, create however many editors you need to for each object
then in your controller your receiving parameter of create will be a list of your model instead too.

why ajax.actionlink not refresh the page?

First,sorry to my bad english.
I don't understand why my page not refresh when i click on the delete user...
After the click i check in database and the user is delete but my page with table not refresh, i dont't understand.
My views is:
#model IEnumerable<SiteWebEmpty.Models.User.UserDisplay>
<script src="#Url.Content("~/Scripts/jquery.unobtrusive-ajax.min.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/MicrosoftAjax.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/MicrosoftMvcAjax.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/MicrosoftMvcAjax.debug.js")" type="text/javascript"></script>
<h2>Display User</h2>
<div id="deleteUser">
#using (Html.BeginForm())
{
<table class="tabledisplayUser" border="1">
<tr>
<th>Name FirstName</th>
<th>UserName</th>
<th>Roles</th>
<th>Choice</th>
</tr>
<tr>
<th>#Html.Editor("name")</th>
<th>#Html.Editor("username")</th>
<th>#Html.Editor("roles")</th>
<th>#Html.Editor("choice")</th>
</tr>
#foreach (var user in Model)
{
<tr>
<td class="action nameFirstName">#Html.DisplayFor(u => user.NameFirstName)</td>
<td class="action userName">#Html.DisplayFor(u => user.UserName)</td>
#if (user.Roles.Roles.Count.Equals(0))
{
<td>Nobody Role</td>
}
else
{
<td>#Html.DropDownList("Roles", user.Roles.Roles)</td>
}
<td>#Html.ActionLink("Edit", "Edit", new { UserName = user.UserName }) | #Ajax.ActionLink("Delete", "Delete", new { UserName = user.UserName },
new AjaxOptions()
{
HttpMethod = "POST",
Confirm = "Do you want delete this user?",
UpdateTargetId = "deleteUser"
})</td>
</tr>
}
</table>
}
</div>
My controller is:
public ActionResult DisplayUser()
{
List<UserDisplay> users=getAllUserInDB();
GetAllNameFirstNameLDAP(users);
return View(users);
}
public ActionResult Delete(String userName)
{
DeleteDB(userName);
if (!Request.IsAjaxRequest())
return RedirectToAction("DisplayUser");
else
{
List<UserDisplay> users = getAllUserInDB();
GetAllNameFirstNameLDAP(users);
return PartialView("DisplayUser",users);
}
}
I don't understand why it not working, thank you for your help !
UpdateTargetId = "deleteUser" means to refresh a DOM element with id="deleteUser". You don't have such element.
You have:
<div class="deleteUser">
which is not the same as:
<div id="deleteUser">
So replace the class with id and your table should refresh normally.

Parameters from view not getting to controller action method

I'm implementing Troy Goode's PagedList in one of my views (ASP.NET MVC 3 Razor). The challenge I'm having is when I click on a page number link, the request is routed to my HttpGet method, which just returns the empty page (ready for input).
My View Model:
public class SearchViewModel
{
public SelectList IndustrySelectList { get; set; }
public IPagedList<KeyValuePair<string, SearchResult>> SearchResults { get; set; }
public PagingInfo PagingInfo { get; set; }
}
Controller:
[HttpGet]
public ViewResult Search(string searchTerm = "")
{
SearchViewModel vm = new SearchViewModel
{
IndustrySelectList = new SelectList(_Industries.AsEnumerable(), "IndustryId", "IndustryName"),
PagingInfo = new PagingInfo
{
CurrentPage = 1,
ItemsPerPage = 25,
TotalItems = 0
}
};
return View(vm);
}
[HttpPost]
public ActionResult Search(string[] industries, string searchTerm = "", int page = 1)
{
SearchViewModel vm = null;
_url = "http://localhost/MasterNode/masternode.cgi?zoom_query={" + searchTerm + "}&zoom_xml=1&zoom_page={startPage?}&zoom_per_page=1000";
StringBuilder sb = new StringBuilder();
int pageSize = 5;
if (string.IsNullOrEmpty(searchTerm))
{
vm = new SearchViewModel
{
IndustrySelectList = new SelectList(_Industries.AsEnumerable(), "IndustryId", "IndustryName")
};
}
else
{
_request = new SearchRequest(SearchRequest.EnvironmentTypes.Development, "", _url, searchTerm, SearchRequest.SearchType.AllWords, 1000);
sb.Append(GetResults(_url));
_results = new Dictionary<string, SearchResult>();
ParseResults(sb);
GetDetailInformationForResults(searchTerm);
vm = new SearchViewModel
{
IndustrySelectList = new SelectList(_Industries.AsEnumerable(), "IndustryId", "IndustryName"),
SearchResults = _results.ToList<KeyValuePair<string, SearchResult>>().ToPagedList(1, 25),
PagingInfo = new PagingInfo
{
CurrentPage = page,
ItemsPerPage = pageSize,
TotalItems = _results.Count()
}
};
}
return View(vm);
}
View:
#model MultiView.OmniGuide.ViewModels.SearchViewModel
#using MultiView.OmniGuide.HtmlHelpers
#using PagedList
#using PagedList.Mvc
#{
ViewBag.Title = "Search";
}
<link href="/Content/PagedList.css" rel="stylesheet" type="text/css" />
#using (Html.BeginForm("Search", "Home"))
{
#Html.HiddenFor(c => c.IndustrySelectList)
#Html.HiddenFor(c => c.PagingInfo)
#Html.HiddenFor(c => c.SearchResults)
<table width="70%">
<tr>
<td colspan="2" style="background: #fff">
<input id="searchTerm" name="searchTerm" type="text" class="SearchBox" style="width: 450px" />
<input type="submit" class="SearchButton" value=" " />
</td>
</tr>
<tr align="left">
<td align="left" style="background: #fff">
#Html.ActionLink("MultiView corporate site", "Search")
</td>
</tr>
<tr>
<td colspan="1" align="center" style="width: 450px">
#{
Html.Telerik().PanelBar()
.Name("searchPanel")
.Items(title =>
{
title.Add()
.Text("Filter by Industry")
.Content(() =>
{
#Html.RenderPartial("_Industry", #Model);
});
})
.Render();
}
</td>
</tr>
<tr><td colspan="2"></td></tr>
</table>
<br />
if (Model.SearchResults != null)
{
<table width="70%">
<tr>
<th>
Company Image
</th>
<th class="tableHeader">
Company Name Here
</th>
<th class="tableHeader">
Website
</th>
</tr>
#foreach (KeyValuePair<string, MultiView.OmniGuide.Models.SearchResult> itm in Model.SearchResults)
{
<tr>
<td align="left" style="width: 15%">
#itm.Value.DetailedInfo.LogoURL
</td>
<td align="left" style="width: 60%">
<p style="text-align: left">
#itm.Value.DetailedInfo.DescriptionAbbreviated
<br />
</p>
#Html.AnchorLink(itm.Value.FoundURL, itm.Value.FoundURL)
</td>
<td style="width: 25%">
#itm.Value.FoundURL
</td>
</tr>
}
</table>
#Html.PagedListPager((IPagedList)Model.SearchResults, page => Url.Action("Search", "Home", new { page }))
}
}
When text is supplied in the input box and the button is clicked, the requested is routed to the HttpPost method. In looking at the request.form values, all expected data but paging information is present.
?HttpContext.Request.Form.AllKeys
{string[5]}
[0]: "IndustrySelectList"
[1]: "PagingInfo"
[2]: "SearchResults"
[3]: "searchTerm"
[4]: "industries"
Any help with this would be very much appreciated!
By clicking the button you are submitting the form which is why it is doing the httppost. The next page link is hitting the httpget correctly but you are not passing it any information to so that it knows what to get. The get needs other information, like what page you are wanting.
The page number links fire a GET request, so you'll need to make sure that your GET action can handle the full search as well, so will need to get the page number and industries array - using defaults for when those parameters aren't available.
e.g.
[HttpGet]
public ViewResult Search(string searchTerm = "", int page = 1,
string industries = "")
{
//.....
}
You'll need to modify the pager link like this to pass industries to the get action.
#Html.PagedListPager((IPagedList)Model.SearchResults, page => Url.Action("Search", "Home", new { page, industries = string.Join(",", Model.IndustrySelectList.Where( x => x.Selected).Select( x => x.Text)) }))
It's not clear to me from your code where the post action is getting string[] industries from, or what it is doing with it, but you will need some way of passing this same this to your get action, probably as a single string that is comma separated. The example I've provided assumed you are taken it from the select list on the viewmodel

MVC3 Partial view method not firing

I have a Partial view (Login, with username, password and Submit button), and the partial view is being used on my _layout (materpage).
So, on my _layout page, I have:
<div style="text-align: right">
#Html.Partial("_LoginPartial")
</div>
My _LoginPartial has the following code:
#if (Request.IsAuthenticated)
{
<textarea>Welcome!
[ #Html.ActionLink("Log Off", "Logout", "Account")]</textarea>
}
else
{
#Html.Partial("~/Views/Account/Index.cshtml")
}
The Index file to display the login box looks like this:
#using GalleryPresentation.Models
#model LoginModel
<script src="../../Scripts/jquery.validate.min.js" type="text/javascript"></script>
#using (Html.BeginForm("index", "Account"))
{
<table>
<tr>
<td>#Html.LabelFor(m => m.Username)</td>
<td>#Html.TextBoxFor(m => m.Username)</td>
</tr>
<tr>
<td>#Html.LabelFor(m => m.Password)</td>
<td>#Html.PasswordFor(m => m.Password) kjkj</td>
</tr>
<tr>
<td colspan="2"><input type="submit" value="Login"/></td>
</tr>
<tr>
<td colspan="2">#Html.ValidationSummary()</td>
</tr>
</table>
}
In my AccountCOntroller, I have the following code:
public ActionResult Index()
{
return View();
}
[HttpPost]
public ActionResult Index(LoginModel loginModel)
{
if(ModelState.IsValid)
{
var g = new GallaryImage();
var user = g.LoginUser(loginModel.Username, loginModel.Password);
if(user != null)
{
FormsAuthentication.SetAuthCookie(user.username, false);
return RedirectToAction("Index", "Home");
}
ModelState.AddModelError("", "Invalid Username/Password");
}
return View(loginModel);
}
public ActionResult Logout()
{
FormsAuthentication.SignOut();
return RedirectToAction("Index", "Home");
}
I have breakpoints on all methods - but they never get hit. Pressing the submit button simply changes my URL to:
http://localhost:8741/?Username=myusername&Password=mypassword
Can anyone spot the error I am making?
Since Html.BeginForm defaults to making GET requests, you are making a GET request with from your view. However, your action only accepts POST requests.
You can change #using (Html.BeginForm("index", "Account"))
to #using (Html.BeginForm("index", "Account", FormMethod.Post)).

Resources