MVC 4 Ajax.BeginForm and ModelState.AddModelError - ajax

I'm trying to get errors to show up after an ajax submit has returned an error. I'm not sure what I'm missing, but I can't get it to work. This question is basically the same thing - ModelState.AddModelError is not being displayed inside my view but I'm still not having any luck. My experience with Ajax and MVC (any version) is still a bit limited. Here is a very simple example, most of which I took from the previous link.
View: test.cshtml
#model TestProject.VisitLabResult
#Scripts.Render("~/Scripts/jquery.unobtrusive-ajax.min.js")
#Scripts.Render("~/Scripts/ckeditor/ckeditor.js")
#{
AjaxOptions ajaxOpts = new AjaxOptions
{
Url = Url.Action("test"),
HttpMethod = "Post",
LoadingElementId = "loading",
LoadingElementDuration = 500,
OnSuccess = "processData"
};
}
#Html.ValidationMessage("CustomError")
<div id="loading" class="load" style="display:none">
<p>Saving...</p>
</div>
<table>
#for (int item = 0; item < 10; item++)
{
<tr id = #item>
#using (Ajax.BeginForm(ajaxOpts))
{
#Html.ValidationSummary(true)
#Html.AntiForgeryToken()
<td>
<input type="submit" value="Create" />
</td>
<td id = #(item.ToString() + "td")>
</td>
}
</tr>
}
</table>
Controller: HomeController.cs
public ActionResult test()
{
return View();
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult test(VisitLabResult vlr, int visitid = 28)
{
try
{
if (ModelState.IsValid)
{
if (Request.IsAjaxRequest())
{
throw new Exception("error");
}
else
return View(vlr);
}
else
return View(vlr);
}
catch (Exception ex)
{
ModelState.AddModelError("CustomError", "The Same test Type might have been already created, go back to the Visit page to see the available Lab Tests");
return View(vlr);
}
}
Model
public class VisitLabResult
{
public int visitid { get; set; }
}
If it is an Ajax request I throw an error and it's caught and an error is added to ModelState. That error never shows up on the page though. Am I approaching this the right way at all? Or do I need to take a different route? I appreciate any help.

Just to clarify the solution for other people hitting this question. The ajax helper fires OnSuccess vs OnFailure based on the returned HTTP Code per the AjaxOptions docs:
OnSuccess: This function is called if the response status is in the 200 range.
OnFailure: This function is called if the response status is not in the 200 range.
In other words, you have to manually specify that there was a failure when returning your ActionResult by changing the Response.StatusCode and then returning whatever values you're expecting in your OnFailure js method. You can drive that based on any business logic you want (i.e. catch Exception ex) or !ModelState.IsValid ...)
[HttpPost]
public ActionResult Search(Person model)
{
if (ModelState.IsValid) {
// if valid, return a HTML view inserted by AJAX helper
var results = PersonRepository.Get(model)
return PartialView("Resulsts", vm);
} else {
// if invalid, return a JSON object and handle with OnFailure method
Response.StatusCode = (int)HttpStatusCode.BadRequest;
return Json(new { errors = ModelState.Values.SelectMany(v => v.Errors) });
}
}
Further Reading:
MVC 4 Ajax.BeginForm and ModelState.AddModelError
ASP.NET MVC “Ajax.BeginForm” executes OnSuccess even though model is not valid

Related

Redirecting to ajax edit form on model binding error in asp.net mvc

I have a simple model called JobStatus, and I am enabling edit functionality within the Index view with an Ajax Actionlink.
My problem is that if there is a model error when editing an item, I don't know how to bring the item back to the model.
My Index action in my controller:
public ActionResult Index()
{
return View(db.JobStatuses.OrderBy(x => x.JobStatusID).ToList());
}
Here is my Index view:
#model IEnumerable<Project.Models.JobStatus>
#foreach (var item in Model)
{
<div id='#string.Format("div_{0}", item.JobStatusID)'>
#item.JobStatusID
#item.Name
#item.Description
#Ajax.ActionLink("Edit", "Edit", new { id = item.JobStatusID }, new AjaxOptions() { HttpMethod = "GET", UpdateTargetId = string.Format("div_{0}", item.JobStatusID) })
</div>
}
And here is my edit GET request:
public ActionResult Edit(string id = null)
{
JobStatus jobstatus = db.JobStatuses.Find(id);
if (jobstatus == null)
{
return HttpNotFound();
}
return PartialView(jobstatus);
}
My edit.cshtml:
#model Project.Models.JobStatus
#using Microsoft.Ajax;
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
#Html.HiddenFor(model => model.JobStatusID)
#Html.TextBoxFor(model => model.Name, null, new { #class = "form-control" })
#Html.TextBoxFor(model => model.Description, null, new { #class = "form-control" })
<input type="submit" value="Save" />
}
And finally my edit POST method:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(JobStatus jobstatus, string action)
{
if (ModelState.IsValid)
{
db.Entry(jobstatus).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
// Not sure what to do here, how to I repopulate my index form?
return RedirectToAction("Index");
}
At the moment, on a modelstate failure the user is just redirected to the index page - what I'd like is to simply redisplay the index form, with the appropriate edit form enabled and populated, and any validation errors shown.
I've tried redirecting to my Edit action, but this just shows my form on a new page, without my index form (because currently my edit action is an ajax action)
Of course, please let me know if there is a better way to achieve this!
You can't do the RedirectToAction with Ajax Post.
Check out asp.net mvc ajax post - redirecttoaction not working
But you can load in the following way
[HttpPost]
[ValidateAntiForgeryToken]
public JsonResult Edit(JobStatus jobstatus, string action)
{
if (ModelState.IsValid)
{
db.Entry(jobstatus).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return Json(new{id=jobStatus, success=true})
}
Once you get the JSON result from server you can load the same using the id returned
Like
$.post('/jobs/edit',{data:$(form).serialize()},function(data){
if(data.success){
$.get('/jobs/index/'+data.id,{},function(data){
$('#container').html(data);
});
}
});

MVC Knockout validation display and

I am using knockout for the first time and I am struggling to get my head around a problem.
I have a page with multiple sections and want to be able to edit a section and submit to the controller, then display the saved details.
Each section is a partial view which contains the display information and the form. They are shown and hidden as required. I have the code working for submitting, but the problem is when the ModelState is not valid. I need to return to the form with the validation message displayed
How can I display the form again when the server validation fails? When the validation fails it currently goes back to the display section.
Also I have noticed the validation message does not display.
I am sure this must be a common problem with a simple fix. I know there are knockout validation tools, but will need to do more complex business logic validation later on and need to get the technique working.
ViewModel:
[Required]
public DateTime? InterviewDate { get; set; }
View:
<div data-bind="if: showAdminInterviewDisplay" id="Display">
<div>
<button data-bind="click: showInterviewForm" id="EditButton">Edit</button>
</div>
<div>
#Html.Label("Inteview Date") :
<label data-bind="text: interviewDate"></label>
</div>
</div>
<div data-bind="if: showAdminInterviewForm" id="Form">
<div>
#Html.Label("Interview Date")
<input data-bind="value: interviewDate" id="interviewDatePicker" />
#Html.ValidationMessageFor(m => m.InterviewDate)
</div>
<div>
<button data-bind="click: saveInterviewDate">Submit</button>
</div>
Knockout ViewModel:
function InterviewViewModel() {
//Data
var self = this;
var jsonDate = #Html.Raw(Json.Encode(#Model.InterviewDate));
var date = new Date(parseInt(jsonDate.substr(6)));
self.interviewDate = ko.observable(dateFormat(date, "dd/mm/yyyy"));
self.showAdminInterviewDisplay = ko.observable(true);
self.showAdminInterviewForm = ko.observable();
self.showInterviewForm = function () {
self.showAdminInterviewDisplay(false);
self.showAdminInterviewForm(true);
$("#interviewDatePicker").datepicker({dateFormat: 'dd/mm/yy'});
};
//Operations
self.saveInterviewDate = function() {
$.ajax("#Url.Action("SaveInterview")", {
data: ko.toJSON(self),
type: "post",
contentType: "application/json",
success: function(data) {
self.showAdminInterviewDisplay(true);
self.showAdminInterviewForm(false);
}
});
};
};
ko.applyBindings(new InterviewViewModel());
Controller:
public ActionResult SaveInterview(KnockoutViewModel model)
{
if (ModelState.IsValid)
{
return Json(model);
}
return PartialView("_AdminInterview", model);
}
Instead of returning a Partial View from your Action Method, return a serialised error model to the success function in the AJAX call. The error model will contain all the errors in the ModelState.
See this post on how to get and consume the errors from Model State:
ASP.NET MVC How to convert ModelState errors to json (JK's answer)
So you would have something like:
Error Model:
public class JsonErrorModel
{
public JsonErrorModel()
{
HasFailed = true;
}
public bool HasFailed { get; set; }
public IEnumerable ErrorList { get; set; }
}
Controller:
if(ModelState.IsValid)
{
//Do whatever here
return Json(new { model });
}
return Json(new JsonErrorModel {ErrorList = ModelState.Errors()});
Success function of AJAX call:
success: function (result) {
if(result.HasFailed) {
self.showAdminInterviewDisplay(false);
self.showAdminInterviewForm(true);
DisplayErrors(result.ErrorList);
}
else {
self.showAdminInterviewDisplay(true);
self.showAdminInterviewForm(false);
}
}
So now, if the server side validation failed, the view will show the form and the validation errors.

Result not showing with unobtrusive ajax in mvc 3

I'm trying to implement a little ajax using unobtrusive ajax of mvc 3. Here is the code snippet:
Controller:
[HttpGet]
public ActionResult ViewEmployee()
{
return View();
}
[HttpPost]
public ActionResult ViewEmployee(EMPLOYEE model)
{
var obj = new EmployeeService();
var result=obj.FindEmployee(model);
return View("ViewEmployee", result);
}
View:
#{AjaxOptions AjaxOpts = new AjaxOptions { UpdateTargetId = "ajax", HttpMethod = "Post" };}
#using (Ajax.BeginForm("ViewEmployee", "Home", AjaxOpts))
{
#Html.LabelFor(x => x.EmployeeID)
#Html.TextBoxFor(x => x.EmployeeID)
<input type="submit" name="Find Name" value="Find Name" />
}
<div id="ajax">
#{
if (Model != null)
{
foreach (var x in Model.EmployeeName)
{
#x
}
}
else
{
#Html.Label("No Employee is selected!")
}
}
</div>
I debugged the code, its sending the employee id to the ViewEmployee method, finding the name but not being able to display the name back into the view.
I've activated the unobtrusive ajax property in web.config & imported the scripts into the view.
Whats going wrong with this? Please help.
This is simple but effective article, ask me if you have any more question! I resolved the problem! By the way, whats wrong with stackoverflow, i'm not getting no response literally!
http://www.c-sharpcorner.com/UploadFile/specialhost/using-unobtrusive-ajax-forms-in-Asp-Net-mvc3/

json success and fail response in MVC

I'm practicing Ajax! I have a simple contact form and this is my actions :
public ActionResult Contact()
{
return View("Contact");
}
[HttpPost]
public ActionResult Contact(ContactViewModel contactViewModel)
{
if (ModelState.IsValid)
{
var contact = contactViewModel.ConvertToContactModel();
_contactRepository.Add(contact);
_contactRepository.Save();
return Json(new { msg = "Your contact Sent, I'll response soon." });
}
return Json("Sorry! Somthing went wrong, try again or contact again");
}
and this is my View :
#model Blog.Web.UI.ViewModels.ContactViewModel
#{
ViewBag.Title = "Contact";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<div id="Contact">
#using (Ajax.BeginForm("Contact", "Home", new AjaxOptions() { OnSuccess = "Success" }))
{
<div>
#Html.LabelFor(c => c.Name)
#Html.TextBoxFor(c => c.Name)
</div>
<div>
#Html.LabelFor(c => c.Email)
#Html.TextBoxFor(c => c.Email)
#Html.ValidationMessageFor(c => c.Email)
</div>
<div>
#Html.LabelFor(c => c.Subject)
#Html.TextBoxFor(c => c.Subject)
</div>
<div>
#Html.LabelFor(c => c.Body)
#Html.TextAreaFor(c => c.Body)
#Html.ValidationMessageFor(c => c.Body)
</div>
<input type="submit" value="Send" />
}
</div>
<script type="text/javascript">
function Success(context) {
if (context[0]) {
$("#Contact").empty().html(context[1]);
}
}
</script>
Now I wanna to show the user success or failing of contact made , what's the problem of my code that doesn't work??
it is so interesting that my validation doesn't work in this case!
please help me about this , thanks
if you want an error response so be sent, then you should set the ResponseCode of the Response object to a suitable http error code, such as 400 for bad request.
You will then need to provide an error handler in the ajax.beginform to display the content you want. If you don't, it will return a responsecode of 200 and that is treat that everything is hunky dory, so your error handler won't be triggered
Contrary to Slicksim, we usually have a JSON return class defined in our application models that contains a boolean variable Success. We can then use this to have the Javascript determine if the request was successful or not.
public class JsonResponseModel(){
public bool Success {get;set;}
public string Message {get;set;}
}
public ActionResult Contact()
{
return View("Contact");
}
[HttpPost]
public ActionResult Contact(ContactViewModel contactViewModel)
{
if (ModelState.IsValid)
{
var contact = contactViewModel.ConvertToContactModel();
_contactRepository.Add(contact);
_contactRepository.Save();
return Json(new JsonResponseModel{ Success = true, Messsage = "Your contact Sent, I'll response soon." });
}
return Json(new JsonResponseModel{Success = false, Message = "Sorry! Somthing went wrong, try again or contact again"});
}
<script type="text/javascript">
function Success(response) {
if (response.Success) {
$("#Contact").empty().html(response.Message);
}
else{
alert(response.Message);
}
}
</script>
I suppose this is the route that we went instead of modifying the server headers because you may want to do something different if the validation failed on a AJAX call rather than an actual server error (HTTP Status COde 500/404/etc)

Return PartialView to specific div from Action

I am playing about with jQuery UI and PartialViews and have run into a problem I can't quiet get my head around.
This bit works as I expect:
<div>
#Ajax.ActionLink("Test Me!", "dialogtest", new { id = Model.Id }, new AjaxOptions { InsertionMode = InsertionMode.Replace, UpdateTargetId = "dialogtest-view" })</td>
</div>
<div id="dialogtest-view">
</div>
this GETs to this action method
[HttpGet]
public PartialViewResult DialogTest(int id)
{
//pretend to get something from DB here
var vm = new DialogUITestVM();
return PartialView("uidialog_partial", vm);
}
And returns me a PartialView which displays in the targeted div. jQuery + jQueryUI is used to pop this div up as a modal dialog. Part 1 of test done!
OK so now let's say the PartialView returned is just a basic form with a textbox, something along the lines of:
#using (Html.BeginForm("DialogTest", "pages", FormMethod.Post))
{
#Html.HiddenFor(x => x.Id)
#Html.TextBoxFor(x => x.Name)
<button type="submit">Test Me!</button>
}
This is POSTd back to the controller fine -
[HttpPost]
public ActionResult DialogTest(DialogUITestVM vm)
{
//arbitrary validation so I can test pass and fail)
if (vm.Name.Equals("Rob"))
{
//error!
vm.ErrorMessage = "There was an error you numpty. Sort it out.";
return PartialView(vm);
}
//hooray it passed - go back to index
return RedirectToAction("index");
}
However - if I make the action fail the validation, rather than targeting the PartialView to the div again, it redraws the whole page (which obviously loses the jQuery UI dialog).
What I want is: if validation fails, just update the div that contained the form.
Where am I going wrong?
You could use an Ajax form in your partial instead of a normal form and use a OnSuccess callback in your AjaxOptions:
#using (Ajax.BeginForm("DialogTest", "pages", new AjaxOptions { UpdateTargetId = "dialogtest-view", OnSuccess = "success" }))
{
#Html.HiddenFor(x => x.Id)
#Html.TextBoxFor(x => x.Name)
<button type="submit">Test Me!</button>
}
and then modify your controller action respectively:
[HttpPost]
public ActionResult DialogTest(DialogUITestVM vm)
{
//arbitrary validation so I can test pass and fail)
if (vm.Name.Equals("Rob"))
{
//error!
vm.ErrorMessage = "There was an error you numpty. Sort it out.";
return PartialView(vm);
}
//hooray it passed - go back to index
return Json(new { redirectUrl = Url.Action("Index") });
}
and of course define the corresponding success callback in your javascript files:
function success(result) {
if (result.redirectUrl) {
window.location.href = result.redirectUrl;
}
}

Resources