ASP.NET MVC: Implement client side validation with attribute without IClientValidatable - asp.net-mvc

How can I create a custom validation attribute with client side validation without implementing IClientValidatable?
How does System.ComponentModel.DataAnnotations.RequiredAttribute client side validate?
The reason to do this is because I'm using objects from classes in another project as models in my views and I don't want to add the System.Web.MVC reference to that project.
EDIT to add more information:
I know that IClientValidatable is used to add custom attributes to
the HTML to be used later by the unobtrusive validation.
I know I'll need to add the javascript code to made the validation in
the client.
What I don't know is how to use the information from the custom validation attribute to add the necessary attributes to the HTML for unobtrusive validation to work.
This is my custom validation attribute:
public class RequiredGuidAttribute : ValidationAttribute
{
public override bool IsValid(object value)
{
Guid? guidValue = value as Guid?;
if (guidValue == null)
return false;
return guidValue != Guid.Empty;
}
}
This is my property with the attribute applied:
[RequiredGuid(ErrorMessageResourceType = typeof(ClientOrderResources), ErrorMessageResourceName = "RequiredShippingMethod")]
public Guid ShippingMethodId
{
get { return GetProperty(ShippingMethodIdProperty); }
set { SetProperty(ShippingMethodIdProperty, value); }
}
And finally I'm rendering a hidden input for that property in the view using Html.HiddenFor.
Now, how can I get the error message from the attribute to apply it to the HTML? Should I do it my self using Reflection or there is a better way?
And then how can I tell Html.HiddenFor to use that information to add the necessary attributes to the HTML?

We had a similar problem. We have a model we use for our account creation that uses IClientValidatable on its custom attributes. However, we created a batch account creation process that sits outside of the website that we weren't able to reference System.Web.Mvc in. Because of this, when we called Validator.TryValidateObject, any custom validator that inherited from IClientValidatable was simply skipped. Here's what we were working with that was failing to validate outside of our website:
public class AgeValidatorAttribute : ValidationAttribute, IClientValidatable
{
public int AgeMin { get; set; }
public int AgeMax { get; set; }
public override bool IsValid(object value)
{
//run validation
}
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
var rule = new ModelClientValidationRule
{
ErrorMessage = ErrorMessageString,
ValidationType = "agevalidator"
};
rule.ValidationParameters["agemin"] = AgeMin;
rule.ValidationParameters["agemax"] = AgeMax;
yield return rule;
}
Removing System.Web.Mvc required us to also remove GetClientValidationRules and the IClientValidatable reference. In order to do this and still have client side validation, we had to create a new class:
public class AgeValidatorClientValidator : DataAnnotationsModelValidator<AgeValidatorAttribute>
{
private readonly string _errorMessage;
private readonly string _validationType;
public AgeValidatorClientValidator(ModelMetadata metadata, ControllerContext context, AgeValidatorAttribute attribute)
: base(metadata, context, attribute)
{
this._errorMessage = attribute.FormatErrorMessage(metadata.DisplayName);
this._validationType = "agevalidator";
}
public override IEnumerable<ModelClientValidationRule> GetClientValidationRules()
{
var rule = new ModelClientValidationRule
{
ErrorMessage = this._errorMessage,
ValidationType = this._validationType
};
rule.ValidationParameters["agemin"] = base.Attribute.AgeMin;
rule.ValidationParameters["agemax"] = base.Attribute.AgeMax;
yield return rule;
}
}
As you can see, it does essentially the same thing as it used to, it's just done using the DataAnnatotationsModelValidator rather than IClientValidatable. There's one more step we need to do to actually attach the DataAnnotationsModelValidator to the atttribute, and that's done in the Global.asax.cs Application_Start method
DataAnnotationsModelValidatorProvider.RegisterAdapter(
typeof(AgeValidatorAttribute), typeof(AgeValidatorClientValidator));
Now you can use this just as you would use a normal attribute:
[AgeValidator(AgeMax = 110, AgeMin = 18, ErrorMessage = "The member must be between 18 and 110 years old")]
public string DateOfBirth { get; set; }
I know this question is a year old, but I spent all day yesterday and half of today trying to figure this issue out. So I hope this helps somebody who runs into the same problem if OP hasn't figured the answer out yet.
Please note, I did not include any javascript in this writeup as it required no changes from the standard implementation of custom validation rules using jQuery.validate.

