I implemented the code below to have a way to validate a checkbox unobtrusively found this code posted by Darin Dimitrov. It works really well for the checkbox, but it does not work if you also have password and confirm password validated with the EqualValidator. I wonder if the custom Validator can be changed to take the checkbox and password validation into account. Or do I need to write a custom Validator for the password?
Model
[Validator(typeof(MyViewModelValidator))]
public class MyViewModel
{
public bool IsChecked { get; set; }
}
Validator
public class MyViewModelValidator : AbstractValidator<MyViewModel>
{
public MyViewModelValidator()
{
RuleFor(x => x.IsChecked).Equal(true).WithMessage("Please check this checkbox");
}
}
Controller
public class HomeController : Controller
{
public ActionResult Index()
{
return View();
}
[HttpPost]
public ActionResult Index(MyViewModel model)
{
return View(model);
}
}
View
#model MyViewModel
#using (Html.BeginForm())
{
#Html.LabelFor(x => x.IsChecked)
#Html.CheckBoxFor(x => x.IsChecked)
#Html.ValidationMessageFor(x => x.IsChecked)
#Html.LabelFor(model => model.Password, new { }, ":")
#Html.EditorFor(model => model.Password)
#Html.ValidationMessageFor(model => model.Password)
#Html.LabelFor(model => model.ConfirmPassword, new { }, ":")
#Html.EditorFor(model => model.ConfirmPassword)
#Html.ValidationMessageFor(model => model.ConfirmPassword)
<button type="submit">OK</button>
}
Custom FluentValidationPropertyValidator
public class EqualToValueFluentValidationPropertyValidator : FluentValidationPropertyValidator
{
public EqualToValueFluentValidationPropertyValidator(ModelMetadata metadata, ControllerContext controllerContext, PropertyRule rule, IPropertyValidator validator)
: base(metadata, controllerContext, rule, validator)
{
}
public override IEnumerable<ModelClientValidationRule> GetClientValidationRules()
{
if (!this.ShouldGenerateClientSideRules())
{
yield break;
}
var validator = (EqualValidator)Validator;
var errorMessage = new MessageFormatter()
.AppendPropertyName(Rule.GetDisplayName())
.AppendArgument("ValueToCompare", validator.ValueToCompare)
.BuildMessage(validator.ErrorMessageSource.GetString());
var rule = new ModelClientValidationRule();
rule.ErrorMessage = errorMessage;
rule.ValidationType = "equaltovalue";
rule.ValidationParameters["valuetocompare"] = validator.ValueToCompare;
yield return rule;
}
}
Global.asax
FluentValidationModelValidatorProvider.Configure(provider =>
{
provider.AddImplicitRequiredValidator = false;
provider.Add(typeof(EqualValidator), (metadata, context, description, validator) => new EqualToValueFluentValidationPropertyValidator(metadata, context, description, validator));
});
jQuery
(function ($) {
$.validator.unobtrusive.adapters.add('equaltovalue', ['valuetocompare'], function (options) {
options.rules['equaltovalue'] = options.params;
if (options.message != null) {
options.messages['equaltovalue'] = options.message;
}
});
$.validator.addMethod('equaltovalue', function (value, element, params) {
if ($(element).is(':checkbox')) {
if ($(element).is(':checked')) {
return value.toLowerCase() === 'true';
} else {
return value.toLowerCase() === 'false';
}
}
return params.valuetocompare.toLowerCase() === value.toLowerCase();
});
})(jQuery);
<script src="#Url.Content("~/Scripts/jquery.validate.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/jquery.validate.unobtrusive.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/customadapter.js")" type="text/javascript"></script>
I know this is a few months old, but I was having the same problem. I posted a solution in Darin's original thread -> Validate checkbox on the client with FluentValidation/MVC 3
Here it is:
First, I updated the javascript function with the following.
$.validator.addMethod('equaltovalue', function (value, element, params) {
if ($(element).is(':checkbox')) {
value = $(element).is(':checked') ? "true" : "false";
}
return params.valuetocompare.toLowerCase() === value.toLowerCase();
});
Secondly, I updated EqualToValueFluentValidationPropertyValidator with the following:
public class EqualToValueFluentValidationPropertyValidator : FluentValidationPropertyValidator
{
EqualValidator EqualValidator
{
get { return (EqualValidator)Validator; }
}
public EqualToValueFluentValidationPropertyValidator(ModelMetadata metadata, ControllerContext controllerContext, PropertyRule rule, IPropertyValidator validator) : base(metadata, controllerContext, rule, validator) {
ShouldValidate = false;
}
public override IEnumerable<ModelClientValidationRule> GetClientValidationRules() {
if (!ShouldGenerateClientSideRules()) yield break;
var propertyToCompare = EqualValidator.MemberToCompare as PropertyInfo;
if(propertyToCompare != null) {
// If propertyToCompare is not null then we're comparing to another property.
// If propertyToCompare is null then we're either comparing against a literal value, a field or a method call.
// We only care about property comparisons in this case.
var comparisonDisplayName =
ValidatorOptions.DisplayNameResolver(Rule.TypeToValidate, propertyToCompare, null)
?? propertyToCompare.Name.SplitPascalCase();
var formatter = new MessageFormatter()
.AppendPropertyName(Rule.GetDisplayName())
.AppendArgument("ComparisonValue", comparisonDisplayName);
string message = formatter.BuildMessage(EqualValidator.ErrorMessageSource.GetString());
yield return new ModelClientValidationEqualToRule(message, CompareAttribute.FormatPropertyForClientValidation(propertyToCompare.Name)) ;
}
else
{
var validator = (EqualValidator)Validator;
var errorMessage = new MessageFormatter()
.AppendPropertyName(Rule.GetDisplayName())
.AppendArgument("ValueToCompare", validator.ValueToCompare)
.BuildMessage(validator.ErrorMessageSource.GetString());
var rule = new ModelClientValidationRule();
rule.ErrorMessage = errorMessage;
rule.ValidationType = "equaltovalue";
rule.ValidationParameters["valuetocompare"] = validator.ValueToCompare;
yield return rule;
}
}
}
For FluentValidation v8
public class EqualToValueFluentValidationPropertyValidator : FluentValidationPropertyValidator
{
EqualValidator EqualValidator
{
get { return (EqualValidator)Validator; }
}
public EqualToValueFluentValidationPropertyValidator(ModelMetadata metadata, ControllerContext controllerContext, PropertyRule rule, IPropertyValidator validator)
: base(metadata, controllerContext, rule, validator)
{
ShouldValidate = false;
}
public override IEnumerable<ModelClientValidationRule> GetClientValidationRules()
{
if (!ShouldGenerateClientSideRules())
yield break;
var propertyToCompare = EqualValidator.MemberToCompare as PropertyInfo;
if (propertyToCompare != null)
{
// If propertyToCompare is not null then we're comparing to another property.
// If propertyToCompare is null then we're either comparing against a literal value, a field or a method call.
// We only care about property comparisons in this case.
var comparisonDisplayName =
ValidatorOptions.DisplayNameResolver(Rule.TypeToValidate, propertyToCompare, null)
?? propertyToCompare.Name.SplitPascalCase();
var formatter = new MessageFormatter()
.AppendPropertyName(Rule.GetDisplayName())
.AppendArgument("ComparisonValue", comparisonDisplayName);
string message;
try
{
message = EqualValidator.Options.ErrorMessageSource.GetString(null);
}
catch (FluentValidationMessageFormatException)
{
// User provided a message that contains placeholders based on object properties. We can't use that here, so just fall back to the default.
message = ValidatorOptions.LanguageManager.GetStringForValidator<EqualValidator>();
}
message = formatter.BuildMessage(message);
#pragma warning disable 618
yield return new ModelClientValidationEqualToRule(message, CompareAttribute.FormatPropertyForClientValidation(propertyToCompare.Name));
#pragma warning restore 618
}
else
{
var errorMessage = new MessageFormatter()
.AppendPropertyName(Rule.GetDisplayName())
.AppendArgument("ValueToCompare", EqualValidator.ValueToCompare)
.BuildMessage(EqualValidator.Options.ErrorMessageSource.GetString(null));
var rule = new ModelClientValidationRule
{
ErrorMessage = errorMessage,
ValidationType = "equaltovalue"
};
rule.ValidationParameters["valuetocompare"] = EqualValidator.ValueToCompare;
yield return rule;
}
}
}
Related
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×tamp=${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)
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 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.
i have controller method that upload image file, not using jQuery AJAX, from <input> type "file", the method returns:
Return Redirect(Request.UrlReferrer.PathAndQuery)
Because i want to stay in the same view after the submit click.
I want to show after the success image upload, toastr.success.
How i can do it?
In your http post action method, after successful upload, set an entry to TempData dictionary and read it in the next view which is loaded by the Redirect method and display the toastr message.
TempData["Msg"] = "Uploaded successfully";
return Redirect(Request.UrlReferrer.PathAndQuery);
in your view
<script>
$(function(){
var msg = "#(TempData["Msg"] as string)";
if (msg !== "") {
toastr.success(msg);
}
});
</script>
There is another way.
Create a Toastr model that includes Message, Title, Type, SessionID and Date.
public class Toastr
{
public string Title { get; set; }
public string Message { get; set; }
public ToastrType Type { get; set; }
public string SessionId { get; set; }
public DateTime Date { get; set; }
public Toastr(string message, string title = "Information" , ToastrType type = ToastrType.Info)
{
this.Message = message;
this.Title = title;
this.Type = type;
this.Date = DateTime.Now;
}
}
public enum ToastrType
{
Info = 0,
Success = 1,
Warning = 2,
Error = 3
}
Create a Service or Manager where you define your basic functions (add, remove toasts)
private static List<Toastr> _toasts = new List<Toastr>();
private static string GetSession()
{
return HttpContext.Current.Session.SessionID;
}
public static void AddToUserQueue(Toastr toastr)
{
toastr.SessionId = GetSession();
_toasts.Add(toastr);
}
public static void AddToUserQueue(string message, string title, ToastrType type)
{
var toast = new Toastr(message, title, type);
toast.SessionId = GetSession();
AddToUserQueue(toast);
}
public static bool HasQueue()
{
return _toasts.Any(t => t.SessionId == GetSession());
}
public static void RemoveUserQueue()
{
_toasts.RemoveAll(t => t.SessionId == GetSession());
}
public static void ClearAll()
{
_toasts.Clear();
}
public static List<Toastr> GetUserQueue()
{
if (HasQueue())
return _toasts.Where(t => t.SessionId == GetSession())
.OrderByDescending(x=>x.Date)
.ToList();
return null;
}
public static List<Toastr> GetAndRemoveUserQueue()
{
var list = GetUserQueue();
RemoveUserQueue();
return list;
}
In your layout / page make use of the functions by creating some helpers.
#helper ProcessToasts()
{
List<Toastr> toasts = ToastrManager.GetAndRemoveUserQueue();
if (toasts != null && toasts.Count > 0)
{
foreach (var item in toasts)
{
#ShowToastr(item);
}
}
}
#helper ShowToastr(Toastr item)
{
switch (item.Type)
{
case ToastrType.Info:
#ToastrInfo(item.Message, item.Title)
break;
case ToastrType.Success:
#ToastrSuccess(item.Message, item.Title)
break;
case ToastrType.Warning:
#ToastrWarning(item.Message, item.Title)
break;
case ToastrType.Error:
#ToastrError(item.Message, item.Title);
break;
}
}
#helper ToastrInfo(string message, string title)
{
<script>
toastr.info("#message","#title")
</script>
}
#helper ToastrSuccess(string message, string title)
{
<script>
toastr.success("#message","#title")
</script>
}
#helper ToastrWarning(string message, string title)
{
<script>
toastr.warning("#message","#title")
</script>
}
#helper ToastrError(string message, string title)
{
<script>
toastr.error("#message","#title")
</script>
}
Since the helpers are below closing HTML tag, you need just to add the #ProcessToasts() right before the body closing tag.
I have this code.
The objective of this js is through the dropwdown load the textbox UserMR
But it never finds the UserMR.
I call UserMr model inside Star instantiating a new object.
relationships:
Star contains idModel
Model does not contain IDStar
What is going on?
Model
[CustomValidation(typeof(StarValidation), "DateValidate")]
public partial class Star
{
public virtual string UserMR { get { return this.User.UserMR; } set { this.UserMR = value;} }
public Model User = new Model();
View inside "STAR"
#Html.EditorFor(i => i.IdModel, "StarModel", new { onchange = "updateUserMR($(this))" })
#Html.DisplayFor(i => i.UserMR)
<script type="text/javascript">
function updateUserMR(model) {
var user = model.brother("#Html.NameFor(x => x.UserMR)");
var idModel = model.val();
user.empty();
if (idModel != null && idModel != "")
user.loadOptions("#Url.Action("ListJsonUserMR", "Model")?idModel=" + idModel, true, true);
}
</script>
Controller
public JsonResult ListJsonUserMR(int idModel)
{
var model = this.service.GetUserMr(idModel).Select(x => new
{
Value = x.Id,
Description = x.UserMR
});
return this.Json(model, JsonRequestBehavior.AllowGet);
}
Service
public IEnumerable<Model> GetUser(int idModel)
{
return base.context.Models
.Where(x => x.Id == idModel);
}
Error