MVC HtmlHelper vs FluentValidation 3.1: Troubles getting ModelMetadata IsRequired - asp.net-mvc-3

I created a HtmlHelper for Label that puts a star after the name of that Label if associated field is required:
public static MvcHtmlString LabelForR<TModel, TValue>(
this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression)
{
return LabelHelper(
html,
ModelMetadata.FromLambdaExpression(expression, html.ViewData),
ExpressionHelper.GetExpressionText(expression),
null);
}
private static MvcHtmlString LabelHelper(HtmlHelper helper, ModelMetadata metadata, string htmlFieldName, string text)
{
... //check metadata.IsRequired here
... // if Required show the star
}
If I use DataAnnotations and slap [Required] on the property in my ViewModel, metadata.IsRequired in my private LabelHelper will be equal to True and everything will work as intended.
However, if I use FluentValidation 3.1 and add a simple rule like that:
public class CheckEmailViewModelValidator : AbstractValidator<CheckEmailViewModel>
{
public CheckEmailViewModelValidator()
{
RuleFor(m => m.Email)
.NotNull()
.EmailAddress();
}
}
... in my LabelHelper metadata.IsRequired will be incorrectly set to false. (The validator works though: you can't submit empty field and it needs to be an Email like).
The rest of the metadata looks correct (Ex: metadata.DisplayName = "Email").
In theory, FluentValidator slaps RequiredAttribute on property if Rule .NotNull() is used.
For references:
My ViewModel:
[Validator(typeof(CheckEmailViewModelValidator))]
public class CheckEmailViewModel
{
//[Required]
[Display(Name = "Email")]
public string Email { get; set; }
}
My Controller:
public class MemberController : Controller
{
[HttpGet]
public ActionResult CheckEmail()
{
var model = new CheckEmailViewModel();
return View(model);
}
}
Any help is appreciated.

By default, MVC uses the DataAnnotations attributes for two separate purposes - metadata and validation.
When you enable FluentValidation in an MVC application, FluentValidation hooks into the validation infrastructure but not metadata - MVC will continue to use attributes for metadata. If you want to use FluentValidation for metadata as well as validation then you'd need to write a custom implementation of MVC's ModelMetadataProvider that knows how to interrogate the validator classes - this isn't something that FluentValidation supports out of the box.

I have a custom ModelMetadataProvider that enhances the default DataAnnotations one giving the following:
populates "DisplayName" from propertyname splitting string from Camel Case, if none is specified through DisplayAttribute.
If the ModelMetadata.IsRequired is set to false it checks if there are any fluent validator rules present (of type NotNull or NotEmpty).
I definitely checked out the source code that Jeremy has prepared but I was not ready for a total overhaul so I mixed and matched in order not to lose the default behavior. You can find it here
Here is the code with some additional goodness taken from this post.
public class CustomModelMetadataProvider : DataAnnotationsModelMetadataProvider
{
readonly IValidatorFactory factory;
public CustomModelMetadataProvider(IValidatorFactory factory)
: base() {
this.factory = factory;
}
// Uppercase followed by lowercase but not on existing word boundary (eg. the start)
Regex _camelCaseRegex = new Regex(#"\B\p{Lu}\p{Ll}", RegexOptions.Compiled);
// Creates a nice DisplayName from the model’s property name if one hasn't been specified
protected override ModelMetadata GetMetadataForProperty(
Func<object> modelAccessor,
Type containerType,
PropertyDescriptor propertyDescriptor) {
ModelMetadata metadata = base.GetMetadataForProperty(modelAccessor, containerType, propertyDescriptor);
metadata.IsRequired = metadata.IsRequired || IsNotEmpty(containerType, propertyDescriptor.Name);
if (metadata.DisplayName == null)
metadata.DisplayName = displayNameFromCamelCase(metadata.GetDisplayName());
if (string.IsNullOrWhiteSpace(metadata.DisplayFormatString) &&
(propertyDescriptor.PropertyType == typeof(DateTime) || propertyDescriptor.PropertyType == typeof(DateTime?))) {
metadata.DisplayFormatString = "{0:d}";
}
return metadata;
}
string displayNameFromCamelCase(string name) {
name = _camelCaseRegex.Replace(name, " $0");
if (name.EndsWith(" Id"))
name = name.Substring(0, name.Length - 3);
return name;
}
bool IsNotEmpty(Type type, string name) {
bool notEmpty = false;
var validator = factory.GetValidator(type);
if (validator == null)
return false;
IEnumerable<IPropertyValidator> validators = validator.CreateDescriptor().GetValidatorsForMember(name);
notEmpty = validators.OfType<INotNullValidator>().Cast<IPropertyValidator>()
.Concat(validators.OfType<INotEmptyValidator>().Cast<IPropertyValidator>()).Count() > 0;
return notEmpty;
}
}

Related

Is it possible to force the currency for an MVC3 field with DataType as DataType.Currency

I'm writing an MVC3 application that reads in a bunch of monetary data from a database. The issue I have is that these amounts are all in different currencies.
If I set the type of a field like this:
[DataType(DataType.Currency)]
public Amount{ get; set;}
I get the decimal places and a currency symbol, which looks nice, but it defaults to the user's local currency. A US user sees $423.29 whereas a GB user sees £423.29. I can override the currency by using a <globalization culture="{something}"> in the Web.config, but this sets all currency fields globally.
What would be the easiest way of marking up a field so that it renders with the correct decimal places and currency symbol?
In an ideal world, I'd like to be able to do something like this (for USD):
[DataType(DataType.Currency, culture="en-us")]
public Amount{ get; set; }
and have that always render as $439.38, but that's not possible with the built-in annotations.
The way I would do this is to create a custom attribute that extends the DataType attribute and a custom html helper. It's not necessarily the easiest way of doing it but it would save time in the future.
EDIT
Incorporated CultureInfo.CreateSpecificCulture(cultureName) instead of a switch
Custom Attribute
public class CurrencyDisplayAttribute : DataTypeAttribute
{
public string Culture { get; set; }
public CurrencyDisplayAttribute(string culture)
: base(DataType.Currency)
{
Culture = culture;
}
}
Html Helper
public static class Helpers
{
public static IHtmlString CurrencyDisplayFor<TModel, TProperty>(this HtmlHelper<TModel> helper, Expression<Func<TModel, TProperty>> expression)
{
double value = double.Parse(expression.Compile().Invoke(helper.ViewData.Model).ToString());
var metadata = ModelMetadata.FromLambdaExpression(expression, helper.ViewData);
var prop = typeof (TModel).GetProperty(metadata.PropertyName);
var attribute = prop.GetCustomAttribute(typeof (CurrencyDisplayAttribute)) as CurrencyDisplayAttribute;
// this should be whatever html element you want to create
TagBuilder tagBuilder = new TagBuilder("span");
tagBuilder.SetInnerText(value.ToString("c", CultureInfo.CreateSpecificCulture(attribute.Culture));
return MvcHtmlString.Create(tagBuilder.ToString());
}
}
You can use the attribute in your model
[CurrencyDisplay("en-us")]
public double Amount { get; set; }
Then in your view you can use the helper by
#Html.CurrencyDisplayFor(x => x.Amount);
Provided your model is passed in correctly.
Obviously, you'd need to do error checking and so on.
Make string the amount
public string Amount{ get; set;}
Create a method that converts to string with currency exact
private string LocalizedAmount(decimal theAmount, string cultureName)
{
return theAmount.ToString("c",CultureInfo.CreateSpecificCulture(cultureName));
}
If you are stored in your database two fields or columns one for value and one for culture.
And in the Repository or the controller:
Amount = LocalizedAmount(Convert.ToDecimal(reader[0]),reader[1].ToString());
You need to assing the culture:
Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture(cultureName);
Thread.CurrentThread.CurrentUICulture = CultureInfo.CreateSpecificCulture(cultureName);
Inside this Controller overrided methods:
protected override void OnActionExecuted(ActionExecutedContext filterContext)
protected override void ExecuteCore()
Read the whole technique in this NadeemAfana's blog ASP.NET MVC 3 Internationalization

Is it possible to access additional metadata info from a custom display or editor template?

I am aware that in a custom display or editor template I can get metadata about the model via ViewData.ModelMetadata, which has properties that indicate whether certain metadata attributes have been defined for the property, such as IsRequired, DisplayName, and so on. But is there anyway I can access custom metadata I've added to the property via custom attributes?
For example, say in my view I have a property like so:
[UIHint("Whizbang")]
[SomeAttribute("foobar")]
public string LeftWhizbang { get; set; }
And I have a custom display template named Whizbang.cshtml with the following content:
#model string
Left Whizbang Value: #Model
What I'd like to do is be able to determine whether the property LeftWhizbang is decorated with the attribute SomeAttribute and, if so, I'd like to access the attribute's Message property (say), namely the value "foobar".
I'd like to be able to do something like this in my template:
#model string
Left Whizbang Value: #Model
#{
SomeAttributeAttribute attr = ViewData.ModelMetadata.GetAttributes(...);
if (attr != null)
{
<text>... and the value is #attr.Message</text>
}
}
Is this at all possible, or am I looking down a dead end?
Sure. First you'll need your attribute which implements IMetadataAware so that DataAnnotationsModelMetadataProvider knows about it
public class TooltipAttribute : Attribute, IMetadataAware {
public TooltipAttribute(string tooltip) {
this.Tooltip = tooltip;
}
public string Tooltip { get; set; }
public void OnMetadataCreated(ModelMetadata metadata) {
metadata.AdditionalValues["Tooltip"] = this.Tooltip;
}
}
You can then access the attribute by creating a helper method:
public static IHtmlString TooltipFor<TModel, TValue>(
this HtmlHelper<TModel> html,
Expression<Func<TModel, TValue>> expression) {
var data = ModelMetadata.FromLambdaExpression<TModel, TValue>(expression, html.ViewData);
if (data.AdditionalValues.ContainsKey("Tooltip"))
return new HtmlString((string)data.AdditionalValues["Tooltip"]);
return new HtmlString("");
}

MVC3 / EF CustomValidator two fields in model

Using MVC3 and EF4.1 how do I validate on client and server more than one field in my view model?
I have a start date text box (that can be modified) and I have the original start date in a hidden field. When the user submits the form I want to check that the modied start date is no more than one month either side of the original start date.
I can't figure out how this can be done with DataAnnotation and CustomValidation (or maybe I'm going down the wrong road)? This is an example of whay I've been working with:
[MetadataType(typeof(Metadata.MyUserMetaData))]
public partial class MyUser
{
public System.DateTime DateOfBirth { get; set; }
}
Partial Class
public class MyUserMetaData
{
[CustomValidation(typeof(AmendedStartDate), "amendedstartdate", ErrorMessage = "Invalid date."]
public DateTime StartDate { get; set; };
public DateTime OriginalStartDate { get; set; };
}
Custom Validator
public class AmendedStartDate : ValidationAttribute, IClientValidatable
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
// How do I get multiple field values from object value?
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(Modelmetadata metadate, ControllerContext context)
{
var rule = new ModelClientValidationRule
{
ErrorMessage = FormatErrorMessage(metadata.GetDisplayName()),
ValidationType = "amendedstartdate"
};
yield return rule;
}
}
I know I've still to add jQuery to the view for this validator.
Instead of using data annotations implement IValidatableObject on your model class - it is simpler and much more clear in scenarios with cross validation.
If you still want to use ValidationAttribute you have two parameters in the IsValid method:
value represents validated value of the property where the attribute is assigned
context is context in which the property is validated. It also contains ObjectInstance and ObjectType properties to access the whole model and its type so you can cast the instance and access other properties.
The question asked in MVC custom validation: compare two dates has an example of a validator which compares to a second value in the model. That should get you started.

asp.net MVC 2 - View Model / Model Validation - Is there a way to map validation attributes from model to ViewModel via AutoMapper? [duplicate]

I use AutoMapper to map my domain objects to my view models. I have metadata in my domain layer, that I would like to carry over to the view layer and into ModelMetadata. (This metadata is not UI logic, but provides necessary information to my views).
Right now, my solution is to use a separate MetadataProvider (independently of ASP.NET MVC), and use conventions to apply the relevant metadata to the ModelMetadata object via an AssociatedMetadataProvider. The problem with this approach is that I have to test for the same conventions when binding the ModelMetadata from the domain as I do with my AutoMapping, and it seems like there should be a way to make this more orthogonal. Can anyone recommend a better way to accomplish this?
I use the approach below to automatically copy data annotations from my entities to my view model. This ensures that things like StringLength and Required values are always the same for entity/viewmodel.
It works using the Automapper configuration, so works if the properties are named differently on the viewmodel as long as AutoMapper is setup correctly.
You need to create a custom ModelValidatorProvider and custom ModelMetadataProvider to get this to work. My memory on why is a little foggy, but I believe it's so both server and client side validation work, as well as any other formatting you do based on the metadata (eg an asterix next to required fields).
Note: I have simplified my code slightly as I added it below, so there may be a few small issues.
Metadata Provider
public class MetadataProvider : DataAnnotationsModelMetadataProvider
{
private IConfigurationProvider _mapper;
public MetadataProvider(IConfigurationProvider mapper)
{
_mapper = mapper;
}
protected override System.Web.Mvc.ModelMetadata CreateMetadata(IEnumerable<Attribute> attributes, Type containerType, Func<object> modelAccessor, Type modelType, string propertyName)
{
//Grab attributes from the entity columns and copy them to the view model
var mappedAttributes = _mapper.GetMappedAttributes(containerType, propertyName, attributes);
return base.CreateMetadata(mappedAttributes, containerType, modelAccessor, modelType, propertyName);
}
}
Validator Provivder
public class ValidatorProvider : DataAnnotationsModelValidatorProvider
{
private IConfigurationProvider _mapper;
public ValidatorProvider(IConfigurationProvider mapper)
{
_mapper = mapper;
}
protected override System.Collections.Generic.IEnumerable<ModelValidator> GetValidators(System.Web.Mvc.ModelMetadata metadata, ControllerContext context, IEnumerable<Attribute> attributes)
{
var mappedAttributes = _mapper.GetMappedAttributes(metadata.ContainerType, metadata.PropertyName, attributes);
return base.GetValidators(metadata, context, mappedAttributes);
}
}
Helper Method Referenced in above 2 classes
public static IEnumerable<Attribute> GetMappedAttributes(this IConfigurationProvider mapper, Type sourceType, string propertyName, IEnumerable<Attribute> existingAttributes)
{
if (sourceType != null)
{
foreach (var typeMap in mapper.GetAllTypeMaps().Where(i => i.SourceType == sourceType))
{
foreach (var propertyMap in typeMap.GetPropertyMaps())
{
if (propertyMap.IsIgnored() || propertyMap.SourceMember == null)
continue;
if (propertyMap.SourceMember.Name == propertyName)
{
foreach (ValidationAttribute attribute in propertyMap.DestinationProperty.GetCustomAttributes(typeof(ValidationAttribute), true))
{
if (!existingAttributes.Any(i => i.GetType() == attribute.GetType()))
yield return attribute;
}
}
}
}
}
if (existingAttributes != null)
{
foreach (var attribute in existingAttributes)
{
yield return attribute;
}
}
}
Other Notes
If you're using dependency injection, make sure your container isn't already replacing the built in metadata provider or validator provider. In my case I was using the Ninject.MVC3 package which bound one of them after creating the kernel, I then had to rebind it afterwards so my class was actually used. I was getting exceptions about Required only being allowed to be added once, took most of a day to track it down.
if your metadata are provided with attributes define the attributes in MetaDataTypes, then apply the same MetaDataType to both your domain class and to your viewmodels. You can define all MetaDataTypes in a separate dll that is reference by both layers. There are some issues with this approach if your ViewModel classes have not some properties that is used in the MetaDataType, but this can be fixed with a custom Provider(I have the code if youlike this approach).
Betty's solution is excellent for "inheriting" data annotations. I have extended this idea to also include validation provided by IValidatableObject.
public class MappedModelValidatorProvider : DataAnnotationsModelValidatorProvider
{
private readonly IMapper _mapper;
public MappedModelValidatorProvider(IMapper mapper)
{
_mapper = mapper;
}
protected override IEnumerable<ModelValidator> GetValidators(ModelMetadata metadata, ControllerContext context, IEnumerable<Attribute> attributes)
{
var mappedAttributes = _mapper.ConfigurationProvider.GetMappedAttributes(metadata.ContainerType, metadata.PropertyName, attributes);
foreach (var validator in base.GetValidators(metadata, context, mappedAttributes))
{
yield return validator;
}
foreach (var typeMap in _mapper.ConfigurationProvider.GetAllTypeMaps().Where(i => i.SourceType == metadata.ModelType))
{
if (typeof(IValidatableObject).IsAssignableFrom(typeMap.DestinationType))
{
var model = _mapper.Map(metadata.Model, typeMap.SourceType, typeMap.DestinationType);
var modelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, typeMap.DestinationType);
yield return new ValidatableObjectAdapter(modelMetadata, context);
}
}
}
}
Then in Global.asax.cs:
ModelValidatorProviders.Providers.Clear();
ModelValidatorProviders.Providers.Add(new MappedModelValidatorProvider(Mapper.Instance));

