Load ValidationSummary using ajax - ajax

How to load ValidationSummary using ajax? I was trying to use MVC's ready Membership.
Simple question, but I'm stuck.
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
[RecaptchaControlMvc.CaptchaValidator]
public ActionResult Register(RegisterModel model, bool captchaValid, string captchaErrorMessage)
{
if (ModelState.IsValid)
{
// Attempt to register the user
try
{
if (captchaValid)
{
WebSecurity.CreateUserAndAccount(model.UserName, model.Password);
WebSecurity.Login(model.UserName, model.Password);
return RedirectToAction("Index", "Home");
}
ModelState.AddModelError("", captchaErrorMessage);
}
catch (MembershipCreateUserException e)
{
ModelState.AddModelError("", ErrorCodeToString(e.StatusCode));
}
}
// If we got this far, something failed, redisplay form
return View(model);
}
View:
#using (Html.BeginForm()) {
#Html.AntiForgeryToken()
#Html.ValidationSummary()
<fieldset>
<legend>Registration Form</legend>
<ol>
<li>
#Html.LabelFor(m => m.UserName)
#Html.TextBoxFor(m => m.UserName)
#Html.ValidationMessageFor(m => m.UserName)
<input type="hidden" id ="some" value=""/>
</li>etc.
I don't want to redirect each time on for example, if username exists or etc.

To do this you can return a partial view as html. The rendered partial will contain the modelstate errors and therefore will display when returned as html.
Example
Could can create a class called AjaxResult
public class AjaxResult
{
public string Html { get; set; }
public bool Success { get; set; }
}
Then in your success function from the ajax call you can append the html to the appropriate element. e.g.
$.ajax({
url: 'http://bacon/receive',
dataType: "json",
type: "POST",
error: function () {
},
success: function (data) {
if (data.Success) {
$('body').append(data.Html);
}
}
});

Related

How do I correctly use EditorFor to display checkbox selections from an AJAX call, and POST the selections?

