DataAnnotations "NotRequired" attribute - c#

I've a model kind of complicated.
I have my UserViewModel which has several properties and two of them are HomePhone and WorkPhone. Both of type PhoneViewModel. In PhoneViewModel I have CountryCode, AreaCode and Number all strings. I want to make the CountryCode optional but AreaCode and Number mandatory.
This works great. My problem is that in the UserViewModel WorkPhone is mandatory, and HomePhone is not.
Is there anyway I can dissable Require attributs in PhoneViewModel by setting any attributes in HomeWork property?
I've tried this:
[ValidateInput(false)]
but it is only for classes and methods.
Code:
public class UserViewModel
{
[Required]
public string Name { get; set; }
public PhoneViewModel HomePhone { get; set; }
[Required]
public PhoneViewModel WorkPhone { get; set; }
}
public class PhoneViewModel
{
public string CountryCode { get; set; }
public string AreaCode { get; set; }
[Required]
public string Number { get; set; }
}

[UPDATED on 5/24/2012 to make the idea more clear]
I'm not sure this is the right approach but I think you can extend the concept and can create a more generic / reusable approach.
In ASP.NET MVC the validation happens at the binding stage. When you are posting a form to the server the DefaultModelBinder is the one that creates model instances from the request information and add the validation errors to the ModelStateDictionary.
In your case, as long as the binding happens with the HomePhone the validations will fire up and I think we can't do much about this by creating custom validation attributes or similar kind.
All I'm thinking is not to create model instance at all for HomePhone property when there are no values available in the form (the areacode, countrycode and number or empty), when we control the binding we control the validation, for that, we have to create a custom model binder.
In the custom model binder we are checking if the property is HomePhone and if the form contains any values for it's properties and if not we don't bind the property and the validations won't happen for HomePhone. Simply, the value of HomePhone will be null in the UserViewModel.
public class CustomModelBinder : DefaultModelBinder
{
protected override void BindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor)
{
if (propertyDescriptor.Name == "HomePhone")
{
var form = controllerContext.HttpContext.Request.Form;
var countryCode = form["HomePhone.CountryCode"];
var areaCode = form["HomePhone.AreaCode"];
var number = form["HomePhone.Number"];
if (string.IsNullOrEmpty(countryCode) && string.IsNullOrEmpty(areaCode) && string.IsNullOrEmpty(number))
return;
}
base.BindProperty(controllerContext, bindingContext, propertyDescriptor);
}
}
Finally you have to register the custom model binder in global.asax.cs.
ModelBinders.Binders.Add(typeof(UserViewModel), new CustomModelBinder());
So now of you have an action that takes UserViewModel as parameter,
[HttpPost]
public Action Post(UserViewModel userViewModel)
{
}
Our custom model binder come into play and of form doesn't post any values for the areacode, countrycode and number for HomePhone, there won't be any validation errors and the userViewModel.HomePhone is null. If the form posts atleast any one of the value for those properties then the validation will happen for HomePhone as expected.

I've been using this amazing nuget that does dynamic annotations: ExpressiveAnnotations
It allows you to do things that weren't possible before such as
[AssertThat("ReturnDate >= Today()")]
public DateTime? ReturnDate { get; set; }
or even
public bool GoAbroad { get; set; }
[RequiredIf("GoAbroad == true")]
public string PassportNumber { get; set; }
Update: Compile annotations in a unit test to ensure no errors exist
As mentioned by #diego this might be intimidating to write code in a string, but the following is what I use to Unit Test all validations looking for compilation errors.
namespace UnitTest
{
public static class ExpressiveAnnotationTestHelpers
{
public static IEnumerable<ExpressiveAttribute> CompileExpressiveAttributes(this Type type)
{
var properties = type.GetProperties()
.Where(p => Attribute.IsDefined(p, typeof(ExpressiveAttribute)));
var attributes = new List<ExpressiveAttribute>();
foreach (var prop in properties)
{
var attribs = prop.GetCustomAttributes<ExpressiveAttribute>().ToList();
attribs.ForEach(x => x.Compile(prop.DeclaringType));
attributes.AddRange(attribs);
}
return attributes;
}
}
[TestClass]
public class ExpressiveAnnotationTests
{
[TestMethod]
public void CompileAnnotationsTest()
{
// ... or for all assemblies within current domain:
var compiled = Assembly.Load("NamespaceOfEntitiesWithExpressiveAnnotations").GetTypes()
.SelectMany(t => t.CompileExpressiveAttributes()).ToList();
Console.WriteLine($"Total entities using Expressive Annotations: {compiled.Count}");
foreach (var compileItem in compiled)
{
Console.WriteLine($"Expression: {compileItem.Expression}");
}
Assert.IsTrue(compiled.Count > 0);
}
}
}