Custom model validation of dependent properties using Data Annotations

Since now I've used the excellent FluentValidation
library to validate my model classes. In web applications I use it in conjunction with the jquery.validate plugin to perform client side validation as well.
One drawback is that much of the validation logic is repeated on the client side and is no longer centralized at a single place.
For this reason I'm looking for an alternative. There are many examples out there showing the usage of data annotations to perform model validation. It looks very promising.
One thing I couldn't find out is how to validate a property that depends on another property value.
Let's take for example the following model:
public class Event
{
[Required]
public DateTime? StartDate { get; set; }
[Required]
public DateTime? EndDate { get; set; }
}
I would like to ensure that EndDate is greater than StartDate. I could write a custom
validation attribute extending ValidationAttribute in order to perform custom validation logic. Unfortunately I couldn't find a way to obtain the
model instance:
public class CustomValidationAttribute : ValidationAttribute
{
public override bool IsValid(object value)
{
// value represents the property value on which this attribute is applied
// but how to obtain the object instance to which this property belongs?
return true;
}
}
I found that the CustomValidationAttribute seems to do the job because it has this ValidationContext property that contains the object instance being validated. Unfortunately this attribute has been added only in .NET 4.0. So my question is: can I achieve the same functionality in .NET 3.5 SP1?
UPDATE:
It seems that FluentValidation already supports clientside validation and metadata in ASP.NET MVC 2.
Still it would be good to know though if data annotations could be used to validate dependent properties.
MVC2 comes with a sample "PropertiesMustMatchAttribute" that shows how to get DataAnnotations to work for you and it should work in both .NET 3.5 and .NET 4.0. That sample code looks like this:
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = true)]
public sealed class PropertiesMustMatchAttribute : ValidationAttribute
{
private const string _defaultErrorMessage = "'{0}' and '{1}' do not match.";
private readonly object _typeId = new object();
public PropertiesMustMatchAttribute(string originalProperty, string confirmProperty)
: base(_defaultErrorMessage)
{
OriginalProperty = originalProperty;
ConfirmProperty = confirmProperty;
}
public string ConfirmProperty
{
get;
private set;
}
public string OriginalProperty
{
get;
private set;
}
public override object TypeId
{
get
{
return _typeId;
}
}
public override string FormatErrorMessage(string name)
{
return String.Format(CultureInfo.CurrentUICulture, ErrorMessageString,
OriginalProperty, ConfirmProperty);
}
public override bool IsValid(object value)
{
PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(value);
object originalValue = properties.Find(OriginalProperty, true /* ignoreCase */).GetValue(value);
object confirmValue = properties.Find(ConfirmProperty, true /* ignoreCase */).GetValue(value);
return Object.Equals(originalValue, confirmValue);
}
}
When you use that attribute, rather than put it on a property of your model class, you put it on the class itself:
[PropertiesMustMatch("NewPassword", "ConfirmPassword", ErrorMessage = "The new password and confirmation password do not match.")]
public class ChangePasswordModel
{
public string NewPassword { get; set; }
public string ConfirmPassword { get; set; }
}
When "IsValid" gets called on your custom attribute, the whole model instance is passed to it so you can get the dependent property values that way. You could easily follow this pattern to create a date comparison attribute, or even a more general comparison attribute.
Brad Wilson has a good example on his blog showing how to add the client-side portion of the validation as well, though I'm not sure if that example will work in both .NET 3.5 and .NET 4.0.
I had this very problem and recently open sourced my solution:
http://foolproof.codeplex.com/
Foolproof's solution to the example above would be:
public class Event
{
[Required]
public DateTime? StartDate { get; set; }
[Required]
[GreaterThan("StartDate")]
public DateTime? EndDate { get; set; }
}
Instead of the PropertiesMustMatch the CompareAttribute that can be used in MVC3. According to this link http://devtrends.co.uk/blog/the-complete-guide-to-validation-in-asp.net-mvc-3-part-1:
public class RegisterModel
{
// skipped
[Required]
[ValidatePasswordLength]
[DataType(DataType.Password)]
[Display(Name = "Password")]
public string Password { get; set; }
[DataType(DataType.Password)]
[Display(Name = "Confirm password")]
[Compare("Password", ErrorMessage = "The password and confirmation do not match.")]
public string ConfirmPassword { get; set; }
}
CompareAttribute is a new, very useful validator that is not actually
part of
System.ComponentModel.DataAnnotations,
but has been added to the
System.Web.Mvc DLL by the team. Whilst
not particularly well named (the only
comparison it makes is to check for
equality, so perhaps EqualTo would be
more obvious), it is easy to see from
the usage that this validator checks
that the value of one property equals
the value of another property. You can
see from the code, that the attribute
takes in a string property which is
the name of the other property that
you are comparing. The classic usage
of this type of validator is what we
are using it for here: password
confirmation.
It took a little while since your question was asked, but if you still like metadata (at least sometimes), below there is yet another alternative solution, which allows you provide various logical expressions to the attributes:
[Required]
public DateTime? StartDate { get; set; }
[Required]
[AssertThat("StartDate != null && EndDate > StartDate")]
public DateTime? EndDate { get; set; }
It works for server as well as for client side. More details can be found here.
Because the methods of the DataAnnotations of .NET 3.5 don't allow you to supply the actual object validated or a validation context, you will have to do a bit of trickery to accomplish this. I must admit I'm not familiar with ASP.NET MVC, so I can't say how to do this exactly in conjunction with MCV, but you can try using a thread-static value to pass the argument itself. Here is an example with something that might work.
First create some sort of 'object scope' that allows you to pass objects around without having to pass them through the call stack:
public sealed class ContextScope : IDisposable
{
[ThreadStatic]
private static object currentContext;
public ContextScope(object context)
{
currentContext = context;
}
public static object CurrentContext
{
get { return context; }
}
public void Dispose()
{
currentContext = null;
}
}
Next, create your validator to use the ContextScope:
public class CustomValidationAttribute : ValidationAttribute
{
public override bool IsValid(object value)
{
Event e = (Event)ObjectContext.CurrentContext;
// validate event here.
}
}
And last but not least, ensure that the object is past around through the ContextScope:
Event eventToValidate = [....];
using (var scope new ContextScope(eventToValidate))
{
DataAnnotations.Validator.Validate(eventToValidate);
}
Is this useful?

Resources