My question is two-fold.
I have a View that gets data on the change of a drop down selection.
The data retrieved is a List property of a ViewModel class using an Ajax call.
This data is shown as a selection of check boxes for the user to select any number of them.
If I return a Partial View from an AJAX call, this is easy enough, but from what I have experienced, this doesn't work for POST'ing back to the controller. Nothing is bound correctly.
From what I have read the correct way is to use EditorFor, So firstly, I cannot figure out how to populate the EditorFor from the AJAX call.
Secondly, If I test by sending initial data from my GET, the EditorFor displays the correct checkbox options, but when I POST, the count of the items is 0.
View:
#model EngineeringAssistantMVC.Controllers.FirmwareController.FirmwareViewModel
#using (Html.BeginForm("Upload", "Firmware", FormMethod.Post, new { #id = "uploadFirmwareForm", #class = "form-horizontal" }))
{
<!-- Device -->
<div class="form-group">
<div class="col-lg-1">
#Html.LabelFor(x => x.Device, htmlAttributes: new { #class = "control-label" })
</div>
<div class="col-lg-2">
#Html.DropDownListFor(x => x.Device, ViewBag.Devices as IEnumerable<SelectListItem>, new { #class = "form-control", #id = "Devices" })
</div>
<div class="col-lg-9">
#Html.ValidationMessageFor(x => x.Device, "", new { #class = "text-danger" })
</div>
</div>
#Html.EditorFor(x => x.SelectedModels, "SelectedModels", new { #id = "Models" })
#Html.HiddenFor(x => x.SelectedModels)
}
And the AJAX call:
function GetModels() {
$.ajax({
type: "GET",
url: '#Url.Action("GetModels", "Firmware", null)',
data: { SelectedDevice: $('#Devices').val() },
success: function (dataSet) {
//$('#Models').html(dataSet);
//$('#Models').data(dataSet);
//$('#Models').val(dataSet);
// How do I populate the EditorFor from the dataSet returned?
},
error: function (err) {
console.log("ERROR: " + err.responseText);
},
})
}
SelectedModels EditFor Template:
#model IEnumerable<EngineeringAssistantMVC.ViewModels.ModelViewModel>
#foreach (var item in Model)
{
#Html.CheckBoxFor(x => item.IsSelected)
#Html.Label(item.Description)
#Html.HiddenFor(x => item.ModelId)
#Html.HiddenFor(x => item.IsSelected)
#Html.HiddenFor(x => item.Description)
}
Controller:
[HttpPost]
public ActionResult Upload(HttpPostedFileBase uploadFile, FirmwareViewModel firmwareViewModel)
{
// firmwareViewModel.SelectedModels count is 0 here
}
ModelFirmware Class:
public class ModelFirmware
{
public int ModelFirmwareId { get; set; }
public int FirmwareId { get; set; }
public int ModelId { get; set; }
}
FirmwareViewModel:
public class FirmwareViewModel
{
public int FirmwareViewModelId { get; set; }
[Required]
public string Device { get; set; }
public ICollection<ModelViewModel> SelectedModels { get; set; }
}
I just can't get it to work correctly.
EDIT 1: - Add method that returns the models
[HttpGet]
public ActionResult GetModels(string SelectedDevice)
{
var deviceAbbreviation = _dbContext.Radios.Where(x => x.RadioName == SelectedDevice).Select(x => x.ProjectAbbreviation).FirstOrDefault();
var models = _dbContext.AnatomyModels.Where(x => x.SerialPrefix.StartsWith(deviceAbbreviation.Trim()) && x.ParentId == 0).ToList();
List<ModelViewModel> mvms = models.Select(x => new ModelViewModel()
{
ModelId = x.AnatomyModelId,
Description = x.SerialPrefix,
IsSelected = false,
}).ToList();
return Json(mvms);
}
There are numerous issues with your code.
First your not using the EditorTemplate correctly. Change its name to ModelViewModel.cshtml to match the name of the class, and locate it in the /Views/Shared/EditorTemplates (or /Views/YourControllerName/EditorTemplates) folder. The template is then based on a single object (note also the LabelFor() required to create a label associated with the checkbox, and you need to delete the hidden input for IsSelected)
#model ModelViewModel
#Html.CheckBoxFor(m => m.IsSelected)
#Html.LabelFor(m => m.IsSelected, Model.Description)
#Html.HiddenFor(m => m.ModelId)
#Html.HiddenFor(m => m.Description)
Refer also Post an HTML Table to ADO.NET DataTable to understand why your foreach loop would never have created the correct name attributes for model binding.
Then in the main view use
<div id="container">
#Html.EditorFor(m => m.SelectedModels)
</div>
and remove the hidden input for SelectedModels (but before you do, inspect the html for that element to understand why its value would never bind). The EditorFor() method will correctly generate the html for each item in your collection.
Next, change your GetModels() method to return a partial view based on FirmwareViewModel since that is what you will be posting back. Note that you could return a JsonResult, but that would mean generating a whole lot of html in the ajax call back that would not be strongly typed.
[HttpGet]
public PartialViewResult GetModels(string SelectedDevice)
{
var deviceAbbreviation = _dbContext.Radios.Where(x => x.RadioName == SelectedDevice).Select(x => x.ProjectAbbreviation).FirstOrDefault();
var models = _dbContext.AnatomyModels.Where(x => x.SerialPrefix.StartsWith(deviceAbbreviation.Trim()) && x.ParentId == 0).ToList();
List<ModelViewModel> mvms = models.Select(x => new ModelViewModel()
{
ModelId = x.AnatomyModelId,
Description = x.SerialPrefix,
IsSelected = false, // not really necessary since its the default
}).ToList();
FirmwareViewModel model = new FirmwareViewModel
{
SelectedModels = mvms
};
return PartialView(model);
}
and your GetModels.cshtml view will be
#model FirmwareViewModel
#Html.EditorFor(m => m.SelectedModels)
Then, modify your ajax call to add the partial view in the success callback
$.ajax({
type: "GET",
url: '#Url.Action("GetModels", "Firmware")', // do not need to add 3rd parameter
data: { SelectedDevice: $('#Devices').val() },
success: function (response) {
$('#container').html(response);
},
error: function (err) {
console.log("ERROR: " + err.responseText);
},
})
The .html() function will replace any elements already existing in the <div id="container"> element
Finally, since your using a view model, make use of it and do not use ViewBag. Your view model should contain a IEnumerable<SelectListItem> Devices property which you populate in the GET method (and use #Html.DropDownListFor(x => x.Device, Model.Devices, new { #class = "form-control" }) in the view (note also that the method generates id="Device"). It should also contain a HttpPostedFileBase property to avoid the additional parameter in the POST method, and allow you to add validation attributes.

displaying validation message using Ajax.beginform in a partial

I am new in .NET mvc 5 environment.
I have a view:
Index.cshtml
#model Accounts.WebHost.Models.SendUsernameReminderInputModel
#using (Ajax.BeginForm("Index", "SendUsernameReminder", null, new AjaxOptions { InsertionMode = InsertionMode.Replace, UpdateTargetId = "validationList", LoadingElementId = "loader", OnSuccess = "onSuccess", OnFailure = "onFailure" }, new { #id = "validationForm", #class = "form-inline" }))
{
#Html.AntiForgeryToken()
<div class="form-group">
#Html.EditorFor(model => model.Email, new { htmlAttributes = new { #class = "form-control" } })
</div>
<div class="form-group hidden">
#Html.TextBoxFor(model => model.Tenant, new { #class = "form-control", #Value = "default" })
</div>
<button type="submit" class="btn btn-default">Submit</button>
}
<hr />
<div id="loader" class="alert" style="display:none">
<img src="~/Content/img/ajax-loader.gif" />
</div>
#Html.Partial("_UsernameValidation")
And a Partial view:
_UsernameValidation.cshtml
#model Accounts.WebHost.Models.SendUsernameReminderInputModel
<div id="validationList">
<table>
<tr>
<td>#Html.ValidationMessageFor(model => model.Email)</td>
<td>#Html.ValidationMessageFor(model => model.Tenant)</td>
</tr>
</table>
</div>
this is my Controller:
SendUsernameReminderController.cs
using Accounts.Entities.Models;
using Accounts.WebHost.Models;
using BrockAllen.MembershipReboot;
using System.ComponentModel.DataAnnotations;
using System.Web.Mvc;
namespace Accounts.WebHost.Controllers
{
public class SendUsernameReminderController : Controller
{
public readonly UserAccountService<MemberAccount> userAccountService;
public SendUsernameReminderController(UserAccountService<MemberAccount> userAccountService)
{
this.userAccountService = userAccountService;
}
[HttpGet]
public ActionResult Index(string signin)
{
ViewBag.Signin = signin;
return View();
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Index(SendUsernameReminderInputModel model)
{
if (ModelState.IsValid)
{
try
{
this.userAccountService.SendUsernameReminder(model.Tenant, model.Email);
return RedirectToAction("Success", model);
}
catch (ValidationException ex)
{
if (ex.ValidationResult.ToString() == "The email address for this account is not yet verified.")
{
try
{
userAccountService.ResetPassword(model.Tenant, model.Email);
return RedirectToAction("Unverified");
}
catch (ValidationException resetex)
{
ModelState.AddModelError("", resetex.Message);
ViewBag.Message = "resetex.Message";
return View();
}
}
ModelState.AddModelError("", ex.Message);
return View();
}
}
return View();
}
public ActionResult Success(SendUsernameReminderInputModel model)
{
ViewBag.Subject = "Username Emailed";
ViewBag.Message = "Your username was emailed at " + model.Email + ". If you don't receive this email within 24 hours, please check your junk mail folder or visit our Help pages to contact Customer Service for further assistance.";
return View();
}
public ActionResult Unverified()
{
ViewBag.Subject = "Email has not been verified";
ViewBag.Message = "You will receive an email from us to confirm your email or cancel your registration. If you don't receive this email within 24 hours, please check your junk mail folder or visit our Help pages to contact Customer Service for further assistance.";
return View();
}
}
}
And this is my Model:
SendUsernameReminderInputModel.cs
using System.ComponentModel.DataAnnotations;
namespace Accounts.WebHost.Models
{
public class SendUsernameReminderInputModel
{
[Required]
[EmailAddress]
public string Email
{
get;
set;
}
[Required]
public string Tenant
{
get;
set;
}
}
}
my aim is that when a user clicks the form submit button only the validation message will display below the form. unfortunately, it outputs the whole Index.cshtml in the partial and the validation message at the bottom.
If this is a bad approach please give me directions.
Thank you in advance.
U need to pass Model like View(model) ; so that the View gets the model with those errors.
And u could also use #Html.ValidationSummary to display all the errors on a fly

Partial View and ajax

I want to update Partial View via ajax, but it does not work. Look at this model class:
public class LogOnModel
{
[Required]
[Display(Name = "User name")]
public string UserName { get; set; }
[Required]
[DataType(DataType.Password)]
[Display(Name = "Password")]
public string Password { get; set; }
[Display(Name = "Remember me?")]
public bool RememberMe { get; set; }
public bool IsLoggedIn { get; set; }
public string ReturnUrl { get; set; }
}
the following view:
#model ITW2012Mobile.ViewModels.LogOnModel
<div id='LogOn' style="background-color: White;">
#using (Ajax.BeginForm("LogOnAjax", "Home", new AjaxOptions { UpdateTargetId = "LogOn", OnSuccess = "logInComplete" }))
{
ITW2012Mobile.ViewModels.LogOnModel m = Model;
#Html.EditorFor(model => model.IsLoggedIn)
#Html.EditorFor(model => model.ReturnUrl)
<div>
#Html.ValidationSummary()
</div>
<div>
#Html.LabelFor(model => model.UserName)
#Html.EditorFor(model => model.UserName)
</div>
<div>
#Html.LabelFor(model => model.Password)
#Html.EditorFor(model => model.Password)
</div>
<div>
<input type="submit" value="Login" />
</div>
}
</div>
and the following controller class:
public ActionResult LogOnAjax(LogOnModel model)
{
if (!User.Identity.IsAuthenticated)
{
if (ModelState.IsValid)
{
if (Membership.ValidateUser(model.UserName, model.Password))
{
FormsAuthentication.SetAuthCookie(model.UserName, model.RememberMe);
model.IsLoggedIn = true;
model.ReturnUrl = Url.Action("Index", "Home");
}
else
{
ModelState.AddModelError("", "The user name or password provided is incorrect.");
}
}
// If we got this far, something failed, redisplay form
return PartialView("PartialViewAjaxLogOn", model);
}
else
{
return PartialView("PartialViewLogOut");
}
}
even when username/password are correct and IsLoggedIn = true and ReturnUrl!=empty view shows empty fields for these variables (but debugger shows values inside). Why and how to make it correctly?
Try clearing the values you are modifying in your action from modelstate or if you use them in html helpers the old values will be used:
ModelState.Remove("IsLoggedIn");
model.IsLoggedIn = true;
ModelState.Remove("ReturnUrl");
model.ReturnUrl = Url.Action("Index", "Home");
Also bear in mind that upon successful authentication and cookie emission you should not display a view (partial in your case). You should redirect so that the authentication cookie is sent by the client on the subsequent request. You should redirect to the return url. But since you are doing this using AJAX you should probably send some indication to the client that the authentication was successful so that you can redirect on the client.

MVC3: Pass User Entered ID as a Parameter to a Controller Method

I have a view where a user will enter an ID that will be passed to a controller method which will then populate a view containing the related patient record.
I am new to MVC and have been unable to get the ID from the textbox and pass it to the controller method. I appreciate any help.
In my view:
#model Final.Models.Patient
#Html.TextBoxFor(model => model.Patient_ID)
#Html.ActionLink("Patient", "Details", new { id=???? })
In my controller:
public ViewResult Details(decimal id)
{
Patient patient = db.Patients.Single(p => p.Patient_ID == id);
return View(patient);
}
Thanks.
I was able to make it work with the following:
#using (#Html.BeginForm("Details", "Patient")) {
#Html.TextBoxFor(model => model.Patient_ID)
<input type="submit", value="Submit"/>
public ActionResult Details(Patient _patient)
{
decimal id = _patient.Patient_ID;
Patient patient = db.Patients.Single(p => p.Patient_ID == id);
return View(patient);
}
Is jQuery method acceptable for you? You can assign an id to the textbox, and then get the value (id entered by user) and then submit to your controller using $.ajax
#Html.TextBoxFor(model => model.PatientID, new { id = "patient"})
$.ajax({
url: '/Home/Details',
type: 'POST',
data: { id: $('#patient').val()},
async: false,
success: function (result) {
alert('success!');
}
});
Hope this help you :)
receive the model in ActionResult instead
#model Final.Models.Patient
#usign (#BeginForm("Details","Controller"){
#Html.TextBoxFor(model => model.Patient_ID)
#Html.ActionLink("Patient", "Details", new { id=???? })
<input type="submit", value="Submit"/>
}
in your controller
[HttpPost]
public ActionResult Details(Patient _patient)
{
decimal id = _patient.Patient_ID
Patient patient = db.Patients.Single(p => p.Patient_ID == id);
return View(patient);
}

