MVC3 Partial view method not firing - asp.net-mvc-3

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)).

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.

asp.net mvc partial view redirect or show error

I have a login widget that is a partial view that I want updated with model validation errors when there is a problem otherwise the page should be reloaded.
I have the following
LogOn.cshtml
#{
var ajaxOpts = new AjaxOptions {OnSuccess = "success"};
}
<div id="login">
#Html.ValidationSummary(excludePropertyErrors: true)
<h2>Start by Logging in</h2>
#using (Ajax.BeginForm("LogOn", "Account", ajaxOpts))
{
#Html.Hidden("returnUrl", Request.Url.PathAndQuery)
<table width="100%" border="0" cellspacing="0" cellpadding="5">
<tr>
<td>
<span class="bluey">Username:</span><br />
#Html.TextBoxFor(m => m.UserName, new {tabindex = "1", Class = "field"})
#Html.ValidationMessageFor(m => m.UserName, "*")
</td>
<td>
<span class="bluey">Password:</span><br />
#Html.TextBoxFor(m => m.Password, new {tabindex = "2", Class = "field"})
#Html.ValidationMessageFor(m => m.Password, "*")
</td>
</tr>
<tr>
<td>
<input name="login" type="submit" value="Submit" class="input_btn" tabindex="3" />
</td>
<td>#Html.CheckBoxFor(m => m.RememberMe) #Html.LabelFor(m => m.RememberMe) <span class="bluey"> | </span> #Html.ActionLink("Forgot Password?", "Password", "User")</td>
</tr>
</table>
}
</div>
<script>
function success(context) {
//var returnUrl = context.get_data().returnUrl;
//if (returnUrl) {
// window.location.href = returnUrl;
//} else {
$("#login").replaceWith(context);
//}
}
</script>
and the action that gets called is
[HttpPost]
public ActionResult LogOn(LogOnModel userDetails, string returnUrl)
{
if (ModelState.IsValid)
{
if (Membership.ValidateUser(userDetails.UserName, userDetails.Password))
{
FormsAuthentication.SetAuthCookie(userDetails.UserName, userDetails.RememberMe);
return Redirect(returnUrl);
}
ModelState.AddModelError("", "The username or password provided was incorrect");
}
return PartialView(userDetails);
}
I have it working so that when data is incorrect the partial view is redisplayed with errors however this part only works if I comment out the other lines of javascript in success(context) that are there for when the user logs in successfully and the page should be redirected.
Is there a better way to do this?
I did try returning anonymous objects with Json() that had a status property and either a returnUrl or Html however I could not figure out how to get the html that would have been generated by PartialView(userDetails) into the Json() call, and it seems like that might be the wrong way to go about things anyway.
You cannot redirect in an AJAX call. You could return a JSON object from the server pointing to the url to redirect to and then perform the actual redirect in your success callback.
function success(result) {
if (result.returnUrl) {
// the controller action returned JSON
window.location.href = returnUrl;
} else {
// the controller action returned a partial
$('#login').html(result);
}
}
Now in your controller action in case of success simply return the return url instead of redirecting:
return Json(new { returnUrl = returnUrl });
By the way you already seem to have the return url in the view: Request.Url.PathAndQuery. I don't see the point of having it transit to the server and back. You could simply return some JSON boolean indicating the success and perform the redirect in the success callback to the url.

MVC3. Ajax Confirmation comes twice

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.

Validation firing for both controls