I wouldn't go with the modelBinder; I'd use a custom ValidationAttribute:
public class UserViewModel
{
[Required]
public string Name { get; set; }
public HomePhoneViewModel HomePhone { get; set; }
public WorkPhoneViewModel WorkPhone { get; set; }
}
public class HomePhoneViewModel : PhoneViewModel
{
}
public class WorkPhoneViewModel : PhoneViewModel
{
}
public class PhoneViewModel
{
public string CountryCode { get; set; }
public string AreaCode { get; set; }
[CustomRequiredPhone]
public string Number { get; set; }
}
And then:
[AttributeUsage(AttributeTargets.Property]
public class CustomRequiredPhone : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
ValidationResult validationResult = null;
// Check if Model is WorkphoneViewModel, if so, activate validation
if (validationContext.ObjectInstance.GetType() == typeof(WorkPhoneViewModel)
&& string.IsNullOrWhiteSpace((string)value) == true)
{
this.ErrorMessage = "Phone is required";
validationResult = new ValidationResult(this.ErrorMessage);
}
else
{
validationResult = ValidationResult.Success;
}
return validationResult;
}
}
If it is not clear, I'll provide an explanation but I think it's pretty self-explanatory.

Just some observation: the following code couse a problem if the binding is more than simple filed. I you have a case that in object have nested object it going to skip it and caouse that some filed not been binded in nested object.
Possible solution is
protected override void BindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor)
{
if (!propertyDescriptor.Attributes.OfType<RequiredAttribute>().Any())
{
var form = controllerContext.HttpContext.Request.Form;
if (form.AllKeys.Where(k => k.StartsWith(string.Format(propertyDescriptor.Name, "."))).Count() > 0)
{
if (form.AllKeys.Where(k => k.StartsWith(string.Format(propertyDescriptor.Name, "."))).All(
k => string.IsNullOrWhiteSpace(form[k])))
return;
}
}
base.BindProperty(controllerContext, bindingContext, propertyDescriptor);
}
much thanks to Altaf Khatri

Related

Apply data annotations validation to all properties of same primitive datatype in a viewmodel