How can I safely access key groups in the FormCollection?

I have a table where each tr is grouped by having their input elements name set to a value that is unique for each row.
For example,
<td>
<input data-field="opps" type="text" value="somevalue" name="#item.Code" />
</td>
<td>
<input data-field="asc" type="text" value="somevalue2" name="#item.Code" />
</td>
On POST
[HttpPost]
public ActionResult Update(FormCollection collection)
{
try
{
//doin work on collection...order assumed static
return RedirectToAction("Index");
}
catch
{
return View();
}
}
my System.Web.MVC.FormCollection is grouped in the same order I define the <td>. I don't like to assume order, but without access to my data-field, I'm not sure what else I can do (maybe I could append the data-field value as a prefix to the name and put it all together with a custom collection and Regex..but that seems nutty).
Is there a way to access the data-field? This way I'm not fragile to re-ordering or adding new columns in the View.
Let's say you have a class (model) defined like this:
public class MyModel
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int Age { get; set; }
}
In you controller, you might have an action called Create, like so:
[HttpGet]
public ViewResult Create()
{
MyModel sampleModel = new MyModel();
return View(sampleModel);
}
[HttpPost]
public ActionResult Create(MyModel sampleModel)
{
if (ModelState.IsValid)
{
TempData["Error"] = "There were errors. Please correct the problem and submit again";
return View(sampleModel);
}
// At this point everything is fine and you can access data in your sampleModel
if (sampleModel.Age >= 16)
{
return RedirectToAction("LegalAccess");
}
else
{
TempData["Error"] = "You must be 16 or over to access this site";
return RedirectToAction("AgeRestriction");
}
}
When you create a strongly typed view that uses MyModel as model you might define it something like this:
#model MyModel
#{
Layout = "~/Shared/_Layout.cshtml";
}
#using (Html.BeginForm())
{
#Html.LabelFor(m => m.FirstName)
#Html.TextBoxFor(m => m.FirstName)
<br />
#Html.LabelFor(m => m.LastName)
#Html.TextBoxFor(m => m.LastName)
<br />
#Html.LabelFor(m => m.LastName)
#Html.TextBoxFor(m => m.LastName)
<input type="submit" value="Submit" />
}
When you submit this form, the model binder will in the background copy data from this form using Request.Form into an object of type MyModel which it creates in the background. This new object is passed to an action that handles HTTP POST method. With this you get strongly typed object and you don't have to worry about the order of items in FormCollection.
I hope I helped answer your question.
BTW. I wrote this without Visual Studio, so I hope there are not errors. :-)

Resources