You can't have custom validation on the client unless you implement IClientValidatable. And for that you also need to add client script as well.
http://msdn.microsoft.com/en-us/vs2010trainingcourse_aspnetmvccustomvalidation_topic3.aspx

It is possible, i found this article on how to do it:
http://xhalent.wordpress.com/2011/05/12/custom-unobstrusive-jquery-validation-in-asp-net-mvc-3-using-dataannotationsmodelvalidatorprovider/
basically you have to create a DataAnnotationsModelValidator on your client an register it in Application_Start().
And don't forget that you still have to write the Javascript for client side validation.

Related

MVC Client validation rules not output when model property is excluded from view

I am performing custom validation on a model property. The property is a proxy for other parts of the model and therefore requires no explicit user input. Server-side validation is working correctly but no client-side rules are generated.
I have been able to successfully generate the client rules but only when 'referencing the property in the view' using TextBoxFor, CheckBoxFor (or perhaps more appropriately HiddenFor) on the target property. However this feels like a hack, since the property doesn't even have a setter, so the value is guaranteed to be discarded.
Is there any way to force ASP.NET MVC to generate the client validation rules for a specific property without it being used in the view?
Example Code
public class Model {
public bool Option1 { get; set; }
public bool Option2 { get; set; }
public bool Option3 { get; set; }
[CustomValidator(ErrorMessage = "Validation Failed!")]
public bool AtLeastOneSelected => Option1 != false || Option2 != false || Option3 != false;
}
public class CustomValidator : ValidationAttribute, IClientValidatable {
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context) {
// breakpoint below
return new List<ModelClientValidationRule>();
}
}
Example View
#Html.CheckBoxFor(m => m.Option1)
#Html.CheckBoxFor(m => m.Option2)
#Html.CheckBoxFor(m => m.Option3)
#Html.ValidationMessageFor(m => m.AtLeastOneSelected)
#*//Client rules will not be generated without this line*#
#*//#Html.TextBoxFor(m => m.AtLeastOneSelected)*#
Your sole problem here is that client-side validation doesn't work out of the box. This is because the validation client-side must be tied to a form field. There has to be something that triggers an valid/invalid determination, and without a form field you have nothing to do that with.
You can always write some custom client-side validation. Just check of your options and see if at least one is set, then you add/remove a message accordingly. You can check the jQuery Validation docs (what's behind client-side validation in MVC) for require_from_group. Seems like pretty much what you want in this particular example. You'll just have to add the rule manually.
$( "#myform" ).validate({
rules: {
Option1: {
require_from_group: [1, ".options"]
},
Option2: {
require_from_group: [1, ".options"]
},
Option3: {
require_from_group: [1, ".options"]
}
}
});
Then, you just need to add that class to each of your option fields.
I had a similar issue to yours in that I had to validate three inputs in addition to their own validation due to some silly UI requirements.
I did manage to make it work with all the built-in stuff and without any need for a hack.
I needed a ValidationAttribute
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Globalization;
using System.Web.Mvc;
namespace ValidationAttributes
{
public sealed class MyValidationRule : ValidationAttribute, IClientValidatable
{
public MyValidationRule()
: base("My validation rule's error message {0}")
{
// initialise vars here
}
public override bool IsValid(object value)
{
var valid = value == null;
// validate logic here
return valid;
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
yield return new ModelClientValidationMyValidationRule(FormatErrorMessage("SomeValueFor0ParamAbove"), MinimumAgeInYears);
}
}
}
... and a ModelClientValidationRule
using System.Globalization;
using System.Web.Mvc;
namespace ModelClientValidationRules
{
public class ModelClientValidationMyValidationRule : ModelClientValidationRule
{
public ModelClientValidationMyValidationRule(string errorMessage)
{
ErrorMessage = errorMessage;
ValidationType = "myvalidatorrule";
// add any params needed on the client side from the server side by using the following
ValidationParameters.Add("param_name", "param_value");
}
}
}
... and I needed to add a prop in my Model
[MyValidationRule]
public whatever SomeProperty { get; set; }
... and I needed to add the client side validation in the view
#Html.ValidationMessageFor(x => x.SomeProperty, "Validation message")
... and finally I needed to include some js in my scripts
jQuery.validator.addMethod("myvalidationrule", function (value, element, params) {
var valid = false;
// your validation logic goes here
// NB element will be undefined as the validator is not driven by an element
return valid;
});
jQuery.validator.unobtrusive.adapters.add("myvalidationrule", function (options) {
options.rules["myvalidationrule"] = options.params;
options.message["myvalidationrule"] = options.message;
});
I really hope this makes sense... :S

Uppercase attribute that converts the input to uppercase

I am working in MVC4 and want to define a model using an Uppercase attribute. The idea would be that the presence of the Uppercase attribute would cause the model value to be converted to uppercase when it arrived at the server.
At the moment I have the following code within the model:
[Required]
[Display(Name="Account Code")]
[StringValidation(RegExValidation.AccountCode, Uppercase=true)]
public string Account
{
get { return _account; }
set
{
if (value != null)
_account = value.ToUpper();
}
}
But what I would really like is this:
[Required]
[Display(Name="Account Code")]
[StringValidation(RegExValidation.AccountCode)]
[Uppercase]
public string Account { get; set; }
I think that I may need to create the Uppercase attribute as a ValidationAttribute to ensure it gets fired when the model hits the server. But that seems a bit wrong, as I'm not really validating the data. Is there a better way?
Also, is there any way to ensure the invocation order on the attributes? I really want to convert the data to uppercase before the custom StringValidation attribute fires, as this checks the case of the text in the regex pattern.
To add a bit of background to this, I want to reduce the need to add code to uppercase the data. The nirvana would be a single attribute, which updates the data on the way into the server, either in the model binding or validation stage. This attribute can then be referenced in the StringValidation attribute to amend the RegEx value used in its checks. I can also then lookup this attribute in a custom TextBoxFor helper method, such that I can add text-transform: uppercase so it looks correct on the client side.
Does anyone have any ideas out there?
I have managed to get this working, to a point, so here's my solution for others to appraise.
Once point to note was that the full solution couldn't be achieved because I couldn't get the Modelmetadata inside the StringValidation.IsValid() attribute. The particular issue I had here was that I could get the Metadata, however I could not get the PropertyName from it, only the DisplayName. There were multiple options out there, but the fact that some of my properties have the same DisplayName means that I couldn't be sure that the ProprtyName was the one I was actually validating.
Here's the code for the ValidationAttribute:
public class StringValidationAttribute : ValidationAttribute, IClientValidatable, IMetadataAware {
private bool _uppercase;
public StringValidationAttribute(bool uppercase = false) {
_uppercase = uppercase;
}
...
public void OnMetadataCreated(ModelMetadata metadata)
{
metadata.AdditionalValues["Uppercase"] = _uppercase;
}
}
I then created a new IModelBinder implementation:
public class StringBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
ValueProviderResult result = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
if (result == null)
return null;
if (bindingContext.ModelMetadata.AdditionalValues.ContainsKey("Uppercase")) {
if ((bool)bindingContext.ModelMetadata.AdditionalValues["Uppercase"]])
return result.AttemptedValue.ToUpper();
}
return result.AttemptedValue;
}
}
And registered that in myGlobal.asax file:
ModelBinders.Binders.Add(typeof(string), new StringBinder());
The code so far will cause any string input coming into MVC to be converted to Uppercase if it has StringValidationAttribute attached to it on the model, and where the uppercase indicator has been set.
Next, to achieve my desire of making the html forms be uppercase too, I implemented a new EditorTemplate named string.cshtml. In this view I added:
RouteValueDictionary htmlAttributes = new RouteValueDictionary();
if ((bool)ViewData.ModelMetadata.AdditionalValues["Uppercase"]) {
htmlAttributes.Add("class", "Uppercase");
}
#Html.TextBox("", Model, htmlAttributes)
With the CSS as;
.Uppercase {
text-transform: uppercase;
}
Hope this post helps some others out there.
For Web API purpose it is better to convert the incoming json to uppercase or lowercase.
public class ToUpperCase : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(string);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
return reader.Value.ToString().ToUpper();
}
}
[Display(Name = "PNR NAME")]
[JsonConverter(typeof(Annotations.ToUpperCase))]
public string PNR { get; set; }
OR Globally;
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
//.......... others
JsonMediaTypeFormatter jsonFormatter = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
JsonSerializerSettings jSettings = new Newtonsoft.Json.JsonSerializerSettings();
jSettings.Converters.Add(new UpperCaseStringConverter());
jsonFormatter.SerializerSettings = jSettings;
}
You're right, ValidationAttribute is not the right fit. It seems like doing this at the Model Binding stage would be a better idea. See this article for a detailed explanation of how to customize this behavior.
Based on the information provided there, I believe you should be able to create an attribute based on CustomModelBinderAttribute like this:
[AttributeUsage(AttributeTargets.Property)]
public class UppercaseAttribute : CustomModelBinderAttribute
{
public override IModelBinder GetBinder()
{
return new UppercaseModelBinder();
}
private class UppercaseModelBinder : DefaultModelBinder
{
public override object BindModel(
ControllerContext controllerContext,
ModelBindingContext bindingContext)
{
var value = base.BindModel(controllerContext, bindingContext);
var strValue = value as string;
if (strValue == null)
return value;
return strValue.ToUpperInvariant();
}
}
}
I have not tested this. Let me know if it works or not.
NOTE:
I'm adding on to this post because until I discovered the approach I now use, I read this and tried all above unsuccessfully.
I generally use a two part process when dealing with forcing text data to be formatted as uppercase. 1. at the view and 2. at the controller
At the view layer so that the user knows data is going to be used in the uppercase form. This can be down through htmlAttributes used in the EditorFor HTML helper.
#HTML.EditorFor(model => model.Access_Code, new { htmlAttributes = new Style= "text-transform:uppercase"}})
Now this only forces the data seen and entered by the user to uppercase and not the data sent to the server. To do that requires some code in the associated method in the controller.
I add the ToUpper() method to the target attribute of the object being passed back to the contoller. Here is hypothetical example showing this.
public ActionResult verify(int? id)
{
var userData = db.user.Where (i=> i.userID == id).Single();
userData.Access_Code = userData.Access_Code.ToUpper();
...
}