I searched google and SO for my scenario but not able to find an answer. I want to create a regular expression data annotation validation in a viewmodel class properties which are of double type. Since I have around 20 properties of type double. So I want to create a custom regular expression validation and apply to all double type properties without explicitly specifying on each property like:
[RegularExpression(#"^[0-9]{1,6}(\.[0-9]{1,2})?$", ErrorMessage ="Invalid Input")]
public double Balance { get; set; }
I am expecting thing like this:
[ApplyRegExpToAllDoubleTypes]
public class MyModel
{
public double Balance { get; set; }
public double InstallmentsDue { get; set; }
}
That's an interesting question. Here is how it can be done:
Define a custom ValidationAttribute and apply it at the class level by setting AttributeTargets.Class. Inside the ValidationAttribute, use reflection to get the double properties, then validate the value of each property. If any of the validations fail, return a validation failed message.
[ApplyRegExpToAllDoubleTypes]
public class MyModel {
public double Balance { get; set; }
public double InstallmentsDue { get; set; }
public string Name { get; set; }
public int Age { get; set; }
}
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
public class ApplyRegExpToAllDoubleTypes : ValidationAttribute {
protected override ValidationResult IsValid(object currentObject, ValidationContext validationContext) {
if (currentObject == null) {
return new ValidationResult("Object can't be null");
}
var properties = validationContext.ObjectType.GetProperties().Where(x => x.PropertyType == typeof(double));
foreach (var property in properties) {
//Here I compare the double property value against '5'
//Replace the following with the custom regex check
if ((double)property.GetValue(currentObject) < 5) {
return new ValidationResult("All double properties must be greater than 5");
}
}
return ValidationResult.Success;
}
}

Custom validation - Required only when method is put

I have two API endpoints, post and put:
[HttpPost]
[Route("projects")]
public IHttpActionResult Create([FromBody] ProjectDTO projectDto)
{
if (ModelState.IsValid)
{
var project = MappingConfig.Map<ProjectDTO, Project>(projectDto);
_projectService.Create(project);
return Ok("Project successfully created.");
}
else
{
return BadRequest(ModelState);
}
}
[HttpPut]
[Route("projects")]
public IHttpActionResult Edit([FromBody] ProjectDTO projectDto)
{
if (ModelState.IsValid)
{
var project = _projectService.GetById(projectDto.ProjectId);
if (project == null)
return NotFound();
project = Mapper.Map(projectDto, project);
_projectService.Update(project);
return Ok("Project successfully edited.");
}
else
{
return BadRequest(ModelState);
}
}
DTO looks like this:
public class ProjectDTO
{
public int ProjectId { get; set; }
[Required(ErrorMessage = "Name field is required.")]
public string Name { get; set; }
[Required(ErrorMessage = "IsInternal field is required.")]
public bool IsInternal { get; set; }
}
I'm trying to validate field ProjectId. ProjectId field should be required only in HttpPut method when I'm editing my entity.
Is it possible to make custom validation RequiredIfPut or something like that where that field will be required only when editing, but not when creating?
Here is what you can do using custom validation attribute:
public class RequiredWhenPutAttribute : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if (System.Web.HttpContext.Current.Request.HttpMethod == "PUT")
{
var obj = (ProjectDTO)validationContext.ObjectInstance;
if (obj.ProjectId == null)
{
return new ValidationResult("Project Id is Required");
}
}
else
{
return ValidationResult.Success;
}
}
}
public class ProjectDTO
{
[RequiredWhenPut]
public int? ProjectId { get; set; }
}
Update:
In response to your comment, in order to make the solution more general, you can add a ParentDto class from which other classes are inherited and the shared property needs to be in the ParentDto class, as the following:
public class RequiredWhenPutAttribute : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if (HttpContext.Current.Request.HttpMethod == "PUT")
{
var obj = (ParentDto)validationContext.ObjectInstance;
if (obj.Id == null)
{
return new ValidationResult(ErrorMessage);
}
}
else
{
return ValidationResult.Success;
}
}
}
public class ParentDto
{
[RequiredWhenPut(ErrorMessage = "Id is required")]
public int? Id { get; set; }
}
public class UserDTO : ParentDto
{
// properties
}
public class ProjectTypeDTO : ParentDto
{
// properties
}
public class ProjectDTO : ParentDto
{
// properties
}
That's one of the reasons, why I use different classes for both case (e.g. ProjectUpdateRequestDto and ProjectCreateRequestDto). Maybe both can be derived from a common base class, but even if not it makes it a lot easier to distinguish between both scenarios.
Also security could be a problem, cause if you use the same class it could be possible that the create requests already contains an id and if your create method simply maps the DTO to an database entity you could overwrite existing data. This means you have to be careful and think about such scenarios. If your create DTO class doesn't have that property it can't be set from the mapper and can't be malused.

