Goal:
If you retrieve a error based on the input, it should be displayed in ValidationSummary in relation to ajax without the webpage being refreshed.
Problem:
I have tried to make it but it doesn't work so well.
What part am I missing?
Thank you!
Info:
*I have found some webpages but they do not fit exactly to my purpose.
*I'm using ASP.net mvc
#model WebApplication1.Controllers.PersonnelModel
#{
ViewBag.Title = "Ajax";
}
<h2>Ajax</h2>
<h2>AjaxViewModel</h2>
#using (Html.BeginForm("HtmlViewModel", "Home", null))
{
#Html.ValidationSummary(true)
<fieldset>
<legend>PersonnelModel</legend>
<div class="editor-label">
#Html.LabelFor(model => model.UserName)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.UserName)
#Html.ValidationMessageFor(model => model.UserName)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.MailAdress)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.MailAdress)
#Html.ValidationMessageFor(model => model.MailAdress)
</div>
</fieldset>
<p>
<input type="submit" value="Html Form Action" />
</p>
}
<br/>
<br />
<h2>AjaxViewModel</h2>
#using (Ajax.BeginForm("AjaxViewModel", "Home", new AjaxOptions { UpdateTargetId = "result" }))
{
#Html.ValidationSummary(true)
<fieldset>
<legend>PersonnelModel</legend>
<div id="result"></div>
<div class="editor-label">
#Html.LabelFor(model => model.UserName)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.UserName)
#Html.ValidationMessageFor(model => model.UserName)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.MailAdress)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.MailAdress)
#Html.ValidationMessageFor(model => model.MailAdress)
</div>
</fieldset>
<p>
<input type="submit" value="Ajax Form Action" />
</p>
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/jquery-ajax-unobtrusive#3.2.4/jquery.unobtrusive-ajax.min.js"></script>
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;
using System.Web;
using System.Web.Mvc;
using System.Web.Security;
namespace WebApplication1.Controllers
{
public class HomeController : Controller
{
public ActionResult Index()
{
return View();
}
[HttpPost]
public ActionResult HtmlViewModel(PersonnelModel Pmodel)
{
return Content("Hi " + Pmodel.UserName + ", Thanks for the details, a mail will be sent to " + Pmodel.MailAdress + " with all the login details.", "text/html");
}
[HttpPost]
public ActionResult AjaxViewModel(PersonnelModel Pmodel)
{
/*
ModelState.AddModelError("", "login is fail");
return View("Index", Pmodel);
*/
return Content("Hi " + Pmodel.UserName + ", Thanks for the details, a mail will be sent to " + Pmodel.MailAdress + " with all the login details.", "text/html");
}
}
public class PersonnelModel
{
[Required(ErrorMessage = "UserName Required.")]
public string UserName { get; set; }
[Required(ErrorMessage = "Email id Required.")]
public string MailAdress { get; set; }
}
}
EDIT - 03/11/2017 : There is an easy way to do this
Create a partial view for the form, Let's call it Form.cshtml and move the markup needed for the form to that. For your ajax form, set the data-ajax-mode attribute value to replace and data-ajax-update value to the id of the same form.
If you are using Ajax.BeginForm helper method, this is how you will do
#model PersonnelModel
#using (Ajax.BeginForm("Create", "Home",
new AjaxOptions { UpdateTargetId = "myform",
InsertionMode = InsertionMode.Replace },new { id="myform"}))
{
#Html.ValidationSummary("", true)
#Html.TextBoxFor(f => f.UserName)
#Html.ValidationMessageFor(model => model.UserName)
#Html.TextBoxFor(f => f.MailAdress)
#Html.ValidationMessageFor(model => model.MailAdress)
<input type="Submit" id="submit" value="Submit" class="btn btn-default" />
}
Now in your main view, simple call this partial view
#model PersonnelModel
<h2>My form below</h2>
#Html.Partial("Form",Model)
Now in the action method, when model state validation fails, return the partial view.
public ActionResult Create(PersonnelModel model)
{
if (ModelState.IsValid)
{
// to do : Save
}
if (Request.IsAjaxRequest())
{
return PartialView("Form",model);
}
return View(model);
}
Now when you submit the form and model state validation fails, the action method code will return the partial view result with the validation error messages (generated by the validation helpers) and the jquery.unobtrusive-ajax.js library code will replace (because we specified that with data-ajax-mode="replace") the content of the result of the jquery selector #data-ajax-update (the form tag and it's inner contents) with the response coming back from the server.
This should do it. Less client side code, compared to my old approach (below)
The Html.ValidationSummary method will be executed when the razor view gets executed. If you are doing a normal form post (non ajax), your action method usually returns to the same view when Model validation fails (assuming you write code like that) and the razor view code gets executed and the ValidationSummary method will read the validation errors from the model state dictionary and render the error messages.
When you use Ajax.BeginForm helper method the helper will generate some extra data attributes on the form and as long as you have included the jquery.unobtrusive-ajax.min.js script file, the form submit will be hijacked and it will do an ajax form submit instead.
When you do the ajax form submit, if you want to render model validation messages, you need to explicitly read the model validation errors and return that as a JSON response which your client side code can read and display in the UI.
[HttpPost]
public ActionResult Index(PersonnelModel model)
{
if (ModelState.IsValid)
{
return Json(new {status = "success", message= "Thanks for the details"});
}
else
{
var errors = new List<string>();
foreach (var modelStateVal in ViewData.ModelState.Values)
{
errors.AddRange(modelStateVal.Errors.Select(error => error.ErrorMessage));
}
return Json(new {status = "validationerror", errors = errors});
}
}
Now in your view, make sure you have a success handler for your ajax begin form to handle the response json
#using (Ajax.BeginForm("Index", "Home", new AjaxOptions { OnSuccess = "OnSuccess", }))
{
#Html.ValidationSummary("", true)
#Html.TextBoxFor(model => model.MailAdress)
<!--Your other form input elements-->
<input type="submit" value="Html Form Action" />
}
Note that i used #Html.ValidationSummary method with 2 overloads and passing an empty string as the first param. This will always render a ul element within the div which has class validation-summary-valid.
Now create your OnSuccess function and check the response and see whether response has a status property and the value of it is validationerror. If yes, loop through the errors collection and add a new li element with the error.
function OnSuccess(response) {
var $summaryUl = $(".validation-summary-valid").find("ul");
$summaryUl.empty();
if (response.status === 'validationerror') {
$.each(response.errors,
function(a, b) {
$summaryUl.append($("<li>").text(b));
});
} else {
alert(response.message);
}
}
Related
Validate Input is not working.The form gets submitted and the success view is displayed even when the form is posted with html tags
<b> hello </b>
Why is the cross site scripting prevention not working? It should be enabled by default right?
Razor View
<h4 style="color:purple">
<b>ID:</b> #ViewBag.ID <br />
<b>Name:</b> #ViewBag.Name <br />
</h4>
<hr />
#using (Html.BeginForm("register", "Adder", FormMethod.Post))
{
<div class="form-group">
#Html.TextArea("comments");
<input type="submit" />
</div>
}
Controller Method
[HttpPost]
public string register(string val)
{
// quickdbEntities1 ent = new quickdbEntities1();
// Player p1 = ent.Players.FirstOrDefault(p => p.Name == "name");
//// ent.Players.Add(player);
//// int res = ent.SaveChanges();
// ViewBag.id = player.PlayerId;
// ViewBag.Name = p1.Name;
return ("success");
}
Update:
I have added DataAnnotation [Required] and now uses a form like this.Still the form submits
#model Vidly.Domain.Player
#{
Layout = "~/Views/Shared/_Layout.cshtml";
}
#section scripts
{
#Scripts.Render("~/bundles/jqueryval" )
}
#using (Html.BeginForm("register", "Adder", FormMethod.Post))
{
<div class="form-group">
#Html.TextBoxFor(m => m.Name,new { #class = "form-control" })
#Html.ValidationMessageFor(m => m.Name)
<input type="submit" name="submit" />
</div>
}
It is because you are accepting the parameter as string and no validation script in view page (as of we can't see if the layout page has the scripts).
I have a partial View AddOptionPartial which uses a model OptionViewModel.
This partial view is loaded when its parent, Build is loaded.
This Build view uses the model BuildViewModel.
The partial is loaded in using using #{Html.RenderPartial("AddOptionPartial", new OptionViewModel());}.
The partial looks like this:
#model SurveyService.ViewModels.OptionViewModel
#{
Layout = null;
}
#using (Ajax.BeginForm("AddOption", "Manage", new AjaxOptions { OnSuccess = "handleSavedOptionChoice(data)", HttpMethod = "Post" }))
{
<div class="form-group">
#Html.LabelFor(m => m.ChoiceText)
#Html.TextBoxFor(m => m.ChoiceValue, new { #class = "form-control" })
</div>
#Html.LabelFor(m => m.ChoiceValue)
<div class="form-group">
<div class="btn-group" data-toggle="buttons">
<label class="btn btn-primary">
#Html.RadioButtonFor(m => m.ChoiceValue, 1) True
</label>
<label class="btn btn-primary">
#Html.RadioButtonFor(m => m.ChoiceValue, 0) False
</label>
<label class="btn btn-primary active">
#Html.RadioButtonFor(m => m.ChoiceValue, 2, true) Text
</label>
</div>
</div>
<button type="submit">Create</button>
}
As you can see, i am trying to post this form using an Ajax call. The call refers to this function:
public ActionResult AddOption(OptionViewModel model)
{
***content of function***
}
However, when debugging, the model is an OptionViewModel object containing no data.
I can not find a reason for this on SO, can someone please advise?
Try this:
#using (Ajax.BeginForm("AddOption", "Manage",null, new AjaxOptions { OnSuccess = "handleSavedOptionChoice(data)", HttpMethod = "Post" }))
public ActionResult AddOptionChoice([System.Web.Http.FromBody]OptionViewModel model)
{
***content of function***
}
#{Html.RenderPartial("AddOptionChoicePartial", new OptionViewModel());} passes exactly that; a new, presumably empty, OptionViewModel.
You should either pass in a pre-populated model (from the parent BuildViewModel) like #{Html.RenderPartial("AddOptionChoicePartial", parentViewModel.SomeModel);} or construct the View Model from scratch in AddOptionChoice.
(Note also that you're calling "AddOptionChoicePartial" from Html.RenderPartial, but the Controller Action is named AddOptionChoice. They should match)
Also, in trying to recreate the model from your comments, you're using ChoiceText and ChoiceValue, which do not match OptionText and OptionValue.
Error in the code found:
#Html.LabelFor(m => m.ChoiceText)
#Html.TextBoxFor(m => m.ChoiceValue, new { #class = "form-control" })
#Html.RadioButtonFor(m => m.ChoiceValue, 1) True
Setting multiple values on a single parameter causes that parameter to act like a list, which only creates a ModelState error and will never fill the model.
To anyone who encounters a similar problem in the future: Debug your controller function and have a look at the ModelState in the debugger.
Thanks to everyone who advised.
Change the parameter name to data
public ActionResult AddOption(OptionViewModel data)
{
// Do something here
}
I have the following and am getting a 404 on submit, but from fiddler I'm getting a 500 error about not having the antiforgerytoken. Any thoughts what might be wrong?
ShareController.cs code
// creates the form
[HttpPost]
public ActionResult ShareVideoFormEmail(string vguid)
{
var shareModel = GetShareVideoFormEmailModel(vguid);
return PartialView("ShareVideoFormEmail", shareModel);
}
// handler function
[HttpPost]
[ValidateHoneyPot]
[ValidateAntiForgeryToken]
public ActionResult ProcessShareVideoFormEmail(ShareVideoEmailModel model)
{
//do stuff
return PartialView("Result");
}
ShareVideoFormEmail.cshtml
#model Gcc.GctWebsite.Areas.Forms.Models.Share.ShareVideoEmailModel
#using (Ajax.BeginForm("ProcessShareVideoFormEmail","share",null ,new AjaxOptions
{
InsertionMode = InsertionMode.Replace,
UpdateTargetId = "shareVideoFormWithMyFriends",
OnBegin = "onBeginShareEmail", #* show ajaxspinner *#
OnComplete = "onCompleteShareEmail", #* hide ajaxspinner *#
OnSuccess = "onSuccessShareEmail" #* hide ajaxspinner *#
}, new { id = "shareVideoEmailModelForm" }))
{
<div class="modal-header">
<div class="modal-header-title">Share the video "#ViewBag.VideoTitle" with my friends</div>
</div>
<div class="modal-body">
<div id ="shareVideoFormWithMyFriends" class="share-form-email">
#Html.AntiForgeryToken()
#Html.HoneyPot()
#Html.HiddenFor(x => x.UpdateTargetId)
#Html.HiddenFor(x => x.OgMetaTitle)
#Html.HiddenFor(x => x.OgMetaDescription)
#Html.HiddenFor(x => x.OgMetaDefaultImage)
#Html.HiddenFor(x => x.OgMetaImage)
#Html.HiddenFor(x => x.Brand)
#Html.HiddenFor(x => x.VGuid)
#Html.HiddenFor(x => x.TripCode)
#Html.HiddenFor(x => x.EmailSubject)
<!-- big section of input fields for model properties -->
<div class="form-field">
<span class="req-lbl">* = Required</span>
</div>
<div class="form-field form-submit">
<input class="btn btn-main-theme btn-narrow btn-submit-form" value="Send Email" type="submit">
<input type="reset" class="btn btn-grey btn-reset cancel-share-Email" value="Cancel">
</div>
<script>
$(document).ready(function (e) {
$('.required-input').after("<span class=\"rqdfield\">*</span>");
loadJqueryValidationManually("#shareVideoEmailModelForm");
});
</script>
It turns out that in my model in some of the data there were some html no-no's. I had to htmlencode a couple properties before displaying the view, then it worked fine upon postback.
I discovered this by changing the input parameter of the action method to object and removing the honeypot and xsfr validation. Then I got a nice 500 error explaining I had unsafe data.
I have a controller with 2 actions: 1 for displaying a form and 2nd for processing the submitted form. Smth like this:
public ActionResult Create()
{
TestModel model = new TestModel() { Value1 = "aaa" };
return View(model);
}
[HttpPost]
public ActionResult Create(TestModel model)
{
model.Value2 = "bbb";
return View(model);
}
As you can see I pre-populate Value1 with "aaa" so it appears on the form - this part works fine. However, when I submit the form I would like to fill other properties of the model object and re-display the form using the same view.
The problem is that on submit the form still displays the original model object and not the updated one i.e. only Value1 is populated with "aaa" and Value2 is empty.
The view I use is the Auto-generated one by scaffolding option:
#using (Html.BeginForm()) {
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<fieldset>
<legend>TestModel</legend>
<div class="editor-label">
#Html.LabelFor(model => model.Value1)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Value1)
#Html.ValidationMessageFor(model => model.Value1)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.Value2)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Value2)
#Html.ValidationMessageFor(model => model.Value2)
</div>
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
}
You have to call ModelState.Clear() so the result will be the expected. MVC does assumptions when POSTed data is sent back to the client. But beware, clearing it manually might cause undesired results ...
You can read more about here: Asp.net MVC ModelState.Clear and http://blogs.msdn.com/b/simonince/archive/2010/05/05/asp-net-mvc-s-html-helpers-render-the-wrong-value.aspx
I'm using MVC 4 and Entity Framework to develop an web app. I have a table which contains persons. There is also an Edit button which invokes a modal window and thanks to it, the user can edit a person. I'm using a partial view to do so.
My question is : in my action, I return a View but I just want that when I click on the Save button, the modal window disappear and my table is updated. Any idea?
The actions :
[HttpGet]
public ActionResult EditPerson(long id)
{
var person = db.Persons.Single(p => p.Id_Person == id);
ViewBag.Id_ProductPackageCategory = new SelectList(db.ProductPackageCategories, "Id_ProductPackageCategory", "Name", person.Id_ProductPackageCategory);
return PartialView("_EditPerson", person);
}
[HttpPost]
public ActionResult EditPerson(Person person)
{
ViewBag.Id_ProductPackageCategory = new SelectList(db.ProductPackageCategories, "Id_ProductPackageCategory", "Name", person.Id_ProductPackageCategory);
if (ModelState.IsValid)
{
ModelStateDictionary errorDictionary = Validator.isValid(person);
if (errorDictionary.Count > 0)
{
ModelState.Merge(errorDictionary);
return View(person);
}
db.Persons.Attach(person);
db.ObjectStateManager.ChangeObjectState(person, EntityState.Modified);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(person);
}
The partial view (actually, the modal window) :
#model BuSIMaterial.Models.Person
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
<h3 id="myModalLabel">Edit</h3>
</div>
<div>
#using (Ajax.BeginForm("EditPerson", "Person", FormMethod.Post,
new AjaxOptions
{
InsertionMode = InsertionMode.Replace,
HttpMethod = "POST",
UpdateTargetId = "table"
}))
{
#Html.ValidationSummary()
#Html.AntiForgeryToken()
#Html.HiddenFor(model => model.Id_Person)
<div class="modal-body">
<div class="editor-label">
First name :
</div>
<div class="editor-field">
#Html.TextBoxFor(model => model.FirstName, new { maxlength = 50 })
#Html.ValidationMessageFor(model => model.FirstName)
</div>
<div class="editor-label">
Last name :
</div>
<div class="editor-field">
#Html.TextBoxFor(model => model.LastName, new { maxlength = 50 })
#Html.ValidationMessageFor(model => model.LastName)
</div>
<div class="editor-label">
National number :
</div>
<div class="editor-field">
#Html.EditorFor(model => model.NumNat, new { maxlength = 11 })
#Html.ValidationMessageFor(model => model.NumNat)
</div>
<div class="editor-label">
Start date :
</div>
<div class="editor-field">
#Html.TextBoxFor(model => model.StartDate, new { #class = "datepicker", #Value = Model.StartDate.ToString("yyyy/MM/dd") })
#Html.ValidationMessageFor(model => model.StartDate)
</div>
<div class="editor-label">
End date :
</div>
<div class="editor-field">
#if (Model.EndDate.HasValue)
{
#Html.TextBoxFor(model => model.EndDate, new { #class = "datepicker", #Value = Model.EndDate.Value.ToString("yyyy/MM/dd") })
#Html.ValidationMessageFor(model => model.EndDate)
}
else
{
#Html.TextBoxFor(model => model.EndDate, new { #class = "datepicker" })
#Html.ValidationMessageFor(model => model.EndDate)
}
</div>
<div class="editor-label">
Distance House - Work (km) :
</div>
<div class="editor-field">
#Html.EditorFor(model => model.HouseToWorkKilometers)
#Html.ValidationMessageFor(model => model.HouseToWorkKilometers)
</div>
<div class="editor-label">
Category :
</div>
<div class="editor-field">
#Html.DropDownList("Id_ProductPackageCategory", "Choose one ...")
#Html.ValidationMessageFor(model => model.Id_ProductPackageCategory) <a href="../ProductPackageCategory/Create">
Add a new category?</a>
</div>
<div class="editor-label">
Upgrade? :
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Upgrade)
#Html.ValidationMessageFor(model => model.Upgrade)
</div>
</div>
<div class="modal-footer">
<button class="btn btn-inverse" type="submit">Save</button>
</div>
}
</div>
And my script which invokes the modal :
$(document).ready(function () {
$('.edit-person').click(function () {
var id = $(this).data("id");
var url = '/Person/EditPerson/'+id;
$.get(url, function(data) {
$('#edit-person-container').html(data);
$('#edit-person').modal('show');
});
});
});
a couple of changes would need to be made but here is what I would do this case
1) change the EditPerson post method from an actionresult to a JsonResult
[HttpPost]
public JsonResult EditPerson(Person person)
{
// code here to save person
bool success = true; // somehow determine if the save was successful
string msg = ""; // error message is needed?
return JsonResult(new {success,msg, person});
}
2) Add a javascript function to close the modal
function closeModal(response){
// the response is the Json Result sent back from the action
if (response.success === true){
// actually got a true response back
}
$('#edit-person').modal('show'); // or similar code
}
3) Then Update your Ajax call to perform code on when its successful
#using (Ajax.BeginForm("EditPerson", "Person", FormMethod.Post,
new AjaxOptions
{
InsertionMode = InsertionMode.Replace,
HttpMethod = "POST",
UpdateTargetId = "table",
OnSuccess = "closeModal(response);" // or javascript code to close the modal, you can also
}))
{ ...
a couple of tips
I don't like the MVC ajax helpers. I think they are bloated and I feel there are other frameworks better at it. Thats my opinion. To each their own though. I would prefer to use the jQuery ajax library myself. I think its easier to use but again, its up to you.
The OnSuccess means on "Server Success" not save success. so be careful.
Disclaimer: I wrote this while tired and so it might not be 100% let me know of any issues.
Good luck
In your POST action you could return the following:
return Json(new { error = false, message = "Person edited." });
In the AjaxOptions in Ajax.BeginForm add this:
OnSuccess = "Modal.onAjaxSuccess"
Then somewhere, say in script.js:
function onAjaxSuccess(data, status, xhr) {
if (data.error) {
$.notify({
type: "error",
text: data.message
});
}
else {
$('.modal').modal('hide');
}
}
This will close the window, but I'm still unable to get the darkened screen associated with Bootstrap modals to go away, it also won't update the DIV using AJAX - maybe Darin can shed some light?
If you're still wondering how to do this, this is how:
You have a GET action that returns the list of people:
[HttpGet]
public ActionResult People()
{
return PartialView("_ListOfPeoplePartial");
}
Then have a JS function that fires when you click save (i.e. btn-save-new-person) on the modal that allows you to create new people:
$(function () {
$(document.body).on('click', '#btn-save-new-person', function (e) {
$('#create-new-person-modal').modal('hide');
$('body').removeClass('modal-open');
$('.modal-backdrop').remove();
var url = "/Home/People";
$.get(url, function (data){
$('#list-of-people').html(data);
});
});
});
in my action, I return a View but I just want that when I click on the Save button, the modal window disappear and my table is updated. Any idea?
Instead of returning a view from this controller action you could return a partial view containing the table. And then in the success callback of the AJAX call simply update the corresponding container.