custom validation attribute not working on client, just on server

I am trying to implement custom attribute validation, similar to one demonstrated here in ScottGu's blog:
http://weblogs.asp.net/scottgu/archive/2010/01/15/asp-net-mvc-2-model-validation.aspx
I have this custom validator attribute for Email:
public class EmailAttribute : RegularExpressionAttribute
{
public EmailAttribute() :
base("^[A-Za-z0-9](([_\\.\\-]?[a-zA-Z0-9]+)*)#([A-Za-z0-9]+)(([\\.\\-]?[a-zA-Z0-9]+)*)\\.([A-Za-z]{2,})$") { }
}
My class uses it like this:
[Required(ErrorMessage = ValidationCiM.MsgObaveznoPolje)]
[Email(ErrorMessage = ValidationCiM.MsgMailNeispravan)]
[StringLength(ValidationCiM.LenSrednjePolje, ErrorMessage = ValidationCiM.MsgSrednjePolje)]
public string Mail { get; set; }
and it all works well on server side, model is validated ok, and everything. But client side validation does not activate for this the second attribute, it works for Required, and it also works for StringLength but not for Email.
i have tried including both jquery and Microsoft ajax scripts, but there seems to be no difference.
In ScottGu's blog, he states that the custom validation if implemented like this should work without the need to add custom script.
Any ideas please?
Use IClientValidatable in ASP.NET MVC 3:
public class EmailAttribute : RegularExpressionAttribute, IClientValidatable
{
public EmailAttribute()
:base(#"^([a-zA-Z0-9_\-\.]+)#((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([a-zA-Z0-9\-]+\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})$")
{
}
public System.Collections.Generic.IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
var rule = new ModelClientValidationRegexRule(this.ErrorMessageString, base.Pattern);
return new[] { rule };
}
}
What you actually needed to do was this (on application start):
DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(EmailAttribute), typeof(RegularExpressionAttributeAdapter));
It hooks up the client validation to your custom attribute.