C# Unable to find other properties in custom validation attribute

Trying to make my own Validation Attribute that uses a property name to find another property.
Currently, I am having problems finding the other property. It seems I am unable to find this property (or in fact any properties).
The check for property == null is always coming up as true.
Any ideas why I wouldn't be able to find properties?
This is the custom filter I have made
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var property = validationContext.ObjectInstance.GetType().GetProperty(PropertyName);
if (property == null)
{
return new ValidationResult(string.Format(
"Unknown property {0}",
new[] { PropertyName }
));
}
var propertyValue = property.GetValue(validationContext.ObjectInstance);
// Just for testing purposes.
return new ValidationResult(ErrorMessage);
}
This is the model I am using behind my razor view.
public class OrganisationDetailsModel : PageModel
{
private readonly FormStateContext _context;
public OrganisationDetailsModel(FormStateContext context)
{
_context = context;
}
[BindProperty]
[RegularExpression(pattern: "(yes|no)")]
[Required(ErrorMessage = "Please select if you are registered on companies house")]
public string CompanyHouseToggle { get; set; }
[BindProperty]
[StringLength(60, MinimumLength = 3)]
[RequiredIf("CompanyHouseToggle")]
public string CompanyNumber { get; set; }
[BindProperty]
[StringLength(60, MinimumLength = 3)]
[Required(ErrorMessage = "Enter your organisation name")]
public string OrganisationName { get; set; }
[BindProperty]
[RegularExpression(pattern: "(GB)?([0-9]{9}([0-9]{3})?|[A-Z]{2}[0-9]{3})", ErrorMessage = "This VAT number is not recognised")]
[Required(ErrorMessage = "Enter your vat number")]
public string VatNumber { get; set; }
public void OnGet()
{
}
public IActionResult OnPost()
{
if (!ModelState.IsValid)
{
return Page();
}
return RedirectToPage("ApplicantDetails");
}
I appreciate the fact that the custom validation attribute doesn't really do anything at the moment but that is becuase I have become stuck on this issue.
Thanks for any help.
Let's use the following code snippet to help explain what is going on here:
protected override ValidationResult IsValid(object value, ValidationContext ctx)
{
var typeFullName = ctx.ObjectInstance.GetType().FullName;
...
}
In this example, you might expect typeFullName to be XXX.OrganisationDetailsModel, but it isn't: it's actually System.String (the type of the property you’re trying to validate). System.String clearly does not have a property named e.g. CompanyHouseToggle and so GetProperty rightly returns null.
I've not seen many cases where [BindProperty] has been used more than once on a PageModel. It's certainly possible, but it appears that each property is treated as being individual and that the PageModel itself is not being validated.
In order to work around this, you can just turn your individual properties into a complex type and use that instead. The docs and examples use an inline class for this, inside of the PageModel class. Here's an example of an updated OrganisationDetailsModel class:
public class OrganisationDetailsModel : PageModel
{
...
[BindProperty]
public InputModel Input { get; set; }
public void OnGet() { }
public IActionResult OnPost()
{
if (!ModelState.IsValid)
return Page();
return RedirectToPage("ApplicantDetails");
}
public class InputModel
{
[RegularExpression(pattern: "(yes|no)")]
[Required(ErrorMessage = "Please select if you are registered on companies house")]
public string CompanyHouseToggle { get; set; }
[StringLength(60, MinimumLength = 3)]
[RequiredIf("CompanyHouseToggle")]
public string CompanyNumber { get; set; }
...
}
}
This includes the following changes:
Creation of an InputModel class to hold all properties.
Removal of all other properties that have now moved into InputModel.
Addition of an Input property of type InputModel, that gets bound using [BindProperty].
Removed [BindProperty] from the original properties that have now been moved.
The last step is to replace any usage of e.g. CompanyNumber with Input.CompanyNumber in the PageModel's corresponding .cshtml and to ensure you use the Input. prefix when accessing properties within the PageModel class itself.
By the doc. getProperties() only return publics.
https://learn.microsoft.com/en-us/dotnet/api/system.type.getproperties?view=netframework-4.7.2
So if want to get non-public properties. Find on belows.
Get protected property value of base class using reflections
protected override ValidationResult IsValid(object value, ValidationContext context) {
var property = context.ObjectType.getProperty(context.MemberName);
// TODO
return ValidationResult.Success;
}

Modelbinding a interface property

I'm using MVC.NET 5.2.3 and try to post a model to a controller where the model contains several interfaces. This causes .NET to throw this exception:
Cannot create an instance of an interface
I understand that it's because I use interfaces in my model (ITelephone). My models are as such:
public class AddContactPersonForm
{
public ExternalContactDto ExternalContact { get; set; }
public OrganizationType OrganizationType { get; set; }
}
public class ExternalContactDto
{
public int? Id { get; set; }
public string Name { get; set; }
public string Title { get; set; }
public IList<ITelephone> TelephoneNumbers { get; set; }
}
public interface ITelephone
{
string TelephoneNumber { get; set; }
}
public class TelephoneDto : ITelephone
{
public string TelephoneNumber { get; set; }
}
It works fine if I use the TelephoneDto class instead of the ITelephone interface.
I understand that I need to use a ModelBinder, which is fine. But I really just want to say what kind of instance the modelbinder should create, instead of mapping the entire model manually.
The answer #jonathanconway gave in this question is close to what I want to do.
Custom model binder for a property
But I really would like to combine this with the simplicity of simply telling the defaultbinder what type to use for a specific interface. Sort of the same way you can use the KnownType-attribute. The defaultbinder obviously knows how to map the model as long as it knows which class it should create.
How can I tell the DefaultModelBinder what class it should use to deserialize the interface and then bind it? It currently crashes because the model that is posted (AddContactPersonForm) contains a "complex" model (ExternalContactDto) which has the interface ITelephone.
This is what I got so far.
public class ContactPersonController : Controller
{
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult AddContactPerson([ModelBinder(typeof(InterfaceModelBinder))] AddContactPersonForm addContactPersonForm)
{
// Do something with the model.
return View(addContactPersonForm);
}
}
public class InterfaceModelBinder : DefaultModelBinder
{
protected override void BindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext,
PropertyDescriptor propertyDescriptor)
{
var propertyBinderAttribute = TryFindPropertyBinderAttribute(propertyDescriptor);
if (propertyBinderAttribute != null)
{
// Never occurs since the model is nested.
var type = propertyBinderAttribute.ActualType;
var model = Activator.CreateInstance(type);
bindingContext.ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, type);
base.BindProperty(controllerContext, bindingContext, propertyDescriptor);
return;
}
// Crashed here since because:
// Cannot create an instance of an interface. Object type 'NR.Delivery.Contract.Models.ITelephone'.
base.BindProperty(controllerContext, bindingContext, propertyDescriptor);
}
private InterfaceBinderAttribute TryFindPropertyBinderAttribute(PropertyDescriptor propertyDescriptor)
{
return propertyDescriptor.Attributes
.OfType<InterfaceBinderAttribute>()
.FirstOrDefault();
}
}
public class ExternalContactDto
{
public int? Id { get; set; }
public string Name { get; set; }
public string Title { get; set; }
[InterfaceBinder(typeof(List<TelephoneDto>))]
public IList<ITelephone> TelephoneNumbers { get; set; }
}
public class InterfaceBinderAttribute : Attribute
{
public Type ActualType { get; private set; }
public InterfaceBinderAttribute(Type actualType)
{
ActualType = actualType;
}
}
I think you have missed something:
The ViewModel is a non-visual class
All domain type concerns should be mapped to ViewModel classes. there should be no need for interfaces in your ViewModels as they should all be specific to the page/view that you are rendering.
If you plan to have the same data on multiple pages - You can still use classes - just use partial views, or child actions.
If you need more complex Model's then use inheritance or composition techniques .
Just use classes to avoid this issue altogether.
I think that you need to override a CreateModel method of model binder so you could create a model with correctly instantiated properties. You will have to use some reflection in order to dynamically create property values based on InterfaceBinderAttribute.ActualType property. So something like that should work:
protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)
{
var model = Activator.CreateInstance(modelType);
var properties = modelType.GetProperties(BindingFlags.Public|BindingFlags.Instance);
foreach(var property in properties)
{
var attribute = property.Attributes.OfType<InterfaceBinderAttribute>().FirstOrDefault();
if(attribute != null)
{
var requiredType = (attribute as InterfaceBinderAttribute).ActualType;
property.SetValue(model, Activator.CreateInstance(requiredType ), null);
}
}
return model;
}