I currently have two partial views on a single page, added with:
#Html.Action("_LogonBox","Account")
#Html.Action("_TrackingBox","Tracking")
each has its own form and model...
but if I enter values into the _logonbox view and submit it it causes the validation to fire on the TrackingBox and because the values in tracking box are empty it highlights the textboxes as errors.
How do I sort this out, in webforms it was simply validationGroups?
EDIT
here is the markup:
LogOn View
#model Models.LogonModel
#using (Html.BeginForm("Login", "account", FormMethod.Post, new { Id = "Login" }))
{
<div class="boxwrapper">
<div class="loginbox">
<a href="#" style="float: right; color: #fff; font-size: 95%%; padding: 5px 10px;">Forgotten
Password?</a>
<h3>
My Account</h3>
<div class="content">
<fieldset class="logincontrols">
<table>
<tr>
<td class="loginlabel">
#Html.LabelFor(m => m.UserName)
</td>
<td class="logintextbox">
#Html.TextBoxFor(m => m.UserName, new { ValidationGroup = "Account" })
</td>
</tr>
<tr>
<td class="loginlabel">
#Html.LabelFor(m => m.Password)
</td>
<td class="logintextbox">
#Html.PasswordFor(m => m.Password, new { ValidationGroup = "Account" })
<input type="image" value="Sign In" src="/Content/Images/buttons/signin.png" style="vertical-align: bottom;" ValidationGroup="Account" />
</td>
</tr>
</table>
</fieldset>
</div>
</div>
</div>}
Tracking View
#model .Models.TrackingModel
#using (Html.BeginForm("Index", "Tracking", new { Id = "Tracking" }))
{
<div class="boxwrapper">
<div class="bluebox">
<fieldset class="logincontrols">
<h3>
Shipment Tracking</h3>
<div class="content">
<p style="text-align: left;">
Please enter your reference number:</p>
#Html.TextBoxFor(m => m.TrackingNumber)
<br />
<p style="text-align: right; margin-top: 10px;">
<input type="image" value="Track Shipment" src="/Content/Images/buttons/trackingbutton.png" /> </p>
</div>
<fieldset>
</div>
</div>
}
Further EDIT
added controllers as requested
public class TrackingController : Controller
{
//
// GET: /Tracking/
public ActionResult Index()
{
return View();
}
[HttpPost]
public ActionResult Index(TrackingModel model)
{
return View(model);
}
[HttpPost]
public PartialViewResult _TrackingBox(TrackingModel model)
{
return PartialView(model);
}
public PartialViewResult _TrackingBox()
{
return PartialView();
}
}
public class AccountController : Controller
{
public ActionResult Login()
{
return View();
}
[HttpPost]
public ActionResult Login(LogOnModel model, string returnUrl)
{
if (ModelState.IsValid)
{
if (Membership.ValidateUser(model.UserName, model.Password))
{
FormsAuthentication.SetAuthCookie(model.UserName, false);
if (Url.IsLocalUrl(returnUrl) && returnUrl.Length > 1 && returnUrl.StartsWith("/")
&& !returnUrl.StartsWith("//") && !returnUrl.StartsWith("/\\"))
{
return Redirect(returnUrl);
}
else
{
return RedirectToAction("Index", "Home");
}
}
else
{
ModelState.AddModelError("", "The user name or password provided is incorrect.");
}
}
// If we got this far, something failed, redisplay form
return View(model);
}
public PartialViewResult _Logonbox()
{
return PartialView();
}
[HttpPost]
public PartialViewResult _Logonbox(LogOnModel model, string returnUrl)
{
if (ModelState.IsValid)
{
//do something here
}
// If we got this far, something failed, redisplay form
return PartialView(model);
}
}
I have managed to fix this, but if someone can comment on WHY this fixes it that would be great.
I changed the #Html.Action("_partialViewName") to #Html.Partial("_partialViewName"), and this caused the same problems.
To fix this I had to include a new model object as below.
#Html.Partial("_LogonBox", new TGEFreight.UI.Web.Models.LogOnModel())
#Html.Partial("_TrackingBox", new TGEFreight.UI.Web.Models.TrackingModel())
As to why this works I dont know, this is probably due to my infancy with MVC, but this is the answer anyway.
Thanks for the help guys.
I'm guessing that both of these field sets are wrapped in the same <form>. You can get around this by having a separate <form> for each partial / child action.
Unlike webforms, MVC lets you take full advantage of HTML, meaning you can have more than 1 form on a page. In webforms, everything needed to be on 1 page so that viewstate could be persisted for the entire page during each postback.
If you are using jquery unobtrusive validation, validations only fire for the form that was submitted.
Edit
Sorry, I re-read your question and you say each of these has its own form. Can you show more of the razor markup? What does your submit button look like? Are there any jquery or javascript behaviors attached to the forms? Is validation firing on the client or the server?
Update
Have you tried using #Html.Partial instead of #Html.Action? Html.Action results in a new HTTP request to the child action method. What view does your login action on your account controller return? It could be that the login POST is calling the tracking action as a POST, and causing validation to fire on it.
I agree with #olivehour you should try using #Html.Partial instead of #Html.Action. Now when you say that if you use Partial that you can't define the action for them to use, what exactly do you mean. You should be able to define an HttpGet and HttpPost action without a problem for each of the partials, so for instance as an example you should be able to easily do the following:
public class AccountController : Controller
{
...
[HttpGet]
public ActionResult Login()
{
return PartialView();
}
[HttpPost]
public ActionResult Login(LoginModel model)
{
... Validate Model & additional logic
// return some redirect action
}
}
I noticed that in your post you are simply returning the PartialView again, was that just pseudo code? If not, at least for the login partial, I would assume that if someone logs in that the post action would redirect them to another page. In either case you would display the partial view by using #Html.Partial or #{Html.RenderPartial(...);} in your calling view. Hope that helps.

Resources