How to write custom client-side jQuery validation in ASP.NET MVC 2 RC?

I've read Phil Haack's post on custom client-side validation in ASP.NET MVC 2. I want to do the same thing but with the jQuery adapter and using ASP.NET MVC 2 RC (as opposed to MVC 2 Beta that the post uses). Has anyone been able to figure how to do this?
I specially want to implement the password matching validation (i.e. password & confirm password must match). The ASP.NET MVC 2 RC VS.NET project template does show how to implement that on the server-side (using the PropertiesMustMatchAttribute) but not on the client-side.
I assume you already followed Phil Haack's instructions here http://haacked.com/archive/2009/11/19/aspnetmvc2-custom-validation.aspx) on how to get custom validation working with MS AJAX client validation. To get it to work with jQuery, you'll need to modify the MicrosoftMvcJQueryValidation.js file:
In the __MVC_CreateRulesForField(validationField) function, you'll need to add a case statement. Continuing Phil's example, you'll need to add:
case "price":
__MVC_ApplyValidator_Price(rulesObj, thisRule.ValidationParameters["min"]);
break;
You'll then need to create the __MVC_ApplyValidator_Price function:
function __MVC_ApplyValidator_Price(object, value) {
// min is what jQuery Validate uses to validate for minimum values
object["min"] = value;
}
That should be enough to get Phil's example working.
Now, regarding your PropertiesMustMatchAttribute validation, it doesn't look like MVC generates the client-side json validation definition for attributes that decorate classes. Since PropertiesMustMatchAttribute must be used on the model (and not the property), I can't figure out how to make it trigger client-side validation. Instead, I took a different approach. I created a dummy validation attribute who's IsValid() overload always returns true, and used this attribute on a property. This is just a dummy attribute that will delegate the validation logic to jQuery validator's equalTo function. Here's the dummy attribute:
public class PropertiesMustMatchClientTriggerAttribute : ValidationAttribute
{
public string MatchProperty { get; set; }
public PropertiesMustMatchClientTriggerAttribute(string matchProperty)
{
MatchProperty = matchProperty;
ErrorMessage = "{0} doesn't match {1}.";
}
public override bool IsValid(object value)
{
return true;
}
public override string FormatErrorMessage(string name)
{
return String.Format(CultureInfo.CurrentCulture, ErrorMessageString, name, MatchProperty);
}
}
Here is the custom validator:
public class PropertiesMustMatchClientTriggerValidator : DataAnnotationsModelValidator<PropertiesMustMatchClientTriggerAttribute>
{
private string _message;
private string _matchProperty;
public PropertiesMustMatchClientTriggerValidator(ModelMetadata metaData, ControllerContext context, PropertiesMustMatchClientTriggerAttribute attribute)
: base(metaData, context, attribute)
{
_message = attribute.FormatErrorMessage(metaData.DisplayName);
_matchProperty = attribute.MatchProperty;
}
public override IEnumerable<ModelClientValidationRule> GetClientValidationRules()
{
var rule = new ModelClientValidationRule
{
ErrorMessage = _message,
ValidationType = "equalTo"
};
rule.ValidationParameters.Add("matchField", _matchProperty);
return new[] { rule };
}
}
the above custom validator needs to be registered in Application_Start() per Phil's blog:
DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(PropertiesMustMatchClientTriggerAttribute), typeof(PropertiesMustMatchClientTriggerValidator));
Finally, you need to modify the MicrosoftMvcJQueryValidation.js file:
Add the following case statement to __MVC_CreateRulesForField:
case "equalTo":
__MVC_ApplyValidator_EqualTo(rulesObj, thisRule.ValidationParameters["matchField"]);
break;
add this function:
function __MVC_ApplyValidator_EqualTo(object, elemId) {
object["equalTo"] = document.getElementById(elemId);
}
Now you need to attach the dummy validation attribute to a property:
[PropertiesMustMatchClientTrigger("Password")]
public string ConfirmPassword { get; set; }
That should do it.
Creating this dummy attribute is a bit ugly, so I hope someone can come up with a more elegant solution.
Here's how to add a custom jQuery validation:
$.validator.addMethod("noSpaces", function(value, element) {
if ($(element).val().indexOf(" ") >= 0) {
return false;
} else {
return true;
}
}, "Value must not contain spaces");
I know this is an old post, and you are/were using MVC2, but MVC3 now comes with the CompareAttribute, which can be used for your use case of password confirmation matching.
Source: http://www.nickriggs.com/posts/asp-net-mvc-3-data-annotations-provide-property-level-contingent-validation/