Asp.net Web Api nested model validation

I'm running in to a bit of a problem in asp.net web api's model binding and validation (via data annotations).
It seems like if i have a model with property such as
Dictionary<string, childObject> obj { get; set; }
the childObject's validations don't seem to trigger. The data is bound from json with Json.Net serializer.
Is there some workaround or fix to this? Or have I misunderstood something else related to this?
I can't help but wonder why this doesn't result in errors:
public class Child
{
[Required]
[StringLength(10)]
public string name;
[Required]
[StringLength(10)]
public string desc;
}
//elsewhere
Child foo = new Child();
foo.name = "hellowrodlasdasdaosdkasodasasdasdasd";
List<ValidationResult> results = new List<ValidationResult>();
Validator.TryValidateObject(foo, new ValidationContext(foo), results, true);
// results.length == 0 here.
Oh god. I had forgotten to declare properties instead of fields.
There are 2 ways you can setup validation of the Dictionary Values. If you don't care about getting all the errors but just the first one encountered you can use a custom validation attribute.
public class Foo
{
[Required]
public string RequiredProperty { get; set; }
[ValidateDictionary]
public Dictionary<string, Bar> BarInstance { get; set; }
}
public class Bar
{
[Required]
public string BarRequiredProperty { get; set; }
}
public class ValidateDictionaryAttribute : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if (!IsDictionary(value)) return ValidationResult.Success;
var results = new List<ValidationResult>();
var values = (IEnumerable)value.GetType().GetProperty("Values").GetValue(value, null);
values.OfType<object>().ToList().ForEach(item => Validator.TryValidateObject(item, new ValidationContext(item, null, validationContext.Items), results));
Validator.TryValidateObject(value, new ValidationContext(value, null, validationContext.Items), results);
return results.FirstOrDefault() ?? ValidationResult.Success;
}
protected bool IsDictionary(object value)
{
if (value == null) return false;
var valueType = value.GetType();
return valueType.IsGenericType && valueType.GetGenericTypeDefinition() == typeof (Dictionary<,>);
}
}
The other way is to create your own Dictionary as an IValidatableObject and do the validation in that. This solution gives you the ability to return all the errors.
public class Foo
{
[Required]
public string RequiredProperty { get; set; }
public ValidatableDictionary<string, Bar> BarInstance { get; set; }
}
public class Bar
{
[Required]
public string BarRequiredProperty { get; set; }
}
public class ValidatableDictionary<TKey, TValue> : Dictionary<TKey, TValue>, IValidatableObject
{
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
var results = new List<ValidationResult>();
Values.ToList().ForEach(item => Validator.TryValidateObject(item, new ValidationContext(item, null, validationContext.Items), results));
return results;
}
}
Validation always passes on fields because attributes can only be applied to properties. You need to change the fields name and desc into properties using auto implemented getter and setters.
These should then look something like
public string name { get; set; }

Categories