I have unobtrusive client-side validation setup for my page. The error messages are returned from our database. For one of the validation messages I needed to add parameters so I can format it with particular values. This works fine server side but I obviously haven't got access to some of these values when the GetClientValidationRules method is first setup. Because of this it looks like I'm going to have to build up the error message in my client-side code but I have no idea on how to do this as you simply return true or false in the jQuery.validator.addMethod.
So what I basically need to be able to do is set ErrorMessage to string.Empty in GetClientValidationRules method, and then in my clinet-side code which is doing the validation be able to return whatever message I want.
Here is the client-side code being wired up in MVC 3.
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
var rule = new ModelClientValidationRule
{
ValidationType = "maximumdatecoverrequired",
ErrorMessage = string.Empty,
};
rule.ValidationParameters.Add("maxdate", DateTime.Now.AddDays(Settings.Default.MaximumDateCoverRequiredDaysInFuture).ToString("yyyy/MM/dd"));
return new[] { rule };
}
Here is my client-side code to validate against this particular property.
jQuery.validator.addMethod("maximumdatecoverrequired", function (value, element, params) {
var maxDate = new Date(params["maxdate"]);
var day = maxDate.getDate();
var month = maxDate.getMonth() + 1;
var year = maxDate.getFullYear();
var dateCoverRequired = new Date(value).toString('yyyy/MM/dd');
maxDate = maxDate.toString('yyyy/MM/dd');
if (value > maxDate) {
$("input#DateCoverRequired_Day").val(day);
$("select#DateCoverRequired_Month").val(month);
$("input#DateCoverRequired_Year").val(year);
return false;
}
return true;
});
How do I return a custom message in my client-side code?
Let me give you an example of how to do this. The example I'll choose is registering a new user and checking for their name.
What we're going to do is allow the user to choose a UserName and, if it already exists in the database, we won't let them have it and will make a suggestion.
To do this we'll use Remote validation which points to an ActionMethod in our controller.
Register Model
public class RegisterModel
{
//This is the one I'm giving you the code for...
[Required]
[RegularExpression(#"(\S)+", ErrorMessage = "Username cannot contain spaces.")]
[Remote("CheckUserName", HttpMethod="POST")]
[Display(Name = "Username")]
public string UserName { get; set; }
// You can do this one yourself :-)
[Required]
[Remote("CheckEmailAddress", ErrorMessage="{0} already has an account, please enter a different email address.", HttpMethod="POST")]
[DataAnnotationsExtensions.Email(ErrorMessage="{0} is not a valid email address.")]
[Display(Name = "Email address")]
public string Email { get; set; }
[Required]
[ValidatePasswordLength]
[DataType(DataType.Password)]
[Display(Name = "Password")]
public string Password { get; set; }
[DataType(DataType.Password)]
[Display(Name = "Confirm password")]
[Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
public string ConfirmPassword { get; set; }
}
ActionMethod (the Remote method referenced by the model)
[HttpPost]
[OutputCache(Location = OutputCacheLocation.None, NoStore = true)]
public JsonResult CheckUserName(string userName, Guid? userId = null)
{
if (userName != null || userName.Length > 2)
{
var users = Membership.FindUsersByName(userName);
if (users.Count == 0)
{
return Json(true);
}
else
{
if ((users[userName].ProviderUserKey as Guid?) == userId)
{
return Json(true);
}
else
{
string suggestedUID = String.Format(CultureInfo.InvariantCulture, "{0} is not available.", userName);
// Maybe this is a bit feeble, but it will loop around (inefficiently) and suggest a new username with a number on the end. EG Tom is not available. Try Tom37
for (int i = 1; i < 100; i++)
{
string altCandidate = userName + i.ToString();
if (Membership.FindUsersByName(altCandidate).Count == 0)
{
suggestedUID = String.Format(CultureInfo.InvariantCulture, "{0} is not available. Try {1}.", userName, altCandidate);
break;
}
}
// This is the important bit. I am returning a suggested UserName
return Json(suggestedUID, JsonRequestBehavior.AllowGet);
}
}
}
else
{
return Json(true);
}
}
I think this is pretty cool, because the regular expression makes sure there are no spaces and then (if it's okay) it's submitted to the remote method which checks the database.
Related
I have made a custom data annotation attribute which verifies whether an email already exists in my database like this:
public class ValidateEmail : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
using (var ctx = new myCtx())
{
if (value != null)
{
var valueAsString = value.ToString().ToLower();
IEnumerable<string> email = ctx.Users.Where(x => x.Email != null).Select(x => x.Email);
if (email.Contains(valueAsString))
{
var errorMessage = FormatErrorMessage(validationContext.DisplayName);
return new ValidationResult(errorMessage);
}
}
return ValidationResult.Success;
}
}
}
And in my view model I set it like this:
[ValidateEmail(ErrorMessage = "Email exists")]
[Required(ErrorMessage = "Required")]
[RegularExpression(#"^([\w\.\-]+)#([\w\-]+)((\.(\w){2,3})+)$", ErrorMessage = "Invalid Email")]
public string Email { get; set; }
This works perfectly when the page reloads...But I would like now to change this so that I enable client side validation and message displaying without reloading the page itself...
How can I modify this validation attribute to make it compliable with jquery's unobstrusive validation in .NET MVC?
Based on #Sayan's link, I've implemented something like this:
public class ValidateEmail : ValidationAttribute, IClientValidatable
{
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
var rule = new ModelClientValidationRule();
rule.ErrorMessage = FormatErrorMessage(metadata.GetDisplayName());
rule.ValidationType = "emailvalidate";
yield return rule;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
using (var ctx = new myContext())
{
if (value != null)
{
var valueAsString = value.ToString().ToLower();
IEnumerable<string> email = ctx.Users.Where(x => x.Email != null).Select(x => x.Email);
if (email.Contains(valueAsString))
{
var errorMessage = FormatErrorMessage(validationContext.DisplayName);
return new ValidationResult(errorMessage);
}
}
return ValidationResult.Success;
}
}
}
and client side:
$.validator.addMethod("emailvalidate", function (value, element) {
{
console.log("value: " + value + " " + element);
}});
$.validator.unobtrusive.adapters.add("emailvalidate", function (options) {
//adding rules for validator
options.rules["emailvalidate"] = true;
if (options.message) {
options.messages["emailvalidate"] = options.message;
}
});
But whatever I insert now in the email field as email like:
myemail#ymail.xyz
I get the error that email exists ?
You can implement IClientValidatable in your ValidateEmail validation attribute to supply the data-val-xxx attributes to the client side.
Then you can write the jQuery unobtrusive validator and adapter to validate the field value on client side using the data-val-xxx rendered in HTML.
Make sure to return true (truthy value) or false (falsy value) from the jQuery validator based on whether the field value is valid or not respectively.
Lastly, include this custom jQuery validator script to your view.
You can find more details here. Though this blog post presents a slightly complicated scenario, but seeing how to use IClientValidatable and how to write jQuery unobtrusive validator is enough.
Hope this is helpful.
I'm a novice in javascript, and a junior developper in OOP.
After many attempts and many google search I dind't make it to solve it.
I have a DropDownList and a Partial View. I want to give the selected value to the partial view controller. It works when I write the value directly in, but it doesn't if i try to catch the DropDownList value. For the moment the value returned is always empty.
Model
public partial class Clients
{
public int ClientID { get; set; }
public string Code { get; set; }
public string Nom { get; set; }
public string Adresse1 { get; set; }
public string Adresse2 { get; set; }
public string CP { get; set; }
public string Ville { get; set; }
public Nullable<System.DateTime> DateCreation { get; set; }
public Nullable<System.DateTime> DateModification { get; set; }
}
View
#Html.DropDownList("id", (IEnumerable<SelectListItem>)ViewData["oas"], new { #id = "ClientID" })
<div id="result"></div>
<script>
$(function () {
$('#ClientID').change(function () {
//var pid = $("#id").val();//$(this).data('id');
$('#result').load('#Url.Action("filter")',
{ id: $("#id").val() } //replacing $("#id").val() by "3" makes it work, but i of course don't a constant value here
);
});
});
Controller
public class OnePageController : Controller
{
Entities db = new Entities();
public ActionResult Index()
{
List<SelectListItem> list = new List<SelectListItem>();
list.Add(new SelectListItem { Text = "-Please select-", Value = "Selects items" });
var clts = (
from c in db.Clients
select c).ToArray();
for (int i = 0; i < clts.Length; i++)
{
list.Add(new SelectListItem
{
Text = clts[i].Nom,
Value = clts[i].ClientID.ToString(),
Selected = (clts[i].ClientID == 1)
});
}
ViewData["oas"] = list;
return View(/*db.Clients.ToList()*/);
}
[HttpPost]
public ActionResult Filter(string id)
{
var contact = from c in db.Contacts
where c.ClientID.ToString() == id
select c;
return PartialView(contact);
}
}
Any idea would be greatly appreciated, also i don't know how to debug javasript, i use the developper tools in my when browser to try to catch the values, but i don't really track the changes..
You should change a bit your script:
$(function () {
// You select the element with id = ClientID
var clientEle = $('#ClientID');
// You register an event handler for the change of the selected value
clientEle.change(function () {
// clientEle.val() would return the selected value
$('#result').load('#Url.Action("filter")',{ id: clientEle.val() });
});
});
Regarding how you should debug JavaScript I would suggest to write the following keyword a few lines before you want to start the debugging:
debugger;
Then open developer tools and refresh your page. When JavaScript engine hits the debugger would stop it's execution and from this moment you could examine you code line by line.
For a thorough understanding in how you could debug JavaScript, you could start by checking the following links
https://developers.google.com/web/tools/chrome-devtools/javascript/
https://developer.mozilla.org/en-US/docs/Mozilla/Debugging/Debugging_JavaScript
https://www.w3schools.com/js/js_debugging.asp
I am currently looking into creating some search options to filter down my model results. I have a RunDate field which I want to search between using 2 textbox fields.
#Html.TextBox("StartDate", null, new { #class = "datefield form-control", type = "date" })
#Html.TextBox("EndDate", null, new { #class = "datefield form-control", type = "date" })
<input type="submit" class="btn btn-primary" value="Search" />
And my controller index task
public async Task<ActionResult> Index(int? jobId, int? page, DateTime? StartDate, DateTime? EndDate)
......
......
if (StartDate.HasValue )
{
jobs = jobs.Where(s => s.RunAfter >= StartDate);
pageNumber = 1;
}
if (EndDate.HasValue)
{
jobs = jobs.Where(s => s.RunAfter <= EndDate);
pageNumber = 1;
}
I however want to stop the search from happening if the dates overlap incorrectly eg. StartDate > EndDate.
What's the best way to do this? Do I have to use Javascript and add a validate() to my input click??
I have looked into Request Validation but this is now Obsolete.
I could also add a validationResult such as
if (StartDate > EndDate)
{
return new ValidationResult("EndDate must be greater than StartDate");
}
But I am unsure where to add this. So basically what the best approach to validate these form fields using the most efficient approach?
If you wrap your form fields into a ViewModel, this model can implement IValidatableObject.
public class SearchViewModel : IValidatableObject {
public DateTime? StartDate { get; set; }
public DateTime? EndDate { get; set; }
// other properties ...
// will be called automatically to check ModelState.IsValid
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext) {
if (StartDate > EndDate) {
yield return new ValidationResult("EndDate must be greater than StartDate", "EndDate");
}
// other checks here, also yield ValidationResult ...
}
}
And check ModelState in your Action:
public async Task<ActionResult> Index(SearchViewModel postData) {
if (!ModelState.IsValid) {
// error handling, e.g. return View Index again (errors will already have been added)
}
// no error -> continue
}
A list of the errors can be rendered in Razor like this:
#Html.ValidationSummary()
I'm using Knockout 2.2.1, JQuery 1.9.1, and Ryan Niemeyer's Knockout-jqAutoComplete 0.4.3.
The application allows the user to add a contact to a list of contacts. The contact information comes from a list of possible contacts.
We are using a span to display the names of contacts that exist in the database and data model on page load. When the user clicks the name, they have the opportunity to edit or delete the contact. We hide the span and show an input field in its place. This field is the AutoComplete field and it should show the Name of the contact.
The user can also click a button to add a new contact, which adds a new row with an input field which also uses AutoComplete.
Information edited or added by the user is persisted when a save button is clicked.
The Problem
When the user enters editing mode, the input displays the ID instead of the name.
Here's an image of the data displayed on page load, and here's what it looks like when you switch to editing mode. In the second picture, we'd like for it to display "Doe, John" instead of the primary key from the database.
Using an older version of Knockout-jqAutoComplete, this was not an issue. (We updated to the current version because there were other problems.) I can't find a version number on it, but it was used by including a Razor partial, "_jqueryAutoCompleteBinding.cshtml". The binding for this old version was:
//Deprecated, no longer in use.
<input data-bind="jqAuto: { autoFocus: false }, jqAutoSource: optionsProfessionalsAuto, jqAutoQuery: getProfessionals, jqAutoValue: ProfessionalId, jqAutoSourceLabel: 'displayName', jqAutoSourceInputValue: 'Name', jqAutoSourceValue: 'Id'" class="edit-data Name" />
The binding we're now using is:
<input data-bind="jqAuto: { source: getProfessionals, value: ProfessionalId, valueProp: 'ProfessionalId', labelProp: 'Name', inputProp: 'Name', options: { autoFocus: false } }"class="edit-data Name" />
And here is the relevant portion of the AppViewModel:
function AppViewModel() {
//{...}
self.addProfessionalContact = function () {
var skip = this.ProfessionalContacts().length * 14 - 1; // rows * columns - 1
this.ProfessionalContacts.push({
Name: ko.observable(''),
Specialty: ko.observable(''),
City: ko.observable(''),
State: ko.observable(''),
ProfessionalId: ko.observable(),
optionsProfessionalsAuto: ko.observableArray() //deprecated?
});
WireUpEditGridEvents();
SetDisabled();
RegisterDatePicker();
WireUpPostAddEvents(skip, 'professionalContacts');
$("#professionalContacts tr").last().find(".display-data").hide();
//{...}
}
(Edit 1)
Here is GetProfessionals, as requested:
function getProfessionals(searchTerm, sourceArray) {
try {
$.ajax({
type: "GET"
, contentType: 'application/json; charset=utf-8'
, url: '#Url.Action(MVC.Professional.ActionNames.GetProfessionals, MVC.Professional.Name)?searchTerm=' + searchTerm
, dataType: "json"
, success: function (data) {
var result = [];
for (var i = 0; i < data.length; i++) {
var specialty = data[i].Specialty ? data[i].Specialty + " - " : "";
result.push({ Name: data[i].Name + " (" + specialty + data[i].City + ", " + data[i].State + ")", ProfessionalId: data[i].ProfessionalId });
}
sourceArray(result);
}
}
)
.fail(function () { $.unblockUI(); $.growlUI('Error', 'Failed to get professionals. Unknown error.'); });
} catch (e) {
$.unblockUI();
throw e;
}
(Edit 2)
I think I do need some further clarification.
The getProfessionals function returns a list of type ProfessionalContactEditViewModel. This class contains several properties:
public class ProfessionalContactEditViewModel
{
public int? ClientContactId { get; set; }
public int ProfessionalId { get; set; }
public string Name { get; set; }
public string Specialty { get; set; }
public string Phone { get; set; }
public string Fax { get; set; }
public string Address1 { get; set; }
public string Address2 { get; set; }
public string City { get; set; }
public string State { get; set; }
public string Zip { get; set; }
public bool Send { get; set; }
public bool ReferredBy { get; set; }
public bool Delete { get; set; }
//Added based on RPNiemeyer's response
//public NameIdViewModel ProfessionalNameId
//{
// get
// {
// return new NameIdViewModel()
// {
// Name = this.Name,
// Id = this.ProfessionalId,
// };
// }
// set
// {
// this.Name = value.Name;
// this.ProfessionalId = value.Id ?? default(int);
// }
//}
}
Based upon RP's answer, I added a NameIdViewModel obj. which returns the Name and ProfessionalId. This, however, is not turned into an observable as a nested object, so we're now passing the entire ProfessionalContactViewModel from getProfessionals:
result.push(ko.toJSON(data));
I have removed valueProp from the input binding; it otherwise reads the same as before, but I'm still seeing a number when I edit a contact.
The browser tab also freezes for a few seconds when searching for a contact by name, and displays a completely blank list. The former of these two new issues may be due to the larger amount of data.
I am developing a page with ASP.NET MVC and API, using Knockout, Typescript, and the code is as: JSFiddler
The server code:
// POST: api/Empresas
[ResponseType(typeof(Business))]
public async Task<IHttpActionResult> PostBusiness(Business business)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
db.Businesses.Add(business);
await db.SaveChangesAsync();
return CreatedAtRoute("DefaultApi", new { id = business.Id }, business);
}
Business Obj:
public class Business
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[ScaffoldColumn(false)]
public Guid Id { get; set; }
[Display(Name = "CNPJ", Description = "Cadastro Nacional de Pessoa Juridica")]
public string CNPJ { get; set; }
[Display(Name = "Nome", Description = "Razão Social")]
public string Name { get; set; }
[Display(Name = "Limite Mensal", Description = "Limite mensal de uso")]
public int MonthlyLimit { get; set; }
[Display(Name = "Mensal Atual", Description = "Uso Mensal Atual")]
public int MonthlyCurrent { get; set; }
[Display(Name = "Inicio Assinatura", Description = "Data inicial da assinatura")]
public DateTime SubscriptionStart { get; set; }
[Display(Name = "Meses Contratados", Description = "Numero de meses contratados")]
public int MonthContractCount { get; set; }
}
The example is not gonna work since there is not an end point to test against.
My first question is why won't the data-bind work for this?
Second and most important, I keep getting the following error message:
Message from webpage
[object Object]
parsererror
SyntaxError: Invalid character - failed to create business
OK
I have no idea why this is happening, and I have done multiple changes to the code with success. Any ideas?
Values I was using:
CNPJ = 123.321.321-25
Name = Test Business
monthlyLimit = 20000
SubscriptionStart = 01/01/2014
monthContractCount = 24
Thanks in advance
EDIT:
createBusiness = (formElement) => {}
I changed the signature on the method to look like the above, with attempt to solve the scope problem with "this" key word. Still not working though.
NEW EDIT:
createBusiness = (formElement) => {
$(formElement).validate();
if ($(formElement).valid()) {
var formserialized = $(formElement).serialize();
$.post('/Api/Empresas/', formserialized, null, "json")
.done((result) => {
this.Business.Id = result.Id;
this.Business.Name(result.Name);
this.Business.CNPJ(result.CNPJ);
this.Business.MonthlyLimit(result.MonthlyLimit);
this.Business.SubscriptionStart(result.SubscriptionStart);
this.Business.MonthContractCount(result.MonthContractCount);
this.Business.MonthlyCurrent(result.MonthlyCurrent);
})
.fail((x, y, z) => {
alert(x + '\n' + y + '\n' + z + ' - failed to create business');
});
};
}
This is the last change, and it worked now, but as you can see I had to change the this.BaseUri to '/Api/Empresas/' this change was necessary due to scope of "this", so this is a fix but not really. Any ideas there?
change
var formserialized = $(formElement).serialize();
to
var formserialized = JSON.stringify($(formElement).serialize());