This is my ViewModel class:
public class CreatePersonModel
{
public string Name { get; set; }
public DateTime DateBirth { get; set; }
public string Email { get; set; }
}
CreatePerson.cshtml
#model ViewModels.CreatePersonModel
#{
ViewBag.Title = "Create Person";
}
<h2>#ViewBag.Title</h2>
#using (Html.BeginForm())
{
<fieldset>
<legend>RegisterModel</legend>
#Html.EditorForModel()
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
CreatePersonValidator.cs
public class CreatePersonValidator : AbstractValidator<CreatePersonModel>
{
public CreatePersonValidator()
{
RuleFor(p => p.Name)
.NotEmpty().WithMessage("campo obrigatório")
.Length(5, 30).WithMessage("mínimo de {0} e máximo de {1} caractéres", 5, 30)
.Must((p, n) => n.Any(c => c == ' ')).WithMessage("deve conter nome e sobrenome");
RuleFor(p => p.DateBirth)
.NotEmpty().WithMessage("campo obrigatório")
.LessThan(p => DateTime.Now).WithMessage("a data deve estar no passado");
RuleFor(p => p.Email)
.NotEmpty().WithMessage("campo obrigatório")
.EmailAddress().WithMessage("email inválido")
.OnAnyFailure(p => p.Email = "");
}
}
When trying to create a person with an invalid date format:
Observations
As in my CreatePersonModel class the DateBirth property is a DateTime type, the asp.net MVC validation has done for me.
But I want to customize the error message using the FluentValidation.
I do not want to change the type of property for various reasons such as:
In a CreatePersonValidator.cs class, validation is to check if the date is in the past:
.LessThan (p => DateTime.Now)
Question
How to customize the error message without using DataAnnotations (using FluentValidator).
public CreatePersonValidator()
{
RuleFor(courseOffering => courseOffering.StartDate)
.Must(BeAValidDate).WithMessage("Start date is required");
//....
}
private bool BeAValidDate(DateTime date)
{
return !date.Equals(default(DateTime));
}
Have a look at the Fluent Validation documentation on GitHub:
https://github.com/JeremySkinner/FluentValidation/wiki
Try adding a RegEx Validator to ensure that the user's input (a string) can be parsed as a date correctly, prior to applying the Less Than Validator.
EDIT
Having run few test cases and looked at the source code for Fluent Validator I concede that the above approach won't work.
The standard error you get is added during the Model Binding phase, which happens before the fluent validation framework can access and check the model.
I assumed that the framework's authors had been clever and were injecting their validation code into the model binding phase. Looks like they aren't.
So the short answer is what you want to do does not appear to be possible.
Try this one
RuleFor(f =>
f.StartDate).Cascade(CascadeMode.StopOnFirstFailure).NotEmpty()
.Must(date => date != default(DateTime))
.WithMessage("Start date is required");
As Stewart mentioned, it's not possible to use FluentValidation alone to get in front of the model binding in this way. I'd offer up two ideas/suggestions though:
If you really can't change the ViewModel type from DateTime to string, you could always clear the model state yourself after model binding and then run the validator manually (I'm assuming you've wired FluentValidation to execute automatically after model binding).
In scenarios like this, I would change the property to a string, but then use AutoMapper to map that into a DateTime for whatever business object / domain model / service contract request I need it to ultimately become. That way, you get the most flexibility with parsing and conversion on both sides of the model binding.
I got this to work with DateTime? using really simple code. If you use the built-in validator NotNull(), then you get client side validation, which has 2 benefits.
You don't have to worry about all the model binding stuff people are talking about in other answers. It's client side! ;)
The date the user entered does not get "wiped out". An invalid date will get set by to null by the Post, Model binding to null, Validation error, responding with the view now with a null value in the date. This happens fast, so the user does not see what is wrong with the date.
This is the code I used:
RuleFor(x => x.CompleteDate).NotNull().WithMessage("Complete Date is not a valid date.");
I tested it with a date of 11/31/2021 (There is no day 31 in November) and it worked great with client-side validation.
Win!
Related
how can I avoid the generation of the html attribute "data-val-date" for the element created from a Datetime property?
The model:
public class RegisterModel
{
[Required]
[Display(Name = "Date of birth")]
public DateTime? DateOfBirth { get; set; }
}
The view:
#Html.LabelFor(m => m.DateOfBirth)
#Html.EditorFor(m => m.DateOfBirth)
In fact, I'm creating a three drop down lists element for selecting the date of birth, which don't give a value in a date format.
Some solutions I've seen, consisted in a work around: removing the validation with a javascript.
The solution I envisage is to split the DateTime property into three long one for each value (day, month, year).
Ok, this took me an afternoon of work... apparently mvc4 decided that it was time to render a data-val-date="Message" on EVERY datetime property on the viewmodel. I've tried to modify this default behaviour but didn't succeed.
This solved my problems:
$.validator.addMethod('date',
function (value, element) {
return true; // since MVC4 data-val-date is put on EVERY vm date property. Default implementation does not allow for multiple cultures...
});
You can also try to write your own editor template named "DateTime.cshtml" in your shared EditorFor folder, but don't use TextBoxFor there, because that one is polluted as well.
data-val-date is used by the validation system to validate the date. If you remove it, client-side validation won't work.
If that's what you want, then just disable client-side validation.
Add this to your application start in your global.asax file and the form should fire.
DataAnnotationsModelValidatorProvider.AddImplicitRequiredAttributeForValueTypes = false;
Lets make the following assumptions; ASP.NET MVC 3 Razor C#, a strongly typed view bound to a view model (not entities etc.), using the Html.EditorFor method, to edit a nullable DateTime property in the view model. The two data annotation attributes I added seem to be causing model binding to fail.
Sample view code
#model MyApp.ViewModels.NullableDateTimeViewModel
#using (Html.BeginForm())
{
#Html.EditorFor(m => m.DateOfBirth)
}
Sample ViewModel code
[DataType(DataType.Date,
ErrorMessage = "Please enter a valid date in the format dd MMM yyyy")]
[DisplayFormat(ApplyFormatInEditMode = true, DataFormatString = "{0:dd MMM yyyy}")]
public class NullableDateTimeViewModel
{
public DateTime? DateOfBirth { get; set; }
}
Sample controller code
[HttpPost]
public ViewResult DoB(NullableDateTimeViewModel nullableDateTimeVM)
{
ContextDB db = new ContextDB();
Customer cust = new Customer();
// DateOfBirth is null so the update fails
cust.DateOfBirth = nullableDateTimeVM.DateOfBirth.Value;
db.Customers.Add(cust);
db.SaveChanges();
}
The data entered in the view is not posting back to the controller when the form in the view is submitted when the data annotation attributes are added. This means that model binding is failing when using EditorFor with those attributes. Model binding works fine with TextBoxFor, the value entered in the TextBoxFor input box is passed back to the view with the view model. What is the problem here with EditorFor and the data annotation validation attributes?
Can we please find a solution that does not involved reinventing the wheel by creating multiple additional classes, helpers, templates and writing a whole lot of additional code? I am looking for a one or two line solution.
Could you help me, please.
I have a class:
public class Product
{
...
// NOT REQUIRED!
public virtual Category Category{ get; set; }
}
But when in a view I create
#Html.HiddenFor(model => model.Category.Id), or
#Html.Hidden("model.Category.Id", model => model.Category.Id)
razor adds validation attribute to this.
How to turn it off? (in model, in view)
How to turn off validation event if a property has the attribute [Required]?
I found out that this is not a razor problem, it is somewhere in MVC.
Even if I manage to pass "Category.Id" value = "" to the server, TryModelUpdate() will fail - it requires "Category.Id" to be set, but it's not required in my model.
Why is it so??!
I solved the same issue with an crutch like this:
#{ Html.EnableUnobtrusiveJavaScript(false); }
#Html.HiddenFor(t => t.Prop1)
#Html.HiddenFor(t => t.Prop2)
...
#{ Html.EnableUnobtrusiveJavaScript(true); }
Setup a hidden like:
#Html.Hidden("CategoryIdHidden", model => model.Category.Id)
And process the posted hidden value separate from the model binding stuff... I think the validation is UI specific, and not model specific, so it wouldn't validate the category ID.
Or, supply in the hidden a default value of "0". A value of "" probably won't evaluate correctly if the category.ID is of type int, hence its null, hence it errors.
HTH.
In my model I have the following property:
[DataType(DataType.Currency)]
public decimal? Budget { get; set; }
When the user enters in $1,200.34, I need that value to be valid and strip out the currency symbol and comma.
In my controller I'm doing:
if (race.Budget != null)
{
race.Budget.ToString().Replace("$", "").Replace(",", "");
}
The problem is that client validation doesn't pass the value for budget into the controller. I get a value of null. How can I override the client validation so that I can strip out the currency symbol and comma?
Thank you in advance for the help.
UPDATE
So here's the strange thing. Let's say I want to bypass client validation all together. I added #{ Html.EnableClientValidation(false); } to my view and it's still sending a null value for Budget when I submit to the controller.
This isn't a client side validation problem. Your model has a field of type decimal? The model binder will try to bind a value of $123,456.78 into that and fail, so the value will be null. Here's one way to get around this:
Change your model to have a string property that masks your decimal:
public decimal? Budget { get; set; }
public string BudgetText {
get {
return Budget.HasValue ? Budget.ToString("$") : string.Empty;
}
set {
// parse "value" and try to put it into Budget
}
}
Then, just bind to BudgetText from your View. Validate it as a string with a regular expression that accepts only money input. It'll probably be the same regex you can use for your BudgetText's set method
So you can probably hook in some JQuery to pre-process the form field to strip the characters off you don't want (prior to form submission to the server). This is probably the quickest, dirtiest approach.
For something reusable, have a look into custom client validation adapters. The links aren't spot on, but should get you in the right direction. For Brad's screencast, I believe the relevant parts are fairly early on.
Check out the support for jQuery localization
cliente validation using jQuery validate for currency fields
also there is a plugin for currency validation as well
http://code.google.com/p/jquery-formatcurrency/
check out this recent post as well for a $ in binding
.NET MVC 3 Custom Decimal? Model Binder
I have followed this article and have a passing test showing custom validation error messages being returned from a resource file when a call to Validator.IsValid(someEntity) fails.
I am trying to translate this to the new unobtrusive client-side validation in MVC3 describe by this article. This also more or less works - well the client-side validation does so I can assume my wiring of NHValidators is good, as you can see the following is output:
<input data-val="true" data-val-required="{NotNullNotEmpty}" id="Username" name="Username" type="text" value="" />
<span class="field-validation-valid" data-valmsg-for="Username" data-valmsg-replace="true">
My problem is that this time the CustomMessageInterpolator has not fired - the resource string has not been translated for this property (it's still in curly braces): {NotNullNotEmpty}.
Both my test and the web are usign the same NHValidator config:
var cfg = new FluentConfiguration();
cfg
.SetMessageInterpolator<CustomMessageInterpolator>()
.SetCustomResourceManager("MyProject.Core.Resources.ValidationMessages", Assembly.Load("MyProject.Core"))
.SetDefaultValidatorMode(ValidatorMode.UseAttribute)
.Register(Assembly.Load("MyProject.Core").ValidationDefinitions())
Just for good measure here's the model snippet:
public class LoginModel
{
[NotNullNotEmpty(Message = "{NotNullNotEmpty}")]
public string Username { get; set; }
Long shot I know but any ideas would be appreciated!
I had no luck with this having posted it around a few forums.
In the end I've resorted to using DataAnnotations instead in my View Models for client-side validation.
Of course this is really hacky and a horrible "solution" as I've now two types of validation attributes in my code. (if wondering, yes I'd rather keep my NH Validation attributes 1. as I'm still generating my schema from them and 2. I like the way NH validates before committing to the db i.e. server-side).
If interested, here's how to use resource files to show the correct labels for your controls.
This code snippet shows how to then also rig up custom error messages:
[DisplayResource(typeof(LabelResources))]
public class NewChildTagViewModel : ModuleViewModel
{
public int ParentId { get; set; }
[Required]
public string Title { get; set; }
[Range(1, 1000, ErrorMessageResourceType = typeof(ValidationMessages), ErrorMessageResourceName = "Display_Order")]
[Required]
public int? DisplayOrder { get; set; }
}
Can you show your code? I presume you used some sort of custom ModelValidatorProvider?
I've got something working like what you described. It required writing a custom AssociatedValidatorProvider and ModelValidator, along the lines described here
http://weblogs.asp.net/srkirkland/archive/2010/07/20/nhibernate-client-validator-asp-net-mvc-2-model-validation.aspx
but changing the MessageOrDefault method in the article above to use the NHibernate Validator DefaultMessageInterpolator to translate the messages.
It sounds like you got most of the way towards getting something working, and you just needed to use the DefaultMessageInterpolator to translate the messages.