I would like to use the asp.net mvc client / server validation coming from a configurable source.
Some like a .config file where I could place infos:
Type, Member, ValidationType
<validations>
<add type="Customer" member="Name" validator="Required" />
<add type="Customer" member="Age" validator="Range" mimimum="18" maximum="100" />
</validations>
With this plan, would be possible to enable/disable validations.
Any idea?
If you need this, consider some more advanced validation framework, for example Enterprise Library Validation Block.
If you want to do it yourself, i would suggest creating custom attribute iniherited from ValidationAttribute like this (partly pseudocode, i am sure you get the idea)
public class ConfigurableValidationAttribute: ValidationAttribute
{
public override bool IsValid(object value)
{
string objectType = value.GetType().FullName;
string objectName = GetMyObjectName(value); // interface? reflection?
var validationRules = GetValidationRulesFor(objectType, name); // from your configuration
foreach (var rule in validationRules)
{
ValidationAttribute attr = null;
switch (rule.ValidatorName)
{
case "Required": attr = new RequiredAttribute();
case "StringLength": attr = // you get the idea
}
if (!attr.IsValid(value)) return false;
}
return true;
}
}
Related
I am using IStringLocalizer approach to localize my Blazor app as discussed here.
Injecting the IStringLocalizer on razor pages works great. I also need this to localize some services - whether scoped or even singleton services.
Using constructor injection to inject my IStringLocalizer service into the service works. However, when users change the language via UI, the service (whether singleton or scoped) keeps the initial IStringLocalizer - i.e. the one with the original language used when starting the app, not the updated language selected by the user.
What is the suggested approach to retrieve the updated IStringLocalizer from code?
EDIT
To prevent more details, here is some piece of code.
First, I add a Resources folder and create there a default LocaleResources.resx (with public modifiers) and a LocaleResources.fr.resx file, which contain the key-value pairs for each language.
Supported cultures are defined in the appsettings.json file as
"Cultures": {
"en-US": "English",
"fr": "Français (Suisse)",
...
}
In startup, I register the Resources folder and the supported cultures :
public void ConfigureServices(IServiceCollection services {
...
services.AddLocalization(options => options.ResourcesPath = "Resources");
...
services.AddSingleton<MySingletonService>();
services.AddScoped<MyScopedService>();
}
// --- helper method to retrieve the Cultures from appsettings.json
protected RequestLocalizationOptions GetLocalizationOptions() {
var cultures = Configuration.GetSection("Cultures")
.GetChildren().ToDictionary(x => x.Key, x => x.Value);
var supportedCultures = cultures.Keys.ToArray();
var localizationOptions = new RequestLocalizationOptions()
.AddSupportedCultures(supportedCultures)
.AddSupportedUICultures(supportedCultures);
return localizationOptions;
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) {
...
app.UseRequestLocalization(GetLocalizationOptions());
...
app.UseEndpoints(endpoints => {
endpoints.MapControllers();
endpoints.MapBlazorHub();
endpoints.MapFallbackToPage("/_Host");
});
}
I created an empty LocaleResources.razor control at the root of the project (this is a trick used to inject a single resource file to all components).
I included a routing controller to change language :
[Route("[controller]/[action]")]
public class CultureController : Controller {
public IActionResult SetCulture(string culture, string redirectUri) {
if (culture != null) {
HttpContext.Response.Cookies.Append(
CookieRequestCultureProvider.DefaultCookieName,
CookieRequestCultureProvider.MakeCookieValue(
new RequestCulture(culture)));
}
return LocalRedirect(redirectUri);
}
}
And the language UI switcher looks like this (I use SyncFusion control here, but it could be any lookup actually, that shouldn't really matter)
#inject NavigationManager NavigationManager
#inject IConfiguration Configuration
<SfComboBox TValue="string" TItem="Tuple<string, string>" Placeholder="Select language" DataSource="#Cultures"
#bind-Value="selectedCulture" CssClass="lan-switch" Width="80%">
<ComboBoxFieldSettings Text="Item2" Value="Item1"></ComboBoxFieldSettings>
</SfComboBox>
<style>
.lan-switch {
margin-left: 5%;
}
</style>
#code {
string _activeCulture = System.Threading.Thread.CurrentThread.CurrentCulture.Name;
private string selectedCulture {
get => _activeCulture;
set {
_activeCulture = value;
SelectionChanged(value);
}
}
List<Tuple<string, string>> Cultures;
protected override void OnInitialized() {
var cultures = Configuration.GetSection("Cultures")
.GetChildren().ToDictionary(x => x.Key, x => x.Value);
Cultures = cultures.Select(p => Tuple.Create<string, string>(p.Key, p.Value)).ToList();
}
protected override void OnAfterRender(bool firstRender) {
if (firstRender && selectedCulture != AgendaSettings.SelectedLanguage) {
selectedCulture = AgendaSettings.SelectedLanguage;
}
}
private void SelectionChanged(string culture) {
if (string.IsNullOrWhiteSpace(culture)) {
return;
}
AgendaSettings.SelectedLanguage = culture;
var uri = new Uri(NavigationManager.Uri)
.GetComponents(UriComponents.PathAndQuery, UriFormat.Unescaped);
var query = $"?culture={Uri.EscapeDataString(culture)}&" +
$"redirectUri={Uri.EscapeDataString(uri)}";
NavigationManager.NavigateTo("/Culture/SetCulture" + query, forceLoad: true);
}
}
Finally, to the injection. I inject the IStringLocalizer to pages as follows and it works perfectly fine on razor controls:
#inject IStringLocalizer<LocaleResources> _loc
<h2>#_loc["hello world"]</h2>
Above, when I change language, the page displays the value in the corresponding resource file.
Now, to services: the MySingletonService and MyScopedService are registered at startup. They both have a constructor like
protected IStringLocalizer<LocaleResources> _loc;
public MySingletonService(IStringLocalizer<LocaleResources> loc) {
_loc = loc;
}
public void someMethod() {
Console.WriteLine(_loc["hello world"])
}
I run someMethod on a timer. Strangely, when I break on the above line, the result seems to oscillate : once it returns the default language's value, once the localized one...!
The answer to my question was: your code is correct!
The reason, I found out, is that I use a Scoped service that is started on the default App's start page:
protected async override Task OnAfterRenderAsync(bool firstRender) {
if (firstRender) {
MyScopedService.StartTimer();
}
await base.OnAfterRenderAsync(firstRender);
}
When users change language, the whole page is refreshed and a new instance of the scoped service is created and timer started. As my service did not implement IDisposable, the timer was not actually stopped.
So 2 solutions here:
use singleton services
make servcie disposable and ensure tasks are cancelled when service is disposed of.
I want to create complicate form which will create structure of wizard, partial steps form, validation and submit. This structure have to use model attributes annotations to create one structure object over the model. So after reflection I have model and one other class with structure description. All properties within are strings with Fields which I have to pass on 'asp-for' tag helper. So part of the code is:
#foreach(var field in #group.Fields) {
<div class="col-12 col-md-6 col-lg-4">
<div class="form-group md-form md-outline">
<label asp-for="#field.Name" class="control-label"></label>
<input asp-for="#field.Name" class="form-control" />
<span asp-validation-for="#field.Name" class="text-danger"></span>
</div>
</div>
}
This is nor working because tag helper expect expression and generate wrong values which are not expected from me. The value in #field.Name is 'PostAddress.Street1'. If I replace all of "#field.Name" with "PostAddress.Street1" everything work properly how I expected.
It looks small issue but I'm trying many things and reading some theads in forums but didn't find the answer. What I tried:
Experiment 1
Tried to inherit InputTagHelper class from dotnet library and override property For but without success. It changed ModelExpression but no changes in interface. May be base class have some logic to skip this changed object or is not correct generated:
[HtmlAttributeName("asp-for")]
public new ModelExpression For
{
get
{
return base.For;
}
set
{
ModelExpression me = value;
if (value.Model != null)
{
var viewData = this.ViewContext.ViewData as ViewDataDictionary<AbnServiceModel>;
me = ModelExpressionProvider.CreateModelExpression<AbnServiceModel, string>(viewData, model => model.PostAddress.Street1);
}
base.For = me;
}
}
=================================================
2. Experiment 2
Try to get original implementation from .NET Core code and made some modification in code to fix the issue. But the code and dependencies with internal libraries were very complicated and I reject this idea.
Expiriment 3
Using HTML helpers
#Html.Label(#field.Name, "", new{ #class="control-label" })
#Html.Editor(#field.Name, new { htmlAttributes = new{ #class="form-control" } })
#Html.ValidationMessage(#field.Name,"",new { htmlAttributes = new{ #class="text-danger" } })
It render components correct into the browser but client side validation using jquery.validate.unobtrusive.js is not working. Not sure why.
Expiriment 4
Using HTML helpers:
#Html.LabelFor(m=>m.PostAddress.Street1, new{ #class="control-label" })
#Html.EditorFor(m=>m.PostAddress.Street1, new { htmlAttributes = new{ #class="form-control" } })
#Html.ValidationMessageFor(m=>m.PostAddress.Street1,"",new { htmlAttributes = new{ #class="text-danger" } })
The validation is working but class weren't applied well, may be my mistake. But other problem here is that I'm not using expression which is string which can get from model object. Also It doesn't catch all logic which is included in asp-for tag helper.
Experiment 5
Tried to create my own tag helper and using generator to create the content html. But this means that I have to implement all logic like helper in dotnet core to have all functionality which is same like Expiriment 2
So I didn't find good solution of this "simple" problem and lost some days to investigate and doing some code to resolve it. I'm surprised that no way to pass string variable with property name and it wouldn't work.
Can someone help me to fix this problem with real example? I didn't find the answer in all posts. I want to have all logic from asp-for tag helper but use variable to pass the expression. It cab be and tricky, just want to have some resolution to continue with my project.
Thank you
I resolved my issue.
Created one helper method:
public static class CommonHelperMethods
{
public static ModelExplorer GetModelExplorer(this ModelExplorer container, string field, IModelMetadataProvider modelMetadataProvider = null)
{
ModelExplorer result = container;
var fields = field.Split(".").ToList();
var match = Regex.Match(fields[0], #"(.+)\[(\d)+\]");
if (!match.Success)
{
fields.ForEach(x =>
{
result = result?.GetExplorerForProperty(x) ?? result;
});
}
else
{ //List have to create own Property browser
string proName = match.Groups[1].Value;
int idx = Convert.ToInt32(match.Groups[2].Value);
var model = ((IList)result?.GetExplorerForProperty(proName).Model)[idx];
var targetProperty = model.GetType().GetProperty(fields[1]);
var targetValueModel = targetProperty.GetValue(model);
var elementMetadata = modelMetadataProvider.GetMetadataForProperty(model.GetType(), fields[1]);
return new ModelExplorer(modelMetadataProvider, container, elementMetadata, targetValueModel);
}
return result;
}
}
And just override the tag helper class with this:
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.TagHelpers;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Razor.TagHelpers;
namespace GetTaxSolutions.Web.Infrastructure.TagHelpers
{
[HtmlTargetElement("input", Attributes = ForAttributeName, TagStructure = TagStructure.WithoutEndTag)]
public class InputTextGtTaxHelper : InputTagHelper
{
private const string ForAttributeName = "asp-for";
[HtmlAttributeName("not-exp")]
public bool NotExpression { get; set; } = false;
[HtmlAttributeName(ForAttributeName)]
public new ModelExpression For
{
get
{
return base.For;
}
set
{
ModelExpression me = value;
if (NotExpression)
{
var modelExplorertmp = value.ModelExplorer.Container.GetModelExplorer(value.Model.ToString(), ModelMetadataProvider);
var modelExplorer = new ModelExplorer(ModelMetadataProvider, value.ModelExplorer.Container, modelExplorertmp.Metadata, modelExplorertmp.Model);
me = new ModelExpression(value.Model.ToString(), modelExplorer);
}
base.For = me;
}
}
public IModelExpressionProvider ModelExpressionProvider { get; }
public IModelMetadataProvider ModelMetadataProvider { get; }
public IActionContextAccessor Accessor { get; }
public InputTextGtTaxHelper(
IHtmlGenerator generator,
IModelExpressionProvider modelExpressionProvider,
IModelMetadataProvider modelMetaDataProvider) : base(generator)
{
ModelExpressionProvider = modelExpressionProvider;
ModelMetadataProvider = modelMetaDataProvider;
}
}
}
Also should skip original class in tag helper registration:
#addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
#removeTagHelper Microsoft.AspNetCore.Mvc.TagHelpers.InputTagHelper, Microsoft.AspNetCore.Mvc.TagHelpers
#removeTagHelper Microsoft.AspNetCore.Mvc.TagHelpers.LabelTagHelper, Microsoft.AspNetCore.Mvc.TagHelpers
#removeTagHelper Microsoft.AspNetCore.Mvc.TagHelpers.ValidationMessageTagHelper, Microsoft.AspNetCore.Mvc.TagHelpers
#removeTagHelper Microsoft.AspNetCore.Mvc.TagHelpers.SelectTagHelper, Microsoft.AspNetCore.Mvc.TagHelpers
#addTagHelper GetTaxSolutions.Web.Infrastructure.TagHelpers.*, GetTaxSolutions.Web
And when use model in expression just have to pass attribute 'no-exp' on input elements. Otherwise will work like original tag helper.
<input not-exp="true" asp-for="#field.Name" class="form-control" />
Also you have to do same with label, select and other used tag helpers which you want to support this way of model passing.
Is there a way how to perform a "database" check through the Client Side Validation in MVC?
I have the following class
public class EmailCheck : ValidationAttribute,IClientValidatable
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
string sErrorMessage = "Email already exists";
return new ValidationResult(sErrorMessage);
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
ModelClientValidationRule validationRule = new ModelClientValidationRule();
validationRule.ValidationType = "checkemail";
validationRule.ErrorMessage = "Invalid Email Format";
validationRule.ValidationParameters.Add("param", "");
return new List<ModelClientValidationRule> { validationRule };
}
}
I would like the Client Side Validation to call the "IsValid" method onkeyup/lost focus as well and not just do the regular javascript checks in the "checkemail" javascript function.
The javascript function i have is the following:
//Validation for Well-Formed Email
jQuery.validator.addMethod("checkemail",
function (value, element, param) {
var emailReg = /^([\w-\.]+#([\w-]+\.)+[\w-]{2,4})?$/;
if (!emailReg.test(value))
return false;
return true;
});
jQuery.validator.unobtrusive.adapters.add("checkemail", ["param"], function (options) {
options.rules["checkemail"] = options.params.param;
options.messages["checkemail"] = options.message;
});
I would appreciate if anyone could guide me in the right direction or provide a tutorial of something similar.
Thanks
In your model try to use the "Remote" attribute
http://msdn.microsoft.com/en-us/library/gg508808%28v=vs.98%29.aspx
Rusty is correct, you need to use the Remote attribute in your model
Here is another example with complete detail only for email validation in MVC
MVC model validation for email from database in asp.net
Assume this model:
Public Class Detail
...
<DisplayName("Custom DisplayName")>
<Required(ErrorMessage:="Custom ErrorMessage")>
Public Property PercentChange As Integer
...
end class
and the view:
#Html.TextBoxFor(Function(m) m.PercentChange)
will proceed this html:
<input data-val="true"
data-val-number="The field 'Custom DisplayName' must be a number."
data-val-required="Custom ErrorMessage"
id="PercentChange"
name="PercentChange" type="text" value="0" />
I want to customize the data-val-number error message which I guess has generated because PercentChange is an Integer. I was looking for such an attribute to change it, range or whatever related does not work.
I know there is a chance in editing unobtrusive's js file itself or override it in client side. I want to change data-val-number's error message just like others in server side.
You can override the message by supplying the data-val-number attribute yourself when rendering the field. This overrides the default message. This works at least with MVC 4.
#Html.EditorFor(model => model.MyNumberField, new { data_val_number="Supply an integer, dude!" })
Remember that you have to use underscore in the attribute name for Razor to accept your attribute.
What you have to do is:
Add the following code inside Application_Start() in Global.asax:
ClientDataTypeModelValidatorProvider.ResourceClassKey = "Messages";
DefaultModelBinder.ResourceClassKey = "Messages";
Right click your ASP.NET MVC project in VS. Select Add => Add ASP.NET Folder => App_GlobalResources.
Add a .resx file called Messages.resx in that folder.
Add these string resources in the .resx file:
FieldMustBeDate The field {0} must be a date.
FieldMustBeNumeric The field {0} must be a number.
PropertyValueInvalid The value '{0}' is not valid for {1}.
PropertyValueRequired A value is required.
Change the FieldMustBeNumeric value as you want... :)
You're done.
Check this post for more details:
Localizing Default Error Messages in ASP.NET MVC and WebForms
This is not gonna be easy. The default message is stored as an embedded resource into the System.Web.Mvc assembly and the method that is fetching is a private static method of an internal sealed inner class (System.Web.Mvc.ClientDataTypeModelValidatorProvider+NumericModelValidator.MakeErrorString). It's as if the guy at Microsoft coding this was hiding a top secret :-)
You may take a look at the following blog post which describes a possible solution. You basically need to replace the existing ClientDataTypeModelValidatorProvider with a custom one.
If you don't like the hardcore coding that you will need to do you could also replace this integer value inside your view model with a string and have a custom validation attribute on it which would do the parsing and provide a custom error message (which could even be localized).
As an alternate way around this, I applied a RegularExpression attribute to catch the invalid entry and set my message there:
[RegularExpression(#"[0-9]*$", ErrorMessage = "Please enter a valid number ")]
This slightly a hack but this seemed preferable to the complexity the other solutions presented, at least in my particular situation.
EDIT: This worked well in MVC3 but it seems that there may well be better solutions for MVC4+.
From this book on MVC 3 that I have. All you have to do is this:
public class ClientNumberValidatorProvider : ClientDataTypeModelValidatorProvider
{
public override IEnumerable<ModelValidator> GetValidators(ModelMetadata metadata,
ControllerContext context)
{
bool isNumericField = base.GetValidators(metadata, context).Any();
if (isNumericField)
yield return new ClientSideNumberValidator(metadata, context);
}
}
public class ClientSideNumberValidator : ModelValidator
{
public ClientSideNumberValidator(ModelMetadata metadata,
ControllerContext controllerContext) : base(metadata, controllerContext) { }
public override IEnumerable<ModelValidationResult> Validate(object container)
{
yield break; // Do nothing for server-side validation
}
public override IEnumerable<ModelClientValidationRule> GetClientValidationRules()
{
yield return new ModelClientValidationRule {
ValidationType = "number",
ErrorMessage = string.Format(CultureInfo.CurrentCulture,
ValidationMessages.MustBeNumber,
Metadata.GetDisplayName())
};
}
}
protected void Application_Start()
{
// Leave the rest of this method unchanged
var existingProvider = ModelValidatorProviders.Providers
.Single(x => x is ClientDataTypeModelValidatorProvider);
ModelValidatorProviders.Providers.Remove(existingProvider);
ModelValidatorProviders.Providers.Add(new ClientNumberValidatorProvider());
}
Notice how the ErrorMessage is yielded, you specify the current culture and the localized message is extracted from the ValidationMessages(here be culture specifics).resx resource file. If you don't need that, just replace it with your own message.
Here is another solution which changes the message client side without changed MVC3 source. Full details in this blog post:
https://greenicicle.wordpress.com/2011/02/28/fixing-non-localizable-validation-messages-with-javascript/
In short what you need to do is include the following script after jQuery validation is loaded plus the appropriate localisation file.
(function ($) {
// Walk through the adapters that connect unobstrusive validation to jQuery.validate.
// Look for all adapters that perform number validation
$.each($.validator.unobtrusive.adapters, function () {
if (this.name === "number") {
// Get the method called by the adapter, and replace it with one
// that changes the message to the jQuery.validate default message
// that can be globalized. If that string contains a {0} placeholder,
// it is replaced by the field name.
var baseAdapt = this.adapt;
this.adapt = function (options) {
var fieldName = new RegExp("The field (.+) must be a number").exec(options.message)[1];
options.message = $.validator.format($.validator.messages.number, fieldName);
baseAdapt(options);
};
}
});
} (jQuery));
You can set ResourceKey of ClientDataTypeModelValidatorProvider class to name of a global resource that contains FieldMustBeNumeric key to replace MVC validation error message of number with your custom message. Also key of date validation error message is FieldMustBeDate.
ClientDataTypeModelValidatorProvider.ResourceKey="MyResources"; // MyResource is my global resource
See here for more details on how to add the MyResources.resx file to your project:
Here is another solution in pure js that works if you want to specify messages globally not custom messages for each item.
The key is that validation messages are set using jquery.validation.unobtrusive.js using the data-val-xxx attribute on each element, so all you have to do is to replace those messages before the library uses them, it is a bit dirty but I just wanted to get the work done and fast, so here it goes for number type validation:
$('[data-val-number]').each(function () {
var el = $(this);
var orig = el.data('val-number');
var fieldName = orig.replace('The field ', '');
fieldName = fieldName.replace(' must be a number.', '');
el.attr('data-val-number', fieldName + ' باید عددی باشد')
});
the good thing is that it does not require compiling and you can extend it easily later, not robust though, but fast.
Check this out too:
The Complete Guide To Validation In ASP.NET MVC 3 - Part 2
Main parts of the article follow (copy-pasted).
There are four distinct parts to creating a fully functional custom validator that works on both the client and the server. First we subclass ValidationAttribute and add our server side validation logic. Next we implement IClientValidatable on our attribute to allow HTML5 data-* attributes to be passed to the client. Thirdly, we write a custom JavaScript function that performs validation on the client. Finally, we create an adapter to transform the HTML5 attributes into a format that our custom function can understand. Whilst this sounds like a lot of work, once you get started you will find it relatively straightforward.
Subclassing ValidationAttribute
In this example, we are going to write a NotEqualTo validator that simply checks that the value of one property does not equal the value of another.
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public sealed class NotEqualToAttribute : ValidationAttribute
{
private const string DefaultErrorMessage = "{0} cannot be the same as {1}.";
public string OtherProperty { get; private set; }
public NotEqualToAttribute(string otherProperty)
: base(DefaultErrorMessage)
{
if (string.IsNullOrEmpty(otherProperty))
{
throw new ArgumentNullException("otherProperty");
}
OtherProperty = otherProperty;
}
public override string FormatErrorMessage(string name)
{
return string.Format(ErrorMessageString, name, OtherProperty);
}
protected override ValidationResult IsValid(object value,
ValidationContext validationContext)
{
if (value != null)
{
var otherProperty = validationContext.ObjectInstance.GetType()
.GetProperty(OtherProperty);
var otherPropertyValue = otherProperty
.GetValue(validationContext.ObjectInstance, null);
if (value.Equals(otherPropertyValue))
{
return new ValidationResult(
FormatErrorMessage(validationContext.DisplayName));
}
}
return ValidationResult.Success;
}
}
Add the new attribute to the password property of the RegisterModel and run the application.
[Required]
[DataType(DataType.Password)]
[Display(Name = "Password")]
[NotEqualTo("UserName")]
public string Password { get; set; }
...
Implementing IClientValidatable
ASP.NET MVC 2 had a mechanism for adding client side validation but it was not very pretty. Thankfully in MVC 3, things have improved and the process is now fairly trivial and thankfully does not involve changing the Global.asax as in the previous version.
The first step is for your custom validation attribute to implement IClientValidatable. This is a simple, one method interface:
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(
ModelMetadata metadata,
ControllerContext context)
{
var clientValidationRule = new ModelClientValidationRule()
{
ErrorMessage = FormatErrorMessage(metadata.GetDisplayName()),
ValidationType = "notequalto"
};
clientValidationRule.ValidationParameters.Add("otherproperty", OtherProperty);
return new[] { clientValidationRule };
}
If you run the application now and view source, you will see that the password input html now contains your notequalto data attributes:
<div class="editor-field">
<input data-val="true" data-val-notequalto="Password cannot be the same as UserName."
data-val-notequalto-otherproperty="UserName"
data-val-regex="Weak password detected."
data-val-regex-pattern="^(?!password$)(?!12345$).*"
data-val-required="The Password field is required."
id="Password" name="Password" type="password" />
<span class="hint">Enter your password here</span>
<span class="field-validation-valid" data-valmsg-for="Password"
data-valmsg-replace="true"></span>
</div>
Creating a custom jQuery validate function
All of this code is best to be placed in a separate JavaScript file.
(function ($) {
$.validator.addMethod("notequalto", function (value, element, params) {
if (!this.optional(element)) {
var otherProp = $('#' + params);
return (otherProp.val() !=
}
return true;
});
$.validator.unobtrusive.adapters.addSingleVal("notequalto", "otherproperty");
}(jQuery));
Depending on your validation requirements, you may find that the jquery.validate library already has the code that you need for the validation itself. There are lots of validators in jquery.validate that have not been implemented or mapped to data annotations, so if these fulfil your need, then all you need to write in javascript is an adapter or even a call to a built-in adapter which can be as little as a single line. Take a look inside jquery.validate.js to find out what is available.
Using an existing jquery.validate.unobtrusive adapter
The job of the adapter is to read the HTML5 data-* attributes on your form element and convert this data into a form that can be understood by jquery.validate and your custom validation function. You are not required to do all the work yourself though and in many cases, you can call a built-in adapter. jquery.validate.unobtrusive declares three built-in adapters which can be used in the majority of situations. These are:
jQuery.validator.unobtrusive.adapters.addBool - used when your validator does not need any additional data.
jQuery.validator.unobtrusive.adapters.addSingleVal - used when your validator takes in one piece of additional data.
jQuery.validator.unobtrusive.adapters.addMinMax - used when your validator deals with minimum and maximum values such as range or string length.
If your validator does not fit into one of these categories, you are required to write your own adapter using the jQuery.validator.unobtrusive.adapters.add method. This is not as difficulty as it sounds and we'll see an example later in the article.
We use the addSingleVal method, passing in the name of the adapter and the name of the single value that we want to pass. Should the name of the validation function differ from the adapter, you can pass in a third parameter (ruleName):
jQuery.validator.unobtrusive.adapters.addSingleVal("notequalto", "otherproperty", "mynotequaltofunction");
At this point, our custom validator is complete.
For better understanding refer to the article itself which presents more description and a more complex example.
HTH.
I just did this and then used a regex expression:
$(document).ready(function () {
$.validator.methods.number = function (e) {
return true;
};
});
[RegularExpression(#"^[0-9\.]*$", ErrorMessage = "Invalid Amount")]
public decimal? Amount { get; set; }
Or you can simply do this.
#Html.ValidationMessageFor(m => m.PercentChange, "Custom Message: Input value must be a number"), new { #style = "display:none" })
Hope this helps.
I make this putting this on my view
#Html.DropDownListFor(m => m.BenefNamePos, Model.Options, new { onchange = "changePosition(this);", #class="form-control", data_val_number = "This is my custom message" })
I have this problem in KendoGrid, I use a script at the END of View to override data-val-number:
#(Html.Kendo().Grid<Test.ViewModel>(Model)
.Name("listado")
...
.Columns(columns =>
{
columns.Bound("idElementColumn").Filterable(false);
...
}
And at least, in the end of View I put:
<script type="text/javascript">
$("#listado").on("click", function (e) {
$(".k-grid #idElementColumn").attr('data-val-number', 'Ingrese un número.');
});
</script>
a simple method is, use dataanotation change message on ViewModel:
[Required(ErrorMessage ="الزامی")]
[StringLength(maximumLength:50,MinimumLength =2)]
[Display(Name = "نام")]
public string FirstName { get; set; }
I am using ASP.NET MVC 2 Beta. I can create a wizard like workflow using Steven Sanderson's technique (in his book Pro ASP.NET MVC Framework) except using Session instead of hidden form fields to preserve the data across requests. I can go back and forth between pages and maintain the values in a TextBox without any issue when my model is not a collection. An example would be a simple Person model:
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
public string Email { get; set; }
}
But I am unable to get this to work when I pass around an IEnumerable. In my view I am trying to run through the Model and generate a TextBox for Name and Email for each Person in the list. I can generate the form fine and I can submit the form with my values and go to Step2. But when I click the Back button in Step2 it takes me back to Step1 with an empty form. None of the fields that I previously populated are there. There must be something I am missing. Can somebody help me out?
Here is my View:
<% using (Html.BeginForm()) { %>
<% int index = 0;
foreach (var person in Model) { %>
<fieldset>
<%= Html.Hidden("persons.index", index.ToString())%>
<div>Name: <%= Html.TextBox("persons[" + index.ToString() + "].Name")%></div>
<div>Email: <%= Html.TextBox("persons[" + index.ToString() + "].Email")%></div>
</fieldset>
<% index++;
} %>
<p><input type="submit" name="btnNext" value="Next >>" /></p>
<% } %>
And here is my controller:
public class PersonListController : Controller
{
public IEnumerable<Person> persons;
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
persons = (Session["persons"]
?? TempData["persons"]
?? new List<Person>()) as List<Person>;
// I've tried this with and without the prefix.
TryUpdateModel(persons, "persons");
}
protected override void OnResultExecuted(ResultExecutedContext filterContext)
{
Session["persons"] = persons;
if (filterContext.Result is RedirectToRouteResult)
TempData["persons"] = persons;
}
public ActionResult Step1(string btnBack, string btnNext)
{
if (btnNext != null)
return RedirectToAction("Step2");
// Setup some fake data
var personsList = new List<Person>
{
new Person { Name = "Jared", Email = "test#email.com", },
new Person { Name = "John", Email = "test2#email.com" }
};
// Populate the model with fake data the first time
// the action method is called only. This is to simulate
// pulling some data in from a DB.
if (persons == null || persons.Count() == 0)
persons = personsList;
return View(persons);
}
// Step2 is just a page that provides a back button to Step1
public ActionResult Step2(string btnBack, string btnNext)
{
if (btnBack != null)
return RedirectToAction("Step1");
return View(persons);
}
}
As far as I can tell, this is not supported in ASP.NET MVC 2 Beta, nor is it supported in ASP.NET MVC 2 RC. I dug through the MVC source code and it looks like Dictionaries are supported but not Models that are IEnumerable<> (or that contain nested IEnumerable objects) and it's inheritors like IList<>.
The issue is in the ViewDataDictionary class. Particularly, the GetPropertyValue method only provides a way to retrieve property values from dictionary properties (by calling GetIndexedPropertyValue) or simple properties by using the PropertyDescriptor.GetValue method to pull out the value.
To fix this, I created a GetCollectionPropertyValue method that handles Models that are collections (and even Models that contain nested collections). I am pasting the code here for reference. Note: I don't make any claims about elegance - in fact all the string parsing is pretty ugly, but it seems to be working. Here is the method:
// Can be used to pull out values from Models with collections and nested collections.
// E.g. Persons[0].Phones[3].AreaCode
private static ViewDataInfo GetCollectionPropertyValue(object indexableObject, string key)
{
Type enumerableType = TypeHelpers.ExtractGenericInterface(indexableObject.GetType(), typeof(IEnumerable<>));
if (enumerableType != null)
{
IList listOfModelElements = (IList)indexableObject;
int firstOpenBracketPosition = key.IndexOf('[');
int firstCloseBracketPosition = key.IndexOf(']');
string firstIndexString = key.Substring(firstOpenBracketPosition + 1, firstCloseBracketPosition - firstOpenBracketPosition - 1);
int firstIndex = 0;
bool canParse = int.TryParse(firstIndexString, out firstIndex);
object element = null;
// if the index was numeric we should be able to grab the element from the list
if (canParse)
element = listOfModelElements[firstIndex];
if (element != null)
{
int firstDotPosition = key.IndexOf('.');
int nextOpenBracketPosition = key.IndexOf('[', firstCloseBracketPosition);
PropertyDescriptor descriptor = TypeDescriptor.GetProperties(element).Find(key.Substring(firstDotPosition + 1), true);
// If the Model has nested collections, we need to keep digging recursively
if (nextOpenBracketPosition >= 0)
{
string nextObjectName = key.Substring(firstDotPosition+1, nextOpenBracketPosition-firstDotPosition-1);
string nextKey = key.Substring(firstDotPosition + 1);
PropertyInfo property = element.GetType().GetProperty(nextObjectName);
object nestedCollection = property.GetValue(element,null);
// Recursively pull out the nested value
return GetCollectionPropertyValue(nestedCollection, nextKey);
}
else
{
return new ViewDataInfo(() => descriptor.GetValue(element))
{
Container = indexableObject,
PropertyDescriptor = descriptor
};
}
}
}
return null;
}
And here is the modified GetPropertyValue method which calls the new method:
private static ViewDataInfo GetPropertyValue(object container, string propertyName) {
// This method handles one "segment" of a complex property expression
// First, we try to evaluate the property based on its indexer
ViewDataInfo value = GetIndexedPropertyValue(container, propertyName);
if (value != null) {
return value;
}
// If the indexer didn't return anything useful, continue...
// If the container is a ViewDataDictionary then treat its Model property
// as the container instead of the ViewDataDictionary itself.
ViewDataDictionary vdd = container as ViewDataDictionary;
if (vdd != null) {
container = vdd.Model;
}
// Second, we try to evaluate the property based on the assumption
// that it is a collection of some sort (e.g. IList<>, IEnumerable<>)
value = GetCollectionPropertyValue(container, propertyName);
if (value != null)
{
return value;
}
// If the container is null, we're out of options
if (container == null) {
return null;
}
// Third, we try to use PropertyDescriptors and treat the expression as a property name
PropertyDescriptor descriptor = TypeDescriptor.GetProperties(container).Find(propertyName, true);
if (descriptor == null) {
return null;
}
return new ViewDataInfo(() => descriptor.GetValue(container)) {
Container = container,
PropertyDescriptor = descriptor
};
}
Again, this is in the ViewDataDictionary.cs file in ASP.NET MVC 2 RC. Should I create a new issue to track this on the MVC codeplex site?