How do I validate a model in a JQuery created dialog and Ajax - ajax

I am creating a JQuery dialog where I have use data from a Model I want to validate, but the box is just closed, if I then click to open the dialog again I can see the red text indication errors, but it just closed.
function createEditorDialog() {
$('#floatsam_editor').dialog({ bgiframe: true, autoOpen: false, modal: true, width: 512,
buttons: {
'Close': function () { $('#floatsam_editor').dialog('close'); },
'Create': function () {
$('#flotsam_form').submit();
$('#floatsam_editor').dialog('close');
}
}
});
};
So the red text comes at the submit, but is closed right after, even though the validation failed.
Here is part of the ajax beginform that is shown
<div id="floatsam_editor">
#using (Ajax.BeginForm("CreateFlotsam" , "Flotsam", new { }, new AjaxOptions { HttpMethod = "Post", OnSuccess = "systematic_flotsam.successRequest" }, new { Id = "flotsam_form" }))
{
<div>
<fieldset>
<legend>Create Log Entries</legend>
<div >
<span class="editor-label">
#Html.LabelFor(m => m.Received.Date)
</span>
<span class="editor-field">
#Html.TextBoxFor(m => m.Received.Date, new { id = "flotsam_date", #class="datepicker", maxlength="10"})
</span>
<span class="editor-field">
#Html.TextBoxFor(m => m.Received.Hour, new { id = "flotsam_hours", maxlength="2" })
</span>:<span class="editor-field">
#Html.TextBoxFor(m => m.Received.Minute, new { id = "flotsam_minutes", maxlength="2"})
</span>
<span>
#Html.ValidationMessageFor(m => m.Received.Date)
#Html.ValidationMessageFor(m => m.Received.Hour)
#Html.ValidationMessageFor(m => m.Received.Minute)
</span>
</div>
<div>
<div class="editor-label">
#Html.LabelFor(m =>m.Flotsam.Informant)
</div>
<div class="editor-field">
#Html.TextBoxFor(m => m.Flotsam.Informant, new { #class = "flotsam_dialog_editor_field" })
#Html.ValidationMessageFor(m =>m.Flotsam.Informant)
</div>
</div>
Part of my model is here
[DisplayName("Informant:")]
[Required]
public object Informant { get; set; }
[DisplayName("Flotsam Nature:")]
[Required]
public object FlotsamNature { get; set; }
[DisplayName("Position of Loss:")]
[Required]
public object Position { get; set; }
And as seen it has 3 propertys which are required, but again it still closes if I dont enter anything in my ajax form
So how do I make the dialog box not close when model validation fails?
A very important note is that all this is done on one site and on client side, I do not want to reload the page.

Only close the dialog if the form is valid.
if($("#flotsam_form").valid())
{
$('#flotsam_form').submit();
$('#floatsam_editor').dialog('close');
}
This way it dialog will stay open and validation errors will appear

Since this is dyamically HTML content, you'll need to register the HTML that you wanted validated. This blog post should help you out.

Related

ModelState.AddModelError for Ajax loaded partials

I have a partial view on my main page that loads a form, the model is below:
public class CreateRequestViewModel
{
[Required]
public short ClientId { get; set; }
[Required]
public Guid SystemId { get; set; }
[Required]
public string RequestedUsername { get; set; }
public string TicketReference { get; set; }
public string Notes { get; set; }
public List<SelectListItem> Clients { get; set; }
public List<SelectListItem> Systems { get; set; }
}
This is the partial view:
#model Models.CreateRequestViewModel
#Html.AntiForgeryToken()
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="row">
<div class="col-lg-4 col-md-4 col-sm-12">
<h1>Create a Request</h1>
</div>
<div class="col-lg-8 col-md-8 col-sm-12 right">
<div class="form-group">
#Html.DropDownListFor(m => m.ClientId, Model.Clients, htmlAttributes: new { #class = "form-control form-control-lg", #id = "ClientSelect" })
#Html.ValidationMessageFor(m => m.ClientId, "", htmlAttributes: new { #class = "text-danger" })
</div>
<div class="form-group">
#Html.DropDownListFor(m => m.SystemId, Model.Systems, htmlAttributes: new { #class = "form-control form-control-lg", #id = "ClientSystemSelect" })
#Html.ValidationMessageFor(m => m.SystemId, "", htmlAttributes: new { #class = "text-danger" })
</div>
<div class="form-group">
#Html.TextBoxFor(m => m.RequestedUsername, htmlAttributes: new { #class = "form-control form-control-lg", #placeholder = "Username" })
#Html.ValidationMessageFor(m => m.RequestedUsername, "", htmlAttributes: new { #class = "text-danger" })
</div>
<div class="form-group">
#Html.TextBoxFor(m => m.TicketReference, htmlAttributes: new { #class = "form-control form-control-lg", #placeholder = "Ticket reference" })
</div>
<div class="form-group">
#Html.TextAreaFor(m => m.Notes, htmlAttributes: new { #class = "form-control form-control-lg", #rows = 3, #placeholder = "Notes..." })
</div>
<input type="Submit" class="btn btn-secondary btn-block send-request" value="Submit" name="">
</div>
</div>
This is how I'm loading the page:
<div class="container">
<div class="row">
<div class="col-lg-6">
<form asp-action="CreateRequest" asp-controller="Access"
data-ajax="true"
data-ajax-method="POST"
data-ajax-mode="replace"
data-ajax-update="#createRequest">
<div id="createRequest">
#await Html.PartialAsync("_CreateRequest", Model.CreateRequestModel)
</div>
</form>
</div>
</div>
</div>
With the model as it is and using the unobtrusive javascript, leaving the RequestedUsername blank for example will result in the form not being submitted and a validation message appearing for it. This is great.
However, I have a requirement to check the form data against entries in a database first and throw an error if there's an existing record. I thought that, with all the client side validation passing, I'd use ModelState.AddModelError in the controller like so:
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult CreateRequest(CreateRequestViewModel model)
{
if(model.RequestedUsername == "someincorrectvalue"){ //actual logic removed for brevity
ModelState.AddModelError("RequestedUsername", "Already in use");
}
if(!ModelState.IsValid)
{
//reset lists on model, removed
return PartialView("_CreateRequest", model);
}
_logger.LogInformation("CreateRequest successful");
return RedirectToAction(nameof(Index));
}
However, if I use ModelState.AddModelError, the return PartialView("_CreateRequest", model) call ends up reloading the whole page as if it's returning a full View.
I'm at a loss as to why this is happening. The difference I can see is that i'm adding a ModelState error inside the controller, whereas validation is happening client side otherwise.
Anyone have an idea?
So, this turned out to be a combination of problems. To begin with, the unobtrusive Ajax scripts I had within my solution were not performing. I don't know why but I replaced them with one from the CDN: https://ajax.aspnetcdn.com/ajax/jquery.unobtrusive-ajax/3.2.5/jquery.unobtrusive-ajax.min.js
That solved the problem of the whole page reloading instead of the partial being returned via unobtrusive ajax.
The second problem was that, on success, I was redirecting to the Index controller action instead of returning a Partial again. This was causing the entire Index page to be rendered inside the div i'd chosen as my ajax target. My controller action now looks like this:
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult CreateRequest(CreateRequestViewModel model)
{
if(model.RequestedUsername == "someincorrectvalue"){ //actual logic removed for brevity
ModelState.AddModelError("RequestedUsername", "Already in use");
}
if(!ModelState.IsValid)
{
//reset lists on model, removed
return PartialView("_CreateRequest", model);
}
_logger.LogInformation("CreateRequest successful");
// reset lists on model, removed
ModelState.Clear(); // get rid ofany model details to make way for a new request
return PartialView("_CreateRequest", model);
}

MVC 5 Ajax form with field updates asynchronously and still supporting a Full form post

So I have been using MVC for a while but not a lot of Ajax.
So the issue i have is that I have a create view where the first field of the create view is a Date picker which on change i want ajax to change the selection dropdown of another field on the form. I have the Ajax update working , but the form Post (Create button), now only calls into the Ajax method on the controller. I want the Ajax post back to call the default method Create method on the Controller. So the Create form contains 3 fields
A date (which has the OnChange Ajax submit)
A drop down list of id's and text
other fields as required
I have included the model and cshtml view files (one the partial). The controller is just simply a method taking either the datetime value or the entire model.
So I have the code working where, when the date changes it updates the relevant LocalIds field, but because the 'create' button is inside the Ajax.BeginForm tags, when the create button is pressed, it generates an Ajax call, and I want it to generate a Form Post. What am i missing
CreateModel
public class IdsSelection
{
public string Id { get; set; }
public List<SelectListItem> Selections { get; set; }
}
public class CreateModel
{
[Display(Name="Local Id's")]
public IdsSelection LocalIds;
[Display(Name="Day")]
[Required, DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:dd/MM/yyyy}",ApplyFormatInEditMode=true)]
public DateTime Date { get; set; }
[Required, Display(Name = "Other Value")]
[Range(1.0, 1000000.0, ErrorMessage ="Value must be greater than zero")]
public decimal OtherValue { get; set; }
public CreateModel()
{
Date = DateTime.Today;
}
}
CreateView.cshtml
#Model Demo.Models.CreateModel
....
#using (Ajax.BeginForm("ChangeDate", "Process", new AjaxOptions()
{
InsertionMode = InsertionMode.Replace,
HttpMethod = "GET",
UpdateTargetId = "changeid",
}))
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.Date, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Date, new
{
htmlAttributes = new
{
autofocus = "autofocus",
#class = "form-control",
#Value = Model.Date.ToString("yyyy-MM-dd"),
onchange = "$(this.form).submit();"
}
})
#Html.ValidationMessageFor(model => model.Date, "", new { #class = "text-danger" })
</div>
</div>
#Html.Partial("_ShowIds", Model.LocalIds)
<div class="form-group">
#Html.LabelFor(model => model.OtherValue, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.OtherValue, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.OtherValue, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</div>
</div>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
#section Scripts {
<script src="~/Scripts/jquery.unobtrusive-ajax.min.js"></script>
}
_ShowIds.cshtml
#model Demo.Models.IdsSelection
<div id="changeid" class="form-group">
#Html.Label("Local Id", htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#if(Model.Selections.Count == 1) // one
{
#Html.HiddenFor(model => model.Id)
#Html.EditorFor(model => model.Selections[0].Text, new
{
htmlAttributes = new
{
#class = "form-control",
#readonly = "readonly",
}
})
}
else // more entries so show a dropdown
{
#Html.DropDownListFor(model => model.Id, Model.Selections, new { #class = "form-control" })
#Html.ValidationMessageFor(model => model, "", new { #class = "text-danger" })
}
</div>
</div>
You have not shown you controller methods, but assuming the method which generates this view is named Create(), then create a corresponding POST method
[httpPost]
public ActionResult Create(CreateModel model)
for saving the data. The remove #using (Ajax.BeginForm("ChangeDate", "Process", new AjaxOptions() { ..... ])) and replace with a normal form to post back to the server (and you can remove jquery.unobtrusive-ajax.min.js)
#using (Html.BeginForm())
{
....
Next create a controller method that returns your partial view based on the selected date
public PartialViewResult FetchLocalIds(DateTime date)
{
IdsSelection model = // populate model based on selected date
return PartialView("_ShowIds", model);
}
Next, in the view, wrap the current #Html.Partial() in a placeholder element
<div id="localids">
#Html.Partial("_ShowIds", Model.LocalIds)
</div>
Then use jquery to handle the datepickers .change() event and call the FetchLocalIds() method to update the DOM using the .load() function.
$('#Date').change(function() {
$('#localids').load('#Url.Action("FetchLocalIds")', { date: $(this).val() })
})
Note also to remove onchange = "$(this.form).submit();" from the #Html.EditorFor(model => model.Date, new { ... })

Unobtrusive client validation is not working when the form contains hidden input field

I have a model like:
public class Person
{
public int ID { get; set; }
[Required]
public string LastName { get; set; }
[Required]
public string FirstMidName { get; set; }
}
And the view:
#model ContosoUniversity.Models.Student
#using (Html.BeginForm("Edit", "Student", FormMethod.Post))
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
#Html.HiddenFor(model => model.ID)
<div
#Html.LabelFor(model => model.LastName, new { #class = "control-label col-md-2" })
#Html.EditorFor(model => model.LastName)
#Html.ValidationMessageFor(model => model.LastName)
</div>
<div >
#Html.LabelFor(model => model.FirstMidName, new { #class = "control-label col-md-2" })
#Html.EditorFor(model => model.FirstMidName)
#Html.ValidationMessageFor(model => model.FirstMidName)
</div>
<div>
<input type="submit" value="Save" class="btn btn-default" />
</div>
</div>
}
The problem is that when I am rendering the view using Ajax Get using return PartialView("Edit", student); statement, the unobtrusive jQuery validation fails. The validation is working fine when I remove the hidden field #Html.HiddenFor(model => model.ID).
I have used below code to render the view using Ajax Get request:
function ShowEditPartialView(sender) {
$.ajax({
type: "GET",
url: "/Student/EditPartial",
data: {
id: courseid
},
async: false,
success: function (data) {
$("#EditCoursePartial").html(data);
///because the page is loaded with ajax, the validation rules are lost, we have to rebind them:
$('form').removeData('validator');
$('form').removeData('unobtrusiveValidation');
$.validator.unobtrusive.parse('form');
}
});
}
Any idea on how it will be resolved.

Can I restrict client-side validation to specific fields?

I have a Form where I successfully use unobtrusive-validation with the [remote] annotation.
I have other fields in the Form with [required] annotation in the model but I don't want client-side validation for these fields.
I only want server-side validation for [required] fields
I haven't found a solution and I wonder if it's easily feasible?
EDIT :
A part of my code
Part of my model :
I would like the first field : "Email" use Unobtrusive-validation and the second one : "PasswordHash" only use server-side validation even if it has [required] annotation.
Finally, i don't want an AJAX error message for all my form 's fields.
[Required(ErrorMessageResourceType = typeof(Project.Resources.Models.Accounts.User), ErrorMessageResourceName = "EMAIL_REQUIRED")]
[Display(Name = "EMAIL_DISPLAY", ResourceType = typeof(Project.Resources.Models.Accounts.User))]
[Remote("EmailExists", "User", "An account with this email address already exists.")]
public string Email { get; set; }
[Required(ErrorMessageResourceType = typeof(Project.Resources.Models.Accounts.User), ErrorMessageResourceName = "PASSWORDHASH_REQUIRED")]
[DataType(DataType.Password)]
[Display(Name = "PASSWORDHASH_DISPLAY", ResourceType = typeof(Project.Resources.Models.Accounts.User))]
public string PasswordHash { get; set; }
Part of the action in Controller
Server-side validation :
[HttpPost]
public ActionResult Register(User user)
{
if (ModelState.IsValid )
{
DataContext.User.Add(user);
DataContext.SaveChanges();
return RedirectToAction("Index");
}
return View(user);
}
Ajax validation :
public JsonResult EmailExists(string email)
{
User user = DataContext.User.SingleOrDefault(x => x.Email == email);
return user == null ?
Json(true, JsonRequestBehavior.AllowGet) :
Json(
string.Format("an account for address {0} already exists.",
email), JsonRequestBehavior.AllowGet);
}
Part of the view :
#using (Html.BeginForm())
{
#Html.ValidationSummary(true)
<div class="editor-label">
#Html.LabelFor(model => model.Email)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Email)
#Html.ValidationMessageFor(model => model.Email)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.PasswordHash)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.PasswordHash)
#Html.ValidationMessageFor(model => model.PasswordHash)
</div>
<p>
<input type="submit" name="op" id="edit-submit" value="#Project.Resources.Views.User.Register.SUBMIT_FORM" class="form-submit art-button" />
</p>
}
When I click on the sumbit button i would like a server-side validation.
And for some specific fields like Email i would like an Ajax Validation.
there might be better ways, but one way to accomplish this is to do the following
#using (Html.BeginForm("Register", "Home", FormMethod.Post, new { id = "registerForm" }))
{
#Html.ValidationSummary(true)
<div class="editor-label">
#Html.LabelFor(model => model.Email)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Email)
#Html.ValidationMessageFor(model => model.Email)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.PasswordHash)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.PasswordHash)
#Html.ValidationMessageFor(model => model.PasswordHash)
</div>
<p>
<input type="submit" name="op" id="edit-submit" value="#Project.Resources.Views.User.Register.SUBMIT_FORM" class="form-submit art-button" />
</p>
}
<script type="text/javascript">
// add the rules for the fields that you want client-side validation on
// leave out the fields that you dont want client-side validation. they will be validated
// on server side.
$("#registerForm").validate({
rules: {
Email: { required: true }
},
messages: {
Email: { required : "Email is required" }
}
});
</script>

Telerik MVC 3 (Razor) Q1 2012 Editor EditorFor() Binding Returns Null Value and Unobtrusive Validation Doesn't Work

I'm currently working on my first MVC3 application at work (using the Razor view engine), and decided to use the open source Telerik Q1 2012 controls since they will provide a lot of the functionality I need (and look nice as well). Right now the issue I'm having is using the Telerik Editor control and binding to my view model. I have standard Html.EditorFor() controls on the page that return the value in the ViewModel correctly, but the property bound to Telerik Editor is null. Their documentation is completely useless (it only mentions EditorFor one time), and it doesn't seem like they answer too many questions on the forum either. My main question is, how do I bind the Telerik MVC3 Editor to a model and have it set the property that's bound to it? My code for the view model is below (thanks for any help you can provide, and keep in mind, I'm brand new to MVC, I'm doing this project on my own to get familiar with it and introduce some new technologies to the group):
public class SupportViewModel
{
[Display(Name = "Ticket Subject")]
[MaxLength(30)]
[Required(ErrorMessage = "The ticket subject is required.")]
public string TicketSubject { get; set; }
[Display(Name = "Support Issue")]
[Min(1, ErrorMessage = "You must select a support issue.")]
public int SupportIssueID { get; set; }
[Display(Name = "Ticket Priority")]
[Min(1, ErrorMessage = "You must select a ticket priority.")]
public int TicketPriorityID { get; set; }
//public string EmployeeID { get; set; }
public bool IsClosed { get; set; }
[Required(ErrorMessage = "The detail message is required.")]
public string DetailMessage { get; set; }
}
View Code:
#model RadixMVC.ViewModels.SupportViewModel
#{
ViewBag.Title = "Create New Support Ticket";
}
<h2>Radix Support: Create New Support Ticket</h2>
<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 style="width: 500px">
<legend>Create New Support Ticket</legend>
<div class="editor-label">
#Html.LabelFor(model => model.TicketSubject)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.TicketSubject)
#Html.ValidationMessageFor(model => model.TicketSubject)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.SupportIssueID)
</div>
<div class="editor-field">
#Html.DropDownList("SupportIssueID", string.Empty)
#Html.ValidationMessageFor(model => model.SupportIssueID)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.TicketPriorityID)
</div>
<div class="editor-field">
#Html.DropDownList("TicketPriorityID", string.Empty)
#Html.ValidationMessageFor(model => model.TicketPriorityID)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.IsClosed)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.IsClosed)
#Html.ValidationMessageFor(model => model.IsClosed)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.DetailMessage)
</div>
<div class="editor-field">
#*#Html.EditorFor(model => model.DetailMessage)*#
#Html.ValidationMessageFor(model => model.DetailMessage)
<br />
#{ Html.Telerik().EditorFor(model => model.DetailMessage)
.Name("DetailMessageEditor")
.HtmlAttributes(new { style = "height: 200px" })
.Encode(false)
.Render();
}
</div>
<div>
<br />
<input type="submit" value="Create Ticket" title="Submits a new support ticket" />
<input type="submit" onclick="parent.location='#Url.Action("Index", "Support", "Index")'" value="Cancel" title="Return to Support Home" />
</div>
</fieldset>
}
And finally, Controller Code:
[HttpPost]
public ActionResult Create(SupportViewModel vm)
{
if (ModelState.IsValid)
{
SupportTicket SupportTicket = new SupportTicket()
{
SupportTicketID = Guid.NewGuid(),
EmployeeID = "123456",
TicketOpenDate = DateTime.Now,
TicketModifiedDate = DateTime.Now,
IsClosed = vm.IsClosed,
TicketSubject = vm.TicketSubject,
SupportIssueID = vm.SupportIssueID,
TicketPriorityID = vm.TicketPriorityID
};
TicketDetail TicketDetail = new TicketDetail()
{
TicketDetailID = Guid.NewGuid(),
SupportTicketID = SupportTicket.SupportTicketID,
TicketOrder = 1,
EmployeeID = "123456",
DetailDate = DateTime.Now,
DetailMessage = vm.DetailMessage
};
SupportTicket.TicketDetails.Add(TicketDetail);
db.SupportTickets.Add(SupportTicket);
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.SupportIssueID = new SelectList(db.SupportIssues, "SupportIssueID", "Name", vm.SupportIssueID);
ViewBag.TicketPriorityID = new SelectList(db.TicketPriorities, "TicketPriorityID", "Name", vm.TicketPriorityID);
return View(vm);
}
I was able to get this working. The documentation is either very outdated or just doesn't explain how to do this very well (probably both). But I was able to get this working by making the following change to my Razor syntax:
<div class="editor-label">
#Html.LabelFor(model => model.DetailMessage)
</div>
<div class="editor-field">
#*#Html.EditorFor(model => model.DetailMessage)*#
#Html.ValidationMessageFor(model => model.DetailMessage)
<br />
#{ Html.Telerik().EditorFor(model => model.DetailMessage)
//.Name("DetailMessageEditor")
.HtmlAttributes(new { style = "height: 200px" })
.Encode(true)
.Render();
}
</div>
Removing the "Name" property from the control solved the problem of not getting anything back, but when I tried to save, I immediately got an error (something to do with XSS, cross-site scripting), and I assumed it was because the HTML wasn't being encoded. I changed the Encode property to true, and now all is good.
Came across a similar thing today.
I was using a View Model and the property I wanted to bind to was was a property of a child object in the View Model.
When I submitted, the value in the RTE was not being bound. When I looked in Request.From object I could see the the value was being returned in the correct format for it to bound in the usual way so I was a little confused.
Anyway, if you want it to bind you need to give the exact name of your property to the the RTE so in your case
.Name("DetailMessage")
should work but
.Name("DetailMessageEditor")
will not.
Im my case i had to name the RTE
.Name("object.Property")
where object is the child object on the View model where my property lives to get it to work
Hope this helps someone.

Resources