Should a ValidationAttribute have one error message? - javascript

ValidationAttribute.ErrorMessage is a string implying there should be one error per ValidationAttribute.
Say I have some code like this:
[BeforeThan(nameof(EndTime), nameof(EndTime2), ErrorMessage = "StartTime should before than EndTime and EndTime2")]
public DateTime StartTime { get; set; }
and an IsValid method like this:
protected override ValidationResult IsValid(object startTime, ValidationContext validationContext)
{
var endTimePropertyValue = validationContext.ObjectType.GetProperty(EndTimePropertyName)
.GetValue(validationContext.ObjectInstance);
if (startTime != null && startTime is DateTime
& endTimePropertyValue != null && endTimePropertyValue is DateTime)
{
if ((DateTime)startTime > (DateTime)endTimePropertyValue)
{
return new ValidationResult(ErrorMessage);
}
}
return ValidationResult.Success;
}
There is unobtrusive JavaScript on the client side.
Say I wanted to add more logic to say; if end date 1 is the same as end date 2 then report a different error message.
Create a separate attribute for this i.e. have two attributes.
Amend the code above somehow to return two different error messages depending on the scenario
Please remember that I am usinf obtrusive JavaScript on the client side.

There is no explicit need of using only ErrorMessage property to set error text. You can introduce additional properties indicate whether it's needed to check if dates are equal
public string ErrorMessage2 { get; set; }
protected override ValidationResult IsValid(object startTime, ValidationContext validationContext)
{
var endTimePropertyValue = validationContext.ObjectType.GetProperty(EndTimePropertyName)
.GetValue(validationContext.ObjectInstance);
if (startTime != null && startTime is DateTime
& endTimePropertyValue != null && endTimePropertyValue is DateTime)
{
DateTime startDateTime = (DateTime)startTime;
DateTime endDateTime = (DateTime)endTimePropertyValue;
//if second error message is not empty we check if date are the same
bool checkIfEqual = !string.IsNullOrEmpty(ErrorMessage2);
if (checkIfEqual && startDateTime == endDateTime)
{
return new ValidationResult(ErrorMessage2);
}
else if (startDateTime > endDateTime)
{
return new ValidationResult(ErrorMessage);
}
}
return ValidationResult.Success;
}
Or you can discard ErrorMessage at all and use hardcoded strings
private const string StartDateBefore = "StartTime should before than EndTime and EndTime2";
private const string StartDateEqual = "StartTime is equal to EndTime";
public bool CheckIfEqual { get; set; }
protected override ValidationResult IsValid(object startTime, ValidationContext validationContext)
{
var endTimePropertyValue = validationContext.ObjectType.GetProperty(EndTimePropertyName)
.GetValue(validationContext.ObjectInstance);
if (startTime != null && startTime is DateTime
& endTimePropertyValue != null && endTimePropertyValue is DateTime)
{
DateTime startDateTime = (DateTime)startTime;
DateTime endDateTime = (DateTime)endTimePropertyValue;
if (CheckIfEqual && startDateTime == endDateTime)
{
return new ValidationResult(StartDateEqual); //error message when dates are equal
}
else if (startDateTime > endDateTime)
{
return new ValidationResult(StartDateBefore); //error message when start date is after enddate
}
}
return ValidationResult.Success;
}
Usage
[SomeValidation(nameof(EndDate), CheckIfEqual = true)]
public DateTime StartDate { get; set; }
To make this validation attribute work with client side validation you need to implement IClientModelValidator interface as it described here.
public void AddValidation(ClientModelValidationContext context)
{
//"beforedate" and "equaldate" will be added as custom validators
//for unobtrusive validation
context.Attributes.Add("data-val-beforedate", StartDateBefore);
if (CheckIfEqual)
context.Attributes.Add("data-val-equaldate", StartDateEqual);
}
With this code implemented input will contain additional attributes with respective error messages. Now we need to implement custom validators in javascript and copy validation logic from C# code
//add "beforedate" custom validator
$.validator.addMethod("beforedate", function (value, element, parameters) {
var startDate = new Date(value);
var endDate = new Date($("#EndDate").val());
//if condition is true then value considered valid
return endDate >= startDate;
});
//add unobtrusive adapter to run "beforedate" validation
$.validator.unobtrusive.adapters.add("beforedate", [], function (options) {
options.rules.beforedate = {};
options.messages["beforedate"] = options.message; //save error message passed from C#
});
$.validator.addMethod("equaldate", function (value, element, parameters) {
var startDate = new Date(value);
var endDate = new Date($("#EndDate").val());
return endDate.getTime() != startDate.getTime();
});
$.validator.unobtrusive.adapters.add("equaldate", [], function (options) {
options.rules.equaldate = {};
options.messages["equaldate"] = options.message;
});

Related

Implementing C# code that generates a string from an object field and field values

I have a code that is written in Javascript and I want to convert it to C#. What the code does is to get a javascript object and then creates a string using the key value pairs in object. How would you do something similar to this in C#.
Here is the javascript code
const userSession = {
application_id: 1111,
auth_key: "abc123456,
nonce: a456654,
timestamp: 1254564,
user: {
login : "johnwick",
password: 123434543
}
}
signParams(userSession) {
if (typeof userSession != 'object') {
throw Error('not an object');
}
let signature = Object.keys(userSession)
.map(elem => {
if (typeof userSession[elem] === 'object') {
return Object.keys(userSession[elem])
.map(elem1 => {
return elem + '[' + elem1 + ']=' + userSession[elem][elem1];
})
.sort()
.join('&')
}
else {
return elem + '=' + userSession[elem]
}
})
.sort()
.join('&')
return this.hmacSha1(signature);
}
signParams(userSession)
The string should look like this
"application_id=3610&auth_key=aDRceQyTXSYEdJU&nonce=a456654&timestamp=${timestamp}&user[login]=johnwick&user[password]=123434543"
Here is how I initialize the object in c# using c# object since there is no object in c# that is similar to Javascript object
public class UserSession
{
public int ApplicationId { get; set; }
public string AuthKey { get; set; }
public double Nonce { get; set; }
public double Timestamp { get; set; }
public string Signature { get; set; }
public dynamic User { get; set; }
}
public async Task<Session> GenerateSessionParams(User user)
{
string filename = "config.json";
using FileStream openStream = File.OpenRead(filename);
Config config = await JsonSerializer.DeserializeAsync<Config>(openStream);
Session session = new Session();
session.ApplicationId = config.cred.appId;
session.AuthKey = config.cred.authKey;
session.Nonce = this.RandomNonce();
session.Timestamp = this.GetTimeStamp();
if (user.Login != default && user.Password != default)
{
session.User = new { Login = user.Login, Password = user.Password };
}
else if (user.Email != default && user.Password != default)
{
session.User = new { Email = user.Email, Password = user.Password };
}
return session;
}
I think what you are trying to do is to convert your object into a query string.
I don't think c# has any in-built function to do so, you need to write your own function to do this.
Try using reflection to get Property names and their values at runtime and concat them into strings in a query string format.
Here is an article on how to do so.
Serialize object into a query string with Reflection
I have taken the code snippet from article and changed few things according to your need.
static string ConvertToQueryString(UserSession session)
{
var properties = session.GetType().GetProperties()
.Where(x => x.CanRead)
.Where(x => x.GetValue(session,null) != null)
.ToDictionary(x=>x.Name, x=>x.GetValue(session,null));
var propertyNames = properties
.Where(x => !(x.Value is string) && x.Value is IEnumerable)
.Select(x => x.Key)
.ToList();
foreach (var key in propertyNames)
{
var valueType = properties[key].GetType();
var valueElemType = valueType.IsGenericType
? valueType.GetGenericArguments()[0]
: valueType.GetElementType();
if (valueElemType.IsPrimitive || valueElemType == typeof (string))
{
var enumerable = properties[key] as IEnumerable;
properties[key] = string.Join("&", enumerable.Cast<object>());
}
}
return string.Join("&", properties
.Select(x => string.Concat(
Uri.EscapeDataString(x.Key), "=",
Uri.EscapeDataString(x.Value.ToString()))));
}
}
Note: Like in the article, it is better to create an extension method if you want to chain this function.
Read More about Extension Functions: Extension Methods (C# Programming Guide)

Custom data annotation validation attribute with client side validation

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.

RequiedIf not working on clint side in mvc5

I am working on custom validation in mvc. I am using requiredif attribute. It’s working on server side but not on client side.
RequiredIfAttribute.cs
public class RequiredIfAttribute : ValidationAttribute, IClientValidatable
{
protected RequiredAttribute _innerAttribute;
public string DependentProperty { get; set; }
public object TargetValue { get; set; }
public bool AllowEmptyStrings
{
get
{
return _innerAttribute.AllowEmptyStrings;
}
set
{
_innerAttribute.AllowEmptyStrings = value;
}
}
public RequiredIfAttribute(string dependentProperty, object targetValue)
{
_innerAttribute = new RequiredAttribute();
DependentProperty = dependentProperty;
TargetValue = targetValue;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
// get a reference to the property this validation depends upon
var containerType = validationContext.ObjectInstance.GetType();
var field = containerType.GetProperty(DependentProperty);
if (field != null)
{
// get the value of the dependent property
var dependentValue = field.GetValue(validationContext.ObjectInstance, null);
// trim spaces of dependent value
if (dependentValue != null && dependentValue is string)
{
dependentValue = (dependentValue as string).Trim();
if (!AllowEmptyStrings && (dependentValue as string).Length == 0)
{
dependentValue = null;
}
}
// compare the value against the target value
if ((dependentValue == null && TargetValue == null) ||
(dependentValue != null && (TargetValue.Equals("*") || dependentValue.Equals(TargetValue))))
{
// match => means we should try validating this field
//if (!_innerAttribute.IsValid(value))
if(value==null)
// validation failed - return an error
return new ValidationResult(FormatErrorMessage(validationContext.DisplayName), new[] { validationContext.MemberName });
}
}
return ValidationResult.Success;
}
//public virtual IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
//{
//}
private string BuildDependentPropertyId(ModelMetadata metadata, ViewContext viewContext)
{
// build the ID of the property
string depProp = viewContext.ViewData.TemplateInfo.GetFullHtmlFieldId(DependentProperty);
// unfortunately this will have the name of the current field appended to the beginning,
// because the TemplateInfo's context has had this fieldname appended to it. Instead, we
// want to get the context as though it was one level higher (i.e. outside the current property,
// which is the containing object, and hence the same level as the dependent property.
var thisField = metadata.PropertyName + "_";
if (depProp.StartsWith(thisField))
// strip it off again
depProp = depProp.Substring(thisField.Length);
return depProp;
}
public virtual IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
//IEnumerable<ModelClientValidationRule> IClientValidatable.GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
var rule = new ModelClientValidationRule
{
ErrorMessage = FormatErrorMessage(metadata.GetDisplayName()),
ValidationType = "requiredif",
};
string depProp = BuildDependentPropertyId(metadata, context as ViewContext);
// find the value on the control we depend on;
// if it's a bool, format it javascript style
// (the default is True or False!)
string targetValue = (TargetValue ?? "").ToString();
if (TargetValue is bool)
targetValue = targetValue.ToLower();
rule.ValidationParameters.Add("dependentproperty", depProp);
rule.ValidationParameters.Add("targetvalue", targetValue);
yield return rule;
}
}
requiredif.js
$(function () {
alert('hii');
$.validator.addMethod('requiredif', function (value, element, parameters) {
alert(value);
var id = '#' + parameters['dependentproperty'];
alert(id);
// get the target value (as a string,
// as that's what actual value will be)
var targetvalue = parameters['targetvalue'];
targetvalue = (targetvalue == null ? '' : targetvalue).toString();
// get the actual value of the target control
// note - this probably needs to cater for more
// control types, e.g. radios
var control = $(id);
var controltype = control.attr('type');
var actualvalue =
(controltype === 'checkbox' || controltype === 'radio') ?
control.attr('checked').toString() :
control.val();
// if the condition is true, reuse the existing
// required field validator functionality
if ($.trim(targetvalue) === $.trim(actualvalue) || ($.trim(targetvalue) === '*' && $.trim(actualvalue) !== ''))
return $.validator.methods.required.call(
this, value, element, parameters);
return true;
});
$.validator.unobtrusive.adapters.add(
'requiredif',
['dependentproperty', 'targetvalue'],
function (options) {
options.rules['requiredif'] = {
dependentproperty: options.params['dependentproperty'],
targetvalue: options.params['targetvalue']
};
options.messages['requiredif'] = options.message;
});
});
Model
[Required]
public bool IsFeederSelected { get; set; }
[RequiredIf("IsFeederSelected", true, ErrorMessage = "You must enter purchase date")]
[Display(Name = "Meter Name")]
public List<string> SelectedMeterName { get; set; }
I would like to know how I can achieve the same, any small inputs on the same is also greatly appreciated.
Thanks in advance.

MVC5 - Checking for overlap between 2 Date (Textboxes) used to search against Date Field

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()

MVC3 and custom client-side validation messages

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.

Categories