I am trying to validate if a check box is checked on the client using FluentValidation. I can't figure it our for the life of me.
Can it be done using unobtrusive validation?
Let's assume that you have the following model:
[Validator(typeof(MyViewModelValidator))]
public class MyViewModel
{
public bool IsChecked { get; set; }
}
with the following validator:
public class MyViewModelValidator : AbstractValidator<MyViewModel>
{
public MyViewModelValidator()
{
RuleFor(x => x.IsChecked).Equal(true).WithMessage("Please check this checkbox");
}
}
and a controller:
public class HomeController : Controller
{
public ActionResult Index()
{
return View();
}
[HttpPost]
public ActionResult Index(MyViewModel model)
{
return View(model);
}
}
with a corresponding view:
#model MyViewModel
#using (Html.BeginForm())
{
#Html.LabelFor(x => x.IsChecked)
#Html.CheckBoxFor(x => x.IsChecked)
#Html.ValidationMessageFor(x => x.IsChecked)
<button type="submit">OK</button>
}
and in Global.asax you have registered the fluent validation model validator provider:
FluentValidationModelValidatorProvider.Configure();
So far we have server side validation up and running fine. That's good. That's always the first part that we must setup. I have seen people focusing too much on doing client side validation that they forget doing server side validation and when you disable javascript (or even worse if you stumble upon a user with bad intentions), well, bad things happen.
So far we are confident because we know that even if something gets screwed up on the client our domain is protected with server side validation.
So let's now take care for the client validation. Out of the box FluentValidation.NET supports automatic client validation for the EqualTo validator but when comparing against another property value which is the equivalent of the [Compare] data annotation.
But in our case we are comparing against a fixed value. So we don't get client side vaildation out of the box. And when we don't get something out of the box, we need to put it in the box.
So we start by defining a 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;
}
}
that we are going to register in Application_Start:
FluentValidationModelValidatorProvider.Configure(provider =>
{
provider.AddImplicitRequiredValidator = false;
provider.Add(typeof(EqualValidator), (metadata, context, description, validator) => new EqualToValueFluentValidationPropertyValidator(metadata, context, description, validator));
});
So far we have associated our custom FluentValidationPropertyValidator with the EqualValidator.
The last part is to write a custom adapter:
(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);
And that's pretty much it. All that's left is to include the client scripts:
<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 like the Darin Dimitrov's answer, but if you want to do it quickly, here is my alternative way.
Create an additional property in your model, e.g.:
public bool ValidationTrue { get; set; }
and set its value to true in the model's contructor.
Use it in your view to save the value across the requests:
#Html.HiddenFor(x => x.ValidationTrue)
Now add a validation rule like this:
public class MyViewModelValidator : AbstractValidator<MyViewModel>
{
public MyViewModelValidator()
{
RuleFor(x => x.ValidationTrue)
.Equal(true); // check it for security reasons, if someone has edited it in the source of the page
RuleFor(x => x.HasToBeChecked)
.Equal(x => x.ValidationTrue) // HasToBeChecked has to have the same value as ValidationTrue (which is always true)
.WithMessage("Required");
}
}
That validation is supported by the unobtrusive validator out-of-the-box.
I am coding in ASP.NET MVC5 and Darin's code produces a javascript error on the lines that reference value.ToLowerCase() when a checkbox is involved. Another issue is that this code invalidates the client side equality comparison between two properties. It only seems to work when comparing against a literal value...That may have been his intent, but I need it to work for both situations:
Here's one possible workaround, that involves only two changes to Darin's answer:
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;
}
}
}
This code was copied from the EqualToFluentValidationPropertyValidator internal class in the fluentvalidation source, and I added Darin's logic after the else. This allows the client-side validation to work for property comparisons as well as value comparisons...I'm not sure if this is a great approach since you're basically overriding the built-in equality validator and it may break in future releases of fluent validation....but Darin's answer has the same issue.
There might be better ways to handle this. If somebody knows of a way to directly include the logic from the internal EqualToFluentValidationPropertyValidator class, then I'd love to hear it.
it's based on #cryss answer
RuleFor(x => x.HasToBeChecked)
.Equal(x => true)
.WithMessage("Required");
you don't need to use additional property
Related
I have a simple form with a textbox (and a model editor I want to render in specific cases)
#using (Html.BeginForm("Import", "Flow"))
{
#Html.TextBoxFor(model => model.IsConfirmed)
#if (Model.IsConfirmed)
{
#Html.EditorFor(m => m.Preview)
}
}
The model used in this view is the following
public class ImportViewModel
{
public Boolean IsConfirmed { get; set; }
public PreviewViewModel Preview { get; set; }
public ImportViewModel()
{
this.IsConfirmed = false;
}
}
The form posts on the following controller
public class FlowController
{
[HttpPost]
public ActionResult Import(ImportViewModel model)
{
try
{
if (ModelState.IsValid)
{
if (model.IsConfirmed)
{
// do something else
}
else
{
model.Preview = Preview(model.strCA, model.SelectedAccount);
model.IsConfirmed = true;
return View(model);
}
}
}
catch (Exception ex)
{
throw new Exception("arf", ex);
}
return RedirectToAction("Index", "Home");
}
}
On first load, the textbox contains "false"
When posted, the property IsConfirmed of the model is set to "true" and this model is passed to the same view.
I expect the textbox to be "true" but it is still "false"... moreover the Preview property is correctly rendered, so it means Model.IsConfirmed is indeed true...
Am I missing something ?
Thanks
Make sure you remove the value from the ModelState if you intend to modify it:
ModelState.Remove("IsConfirmed");
model.IsConfirmed = true;
The reason you need to do that is because, by design, all Html helpers (such as TextBoxFor) will first look for a value in the ModelState when binding and only not found they will use the value on your model. And since there's a value with the same name in the ModelState (coming from the POST request), that's what's being used.
I have written an attribute before, but I I have not written a validation attribute before. I am seriously confused about how it all works together. I have read most of the tutorials online about how to go about accomplishing this. But I am left with a couple of questions to ponder.
Keep in mind that I am trying to write a requiredIf attribute that will only call a remote function if a certain Jquery variable is set... which incidentally is a variable that is pulled from view state... I guess I could make that part of my view model. But I digress
1) The C# code is slightly confusing. I know my attribute should extend the ValidationAttribute, IClientValidatable class and interface respectively. But I am a little confused about what each of the overidden methods should be doing? I am trying to write a requiredIf, how does overwriting these methods help me accomplish this goal?
2) If the variable is not there, I simply don't want the remote function to attempt to validate the field. I don't want any message to pop up on my form. Alot of the tutorials seem to revolve around that.
3) I am confused about what I need to do with the jquery to add this function to the view... What do I need to add to the JQuery to get this thing to work... It seems like a lot of extra coding when I could simply just type up a jquery function that did the same thing with just the same ore less coding... I know it also adds server side validation which is good. But still...
Here is what I have for my jquery side of this equation...
(function ($) {
$validator.unobtrusive.adapters.addSingleVal("requiredifattribute", "Dependent");
$validator.addMethod("requiredifattribute", function (value, element, params) {
if (!this.optional(element)) {
var otherProp = $('#' + params)
return (otherProp.val() != value);
}
return true;
})
}(jQuery));
Here is my Attribute (which is basically carbon copied out of one the required if tutorials... I know I need to customize it more, but once I get a better idea of what every piece is doing I will do that...
[AttributeUsage(AttributeTargets.Property)]
public class RequiredIfAttribute : ValidationAttribute, IClientValidatable {
private const string errorMessage = "The {0} is required.";
//public string
private RequiredAttribute innerAttribute = new RequiredAttribute();
public string DependentProperty { get; set; }
public object TargetValue { get; set; }
public RequiredIfAttribute(string dependentProperty, object targetValue){
this.DependentProperty = dependentProperty;
this.TargetValue = targetValue;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext) {
var field = validationContext.ObjectInstance.GetType().GetProperty(DependentProperty);
if (field != null) {
var dependentValue = field.GetValue(validationContext.ObjectInstance, null);
if ((dependentValue == null && TargetValue == null) || (dependentValue.Equals(TargetValue))) {
if (!innerAttribute.IsValid(value))
return new ValidationResult(ErrorMessage);
}
}
return ValidationResult.Success;
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context) {
ModelClientValidationRule modelClientValidationRule = new ModelClientValidationRule {
ErrorMessage = FormatErrorMessage(metadata.DisplayName),
ValidationType = "requiredifattribute"
};
modelClientValidationRule.ValidationParameters.Add("dependent", DependentProperty);
yield return modelClientValidationRule;
}
}
UPDATE: What I have simply isn't working
Here is how a property in my model is anotated with the above attribute
[RequiredIf("isFlagSet", true)]
[Remote("ValidateHosFin", "EditEncounter", AdditionalFields = "hospitalFin, encflag", ErrorMessage = "Got Damn this is complex!")]
[MinLength(6)]
public string HostpitalFinNumber { get; set; }
The value in my view that I was trying to key this validation on is set up like so...
ViewData["ADDENCOREDITTEMP"] = encflag;
if (encflag == "AddEnc"){
isFlagSet = true;
}
I embed it into my page like so...
#Html.Hidden("isFlagSet", isFlagSet, new { id = "isFlagSet"})
I can't get my form to submit... The person who said he just tried this and got it to work, could you post the code?
Model:
public class X
{
[RequiredIf("y", "y", ErrorMessage = "y is not y")]
public string x { get; set; }
public string y { get; set; }
}
View:
#using(Html.BeginForm())
{
#Html.ValidationSummary()
#Html.TextBoxFor(m => m.x)
#Html.TextBoxFor(m => m.y)
<input type="submit"/>
}
I assume your validation fails on the server side? do you have isFlagSet property in your view model?
I'm using fluent validation in MVC 3. Is it possible to turn off fluent validation for specific post action in controller?
Thanks
Assuming you are using the AttributedValidatorFactory and you wanted to disable validation for the Index action on Home controller for POST verbs you could write a custom validator factory:
public class MyAttributedValidatorFactory : AttributedValidatorFactory
{
private readonly Func<HttpContextBase> _contextAccessor;
public MyAttributedValidatorFactory(Func<HttpContextBase> contextAccessor)
{
_contextAccessor = contextAccessor;
}
public override IValidator GetValidator(Type type)
{
var context = _contextAccessor();
var rd = context.Request.RequestContext.RouteData;
var action = rd.GetRequiredString("action");
var controller = rd.GetRequiredString("controller");
if (string.Equals("post", context.Request.HttpMethod, StringComparison.OrdinalIgnoreCase) &&
string.Equals("index", action, StringComparison.OrdinalIgnoreCase) &&
string.Equals("home", controller, StringComparison.OrdinalIgnoreCase)
)
if (type == typeof(MyViewModel))
{
return null;
}
return base.GetValidator(type);
}
}
which will be used to replace the default one in your Application_Start:
FluentValidationModelValidatorProvider.Configure(config =>
{
Func<HttpContextBase> contextAccessor =
() => new HttpContextWrapper(HttpContext.Current);
config.ValidatorFactory = new MyAttributedValidatorFactory(contextAccessor);
});
and then if you have the following action on the Home controller:
[HttpPost]
public ActionResult Index(MyViewModel model)
{
...
}
FluentValidation won't kick in.
Hay men you have miss some important point of fluentvalidation called the Validator customization.
find here http://fluentvalidation.codeplex.com/wikipage?title=mvc
Validator customization
With FluentValidation v3 you can use the CustomizeValidatorAttribute to configure how the validator will be run. For example, if you want the validator to only run for a particular ruleset then you can specify that ruleset name by attributing the parameter that is going to be validated:
public ActionResult Save([CustomizeValidator(RuleSet="MyRuleset")] Customer cust) {
// ...
}
this
I am using a Remote validation attribute on my view model to validate a Bank Account that is specified for my Company:
ViewModel:
[Remote("CheckDefaultBank", "Company")]
public string DefaultBank
{
This in the controller I have:
[HttpGet]
public JsonResult CheckDefaultBank(string defaultBank)
{
bool result = BankExists(defaultBank);
return Json(result, JsonRequestBehavior.AllowGet);
}
That all works well. But, I have two other banks related to my company as well. However, when the remote validation js calls the action it uses a parameter mactching the field name of "DefaultBank"... so I use that as a parameter in my action.
Is there some attribute I can add in the view so that it will use a parameter of say "bankId" on the ajax get so I don't need an action for each field which are basically exactly the same?
The goal here is to eliminate now having to have this in my controller:
[HttpGet]
public JsonResult CheckRefundBank(string refundBank)
{
bool result = BankExists(defaultBank);
return Json(result, JsonRequestBehavior.AllowGet);
}
[HttpGet]
public JsonResult CheckPayrollBank(string payrollBank)
{
bool result = BankExists(defaultBank);
return Json(result, JsonRequestBehavior.AllowGet);
}
I was hoping I could do something like this in the view:
#Html.EditorFor(model => model.DefaultBank, new { data-validate-parameter: bankId })
This way I could just use the same action for all of the Bank entries like:
[HttpGet]
public JsonResult CheckValidBank(string bankId)
{
bool result = BankExists(bankId);
return Json(result, JsonRequestBehavior.AllowGet);
}
Possible?
For just such a situation, I wrote a RemoteReusableAttribute, which may be helpful to you. Here is a link to it: Custom remote Validation in MVC 3
Since MVC uses the default model binder for this, just like a normal action method. You could take a FormsCollection as your parameter and lookup the value. However, I personally would find it much easier to just use several parameters to the function, unless you start having dozens of different parameters.
You could also write a custom model binder, that would translate the passed parameter to a generic one.
Consider encapsulating the logic, "BankExists" in this case into a ValidationAttribute (Data Annotations Validator). This allows other scenarios as well.
Then use a wrapper ActionResult like the one below, which lets you pass in any validator.
[HttpGet]
public ActionResult CheckRefundBank(string refundBank)
{
var validation = BankExistsAttribute();
return new RemoteValidationResult(validation, defaultBank);
}
Here is the code for the ActionResult that works generically with Validators.
using System.ComponentModel.DataAnnotations;
using System.Web.Mvc;
public class RemoteValidationResult : ActionResult
{
public RemoteValidationResult(ValidationAttribute validation, object value)
{
this.Validation = validation;
this.Value = value;
}
public ValidationAttribute Validation { get; set; }
public object Value { get; set; }
public override void ExecuteResult(ControllerContext context)
{
var json = new JsonResult();
json.JsonRequestBehavior = JsonRequestBehavior.AllowGet;
if (Validation.IsValid(Value))
{
json.Data = true;
}
else
{
json.Data = Validation.FormatErrorMessage(Value.ToString());
}
json.ExecuteResult(context);
}
}
As an extra enhancement consider creating a Controller Extension method to dry up your return call even more.
(couldn't think of a better title, sorry)
So I've got my layout page, on this page there is a searchbar + options. Choosing whatever, should take you through to the search page, with the results etc. Fairly standard. What I've done to get this working is to create a MasterModel class, with a SearchDataModel class member on it. This SearchDataModel contains the various parameters for the search (search term, what fields to search on etc).
I've then strongly typed my layout page to the MasterModel class, and using a Html.BeginForm... I've constructed the search form for it. However all the checkboxes relating to the fields aren't checked by default, even though the default value for all the fields is true (via a private getter/setter setup).
Yet when I submit the form to the SearchController, all the checkboxes are set to true. So I'm a bit confused as to why it knows they should be true, yet not set the checkboxes to be checked?
Putting breakpoints in key places seems to show that the model isn't insantiated on the get requests, only the post to the Search controller?
I may be going about this all wrong, so if so, pointers as to the right way always appreciated.
public class MasterModel {
public SearchDataModel SearchModel { get; set; }
}
public class SearchDataModel{
private bool _OnTags = true;
private bool _OnManufacturers = true;
private bool _OnCountries = true;
[Display(Name= "Tags")]
public bool OnTags {
get { return _OnTags; }
set { _OnTags = value; }
}
[Display(Name= "Manufacturers")]
public bool OnManufacturers {
get { return _OnManufacturers; }
set { _OnManufacturers = value; }
}
[Display(Name= "Countries")]
public bool OnCountries {
get { return _OnCountries; }
set { _OnCountries = value; }
}
[Required]
[Display(Name="Search Term:")]
public string SearchTerm { get; set; }
}
Then in the _layout page:
#Html.CheckBoxFor(m => m.SearchModel.OnTags, new { #class="ddlCheckbox", #id="inpCheckboxTag" })
#Html.LabelFor(m =>m.SearchModel.OnTags)
Make sure you return a MasterModel with initialized SearchModel from your views:
public ActionResult Index()
{
var model = new MasterModel
{
SearchModel = new SearchDataModel()
};
return View(model);
}
Another possibility to implement this functionality than strongly typing your master layout to a view model is yo use Html.Action as shown by Phil Haack in his blog post.