How to create custom validation attribute for MVC

I'd like to create a custom validation attribute for MVC2 for an email address that doesn't inherit from RegularExpressionAttribute but that can be used in client validation. Can anyone point me in the right direction?
I tried something as simple as this:
[AttributeUsage( AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false )]
public class EmailAddressAttribute : RegularExpressionAttribute
{
public EmailAddressAttribute( )
: base( Validation.EmailAddressRegex ) { }
}
but it doesn't seem to work for the client. However, if I use RegularExpression(Validation.EmailAddressRegex)] it seems to work fine.
You need to register an adapter for the new attribute in order to enable client side validation.
Since the RegularExpressionAttribute already has an adapter, which is RegularExpressionAttributeAdapter, all you have to do is reuse it.
Use a static constructor to keep all the necessary code within the same class.
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false)]
public class EmailAddressAttribute : RegularExpressionAttribute
{
private const string pattern = #"^\w+([-+.]*[\w-]+)*#(\w+([-.]?\w+)){1,}\.\w{2,4}$";
static EmailAddressAttribute()
{
// necessary to enable client side validation
DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(EmailAddressAttribute), typeof(RegularExpressionAttributeAdapter));
}
public EmailAddressAttribute() : base(pattern)
{
}
}
For more information checkout this post explaining the complete process.
http://haacked.com/archive/2009/11/19/aspnetmvc2-custom-validation.aspx
The CustomValidationAttribute Class MSDN page has a few examples on it now. The Phil Haacked post is out of date.
Look at the universal Dependent Property Validator in this article
Have you tried using Data Annotations?
This is my Annotations project
using System.ComponentModel.DataAnnotations;
public class IsEmailAddressAttribute : ValidationAttribute
{
public override bool IsValid(object value)
{
//do some checking on 'value' here
return true;
}
}
This is in my Models project
namespace Models
{
public class ContactFormViewModel : ValidationAttributes
{
[Required(ErrorMessage = "Please provide a short message")]
public string Message { get; set; }
}
}
This is my controller
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult ContactUs(ContactFormViewModel formViewModel)
{
if (ModelState.IsValid)
{
RedirectToAction("ContactSuccess");
}
return View(formViewModel);
}
You'll need to google DataAnnotations as you need to grab the project and compile it. I'd do it but I need to get outta here for a long w/end.
Hope this helps.
EDIT
Found this as a quick google.

Resources