Apply ValidationAttributes from DataAnnotation to all elements of an IEnumerable - c#

I am using Microsoft.Extension.Options in ASP.NET Core 3.1 and I want to validate entries in an configuration file.
For this I want that, e.g. a RangeAttribute is applied to each element of an IEnumerable.
class MyConfiguration
{
[ApplyToItems]
[Range(1, 10)]
publlic IList<int> MyConfigValues { get; set; }
}
Or something like that. How do I write the ApplyToItems method?
As far as I know there is no way to retrieve the other ValidationAttributes while a possible ApplyToItems is validated.
Alternatively I could imagine something like:
[Apply(Range(1, 10)]
public List<int> MyConfigValues { get; set; }
but is that even valid syntax? How would I write an Attribute like Apply that takes other Attributes as parameter without falling back on something like
[Apply(new RangeAttribute(1, 10)]
which does not look nice.

To create a custom data annotation validator follow these gudelines:
Your class has to inherit from System.ComponentModel.DataAnnotations.ValidationAttribute class.
Override bool IsValid(object value) method and implement validation logic inside it.
That's it.
(from How to create Custom Data Annotation Validators)
So in your case it could be something like this:
public class ApplyRangeAttribute : ValidationAttribute
{
public int Minimum { get; set; }
public int Maximum { get; set; }
public ApplyRangeAttribute()
{
this.Minimum = 0;
this.Maximum = int.MaxValue;
}
public override bool IsValid(object value)
{
if (value is IList<int> list)
{
if (list.Any(i => i < Minimum || i > Maximum))
{
return false;
}
return true;
}
return false;
}
}
Edit
Here's how you would use it:
class MyConfiguration
{
[ApplyRange(Minimum = 1, Maximum = 10)]
public IList<int> MyConfigValues { get; set; }
}

Related

MVC 5 Data Annotation "Not Equal to Zero"

Probably I am missing something, but having the model below
public class MyModel
{
public double WhateverButNotZero { get; set; }
}
is there any MVC built-in DataAnnotation to validate the number as "everything but zero"?
Regex to the rescue:
public class MyModel
{
[RegularExpression("(.*[1-9].*)|(.*[.].*[1-9].*)")]
public double WhateverButNotZero { get; set; }
}
There is no build-in validation for that specifically, but you can create a custom attribute for it if you don't want to use regex as mentioned in other answers.
Create a class that extends the ValidationAttribute class
Override the IsValid(object value) method
Inside your validation method, convert the object to int and check if it's equal zero
public class NotZeroAttribute : ValidationAttribute
{
public override bool IsValid(object value) => (int)value != 0;
}
Then just use it on your class property like that:
public class MyModel
{
[NotZero]
public double WhateverButNotZero { get; set; }
}
try using regex annotation
public class MyModel
{
[RegularExpression("^(?!0*(\.0+)?$)(\d+|\d*\.\d+)$", ErrorMessage = "Not Equal to Zero")]
public double WhateverButNotZero { get; set; }
}
You can use RegularExpression DataAnnotation attribute.
[RegularExpression(#"^\d*[1-9]\d*$")]
public double WhateverButNotZero { get; set; }
Hopefully, What is the regex for “Any positive integer, excluding 0” will be helpful to find out the regular expression as per your need.

Validation strategy

I'm trying to build a series of attribute classes to make it easier for our development team to validate objects. The objects are POCO classes like this.
public class User
{
public string Name { get; set; }
public string Company { get; set; }
}
I want to decorate this model with a custom attribute.
public class User
{
[MustHaveValue]
public string Name { get; set; }
public string Company { get; set; }
}
Then I would create my own class implementing ValidationAttribute, the base class in .NET Framework, which belongs to System.ComponentModel.DataAnnotations.
public class MustHaveValueAttribute : ValidationAttribute
{
.
.
public override IsValid(object value)
{
// validation logic.
}
}
And then I can validate the User model whenever I want by making the set of instances like ValidationContext, List<ValidationResult>.
But in an enterprise environment, problems just can't be solved by a specific class. My validation scenario requires more complex and more flexible ways. Imagine that one of the required validation scenarios would something like this.
public class User
{
public string Name { get; set; }
public string Company { get; set; }
// Check if an item exists in this list.
[MustHaveMoreThanOneItem]
public IList<Client> Clients { get; set; }
}
Then I would need to make another attribute class
public class MustHaveMoreThanOneItemAttribute : ValidationAttribute
{
.
.
public override IsValid(object value)
{
// Let's assume this value is List<Client> for now.
// I know the exact type, so I'm going to cast it to List<Client> without further considerations
List<Client> clients = value as List<Client>;
if(clients.Count > 0) {
return true;
} else {
return false;
}
}
}
But the problem is that there are a lot of other models that have a nested list items. Try to imagine the time when I want to reuse the MustHaveMoreThanOneItem in one of the other models like...
public class Department
{
public string Name { get; set; }
[MustHaveMoreThanOneItem]
public IList<Employee> { get; set; }
}
You already know that it's not going to work because it was strongly typed only for List<Client>. So I decided to use Generic there to solve this problem.
But to my disappointment, the _Attribute interface doesn't support Generic. There's no additional implementation like _Attribute<T> : Attribute and therefore, no ValidationAttribute<T> alas!! I just cannot use Generic here !!
public class Department
{
public string Name { get; set; }
// No way to use this syntax.
[MustHaveMoreThanOneItem<Employee>]
public IList<Employee> { get; set; }
}
So I made a conclusion that Attribute must have been designed for a fixed set of validations like email format, card format, null check, and etc IMAO.
But I still want to use an attribute and give a lot of flexibilities in it to prevent the duplicated, verbose validation codes like this.
if(model.Clients.Count > 0) ...
if(model.Name != null) ...
if(model.Clients.GroupBy(x => x.Country == Country.USA).Count >= 1) ...
if(model.Clients.Where(x => x.CompanyName == Company.Google).ToList().Count > 1 ) ...
.
.
.
I want to pose two questions here.
If Attirbute supports Generic, this problem will be solved?
Is there any way to implement Generic Attribute? in order to use
[MustHaveMoreThanOneItem<Employee>] annotation on a class member?
You can generically check any object that implements IEnumerable like this:
public class MustHaveMoreThanOneItemAttribute : ValidationAttribute
{
public override bool IsValid(object value)
{
// omitted null checking
var enumerable = value as IEnumerable;
var enumerator = enumerable.GetEnumerator();
if (!enumerator.MoveNext())
{
return false;
}
if (!enumerator.MoveNext())
{
return false;
}
return true;
}
}
C# by definition does not support generic type attributes, although this has been requested actively for a long time:
https://github.com/dotnet/roslyn/issues/953
https://github.com/dotnet/csharplang/issues/124
However, you can still inject a type into a validation attribute via constructor. You then can use reflection or whatever you need to define your custom validation criteria.
public class MustHaveMoreThanOneItemAttribute : ValidationAttribute
{
public Type EnumerableType { get; }
public MustHaveMoreThanOneItemAttribute(Type t)
=> this.EnumerableType = typeof(ICollection<>).MakeGenericType(t);
public override bool IsValid(object value)
{
var count = this.EnumerableType.GetProperty("Count").GetValue(value) as int?;
return (count ?? 0) > 1;
}
}
Now this allows you to use something similar to your goal:
public class Department
{
public string Name { get; set; }
[MustHaveMoreThanOneItem(typeof(Employee))]
public IList<Employee> { get; set; }
}

Creating objects that initialize themselves based on a configuration string

I am coding a bunch of the following type of classes and it just seems a bit smelly to me. Basically I want to deserialze based on some json configuration of properties as well as serialize it for storage. I thought the following method would work well since I don't want to stipulate that the serialization/deserialization has to be json etc.
The code looks like this for a simple object:
public class IntegerDatasourceInstanceOptions
{
public int Start { get; set; }
public int Count { get; set; }
public IntegerDatasourceInstanceOptions()
{
}
public IntegerDatasourceInstanceOptions(string config)
{
var options = JsonConvert.DeserializeObject<IntegerDatasourceInstanceOptions>(config);
if (options != null)
{
Start = options.Start;
Count = options.Count;
}
}
public override string ToString()
{
return JsonConvert.SerializeObject(this);
}
}
Is this the correct way to go about this or should I use ISerializable instead?
I want to eliminate having to update all of the properties in the constructor. It's fine for a couple of properties in this case but if I have one with 30 properties it becomes a bit of a nightmare
I guess I'm just looking for some feedback as to whether this is the best way to go or not.
I tend to use a static method in instances like this, for example:
public class IntegerDatasourceInstanceOptions
{
public int Start { get; set; }
public int Count { get; set; }
public IntegerDatasourceInstanceOptions()
{
}
public override string ToString()
{
return JsonConvert.SerializeObject(this);
}
public static IntegerDatasourceInstanceOptions Create(string config)
{
return JsonConvert.DeserializeObject<IntegerDatasourceInstanceOptions>(config);
}
}
You can then just do:
var options = IntegerDatasourceInstanceOptions.Create("{...}");

ASP.NET MVC: Custom Validation by DataAnnotation

I have a Model with 4 properties which are of type string. I know you can validate the length of a single property by using the StringLength annotation. However I want to validate the length of the 4 properties combined.
What is the MVC way to do this with data annotation?
I'm asking this because I'm new to MVC and want to do it the correct way before making my own solution.
You could write a custom validation attribute:
public class CombinedMinLengthAttribute: ValidationAttribute
{
public CombinedMinLengthAttribute(int minLength, params string[] propertyNames)
{
this.PropertyNames = propertyNames;
this.MinLength = minLength;
}
public string[] PropertyNames { get; private set; }
public int MinLength { get; private set; }
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var properties = this.PropertyNames.Select(validationContext.ObjectType.GetProperty);
var values = properties.Select(p => p.GetValue(validationContext.ObjectInstance, null)).OfType<string>();
var totalLength = values.Sum(x => x.Length) + Convert.ToString(value).Length;
if (totalLength < this.MinLength)
{
return new ValidationResult(this.FormatErrorMessage(validationContext.DisplayName));
}
return null;
}
}
and then you might have a view model and decorate one of its properties with it:
public class MyViewModel
{
[CombinedMinLength(20, "Bar", "Baz", ErrorMessage = "The combined minimum length of the Foo, Bar and Baz properties should be longer than 20")]
public string Foo { get; set; }
public string Bar { get; set; }
public string Baz { get; set; }
}
Self validated model
Your model should implement an interface IValidatableObject. Put your validation code in Validate method:
public class MyModel : IValidatableObject
{
public string Title { get; set; }
public string Description { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (Title == null)
yield return new ValidationResult("*", new [] { nameof(Title) });
if (Description == null)
yield return new ValidationResult("*", new [] { nameof(Description) });
}
}
Please notice: this is a server-side validation. It doesn't work on client-side. You validation will be performed only after form submission.
ExpressiveAnnotations gives you such a possibility:
[Required]
[AssertThat("Length(FieldA) + Length(FieldB) + Length(FieldC) + Length(FieldD) > 50")]
public string FieldA { get; set; }
To improve Darin's answer, it can be bit shorter:
public class UniqueFileName : ValidationAttribute
{
private readonly NewsService _newsService = new NewsService();
public override bool IsValid(object value)
{
if (value == null) { return false; }
var file = (HttpPostedFile) value;
return _newsService.IsFileNameUnique(file.FileName);
}
}
Model:
[UniqueFileName(ErrorMessage = "This file name is not unique.")]
Do note that an error message is required, otherwise the error will be empty.
Background:
Model validations are required for ensuring that the received data we receive is valid and correct so that we can do the further processing with this data. We can validate a model in an action method. The built-in validation attributes are Compare, Range, RegularExpression, Required, StringLength. However we may have scenarios wherein we required validation attributes other than the built-in ones.
Custom Validation Attributes
public class EmployeeModel
{
[Required]
[UniqueEmailAddress]
public string EmailAddress {get;set;}
public string FirstName {get;set;}
public string LastName {get;set;}
public int OrganizationId {get;set;}
}
To create a custom validation attribute, you will have to derive this class from ValidationAttribute.
public class UniqueEmailAddress : ValidationAttribute
{
private IEmployeeRepository _employeeRepository;
[Inject]
public IEmployeeRepository EmployeeRepository
{
get { return _employeeRepository; }
set
{
_employeeRepository = value;
}
}
protected override ValidationResult IsValid(object value,
ValidationContext validationContext)
{
var model = (EmployeeModel)validationContext.ObjectInstance;
if(model.Field1 == null){
return new ValidationResult("Field1 is null");
}
if(model.Field2 == null){
return new ValidationResult("Field2 is null");
}
if(model.Field3 == null){
return new ValidationResult("Field3 is null");
}
return ValidationResult.Success;
}
}
Hope this helps. Cheers !
References
Code Project - Custom Validation Attribute in ASP.NET MVC3
Haacked - ASP.NET MVC 2 Custom Validation
A bit late to answer, but for who is searching.
You can easily do this by using an extra property with the data annotation:
public string foo { get; set; }
public string bar { get; set; }
[MinLength(20, ErrorMessage = "too short")]
public string foobar
{
get
{
return foo + bar;
}
}
That's all that is too it really. If you really want to display in a specific place the validation error as well, you can add this in your view:
#Html.ValidationMessage("foobar", "your combined text is too short")
doing this in the view can come in handy if you want to do localization.
Hope this helps!

Custom Attribute For Specifying Display Format

Let me start by saying that I don't know that this specifically requires a custom attribute, but the DisplayFormatAttribute most closely matches the intent I am looking for.
What I Would Like
I would like to be able to specify a string format for properties of a class like so:
public class TestAttribute
{
[CustomDisplayFormatAttribute(DataFormatString = "{0}")]
public int MyInt { get; set; }
[CustomDisplayFormatAttribute(DataFormatString = "{0:0.000}")]
public float MyFloat { get; set; }
[CustomDisplayFormatAttribute(DataFormatString = "{0:0.0}")]
public float MyFloat2 { get; set; }
[CustomDisplayFormatAttribute(DataFormatString = "{0:dd/MM/yyyy}")]
public DateTime MyDateTime { get; set; }
}
...and be able to use it like so:
TestAttribute t = new TestAttribute()
{
MyDateTime = DateTime.Now,
MyFloat = 1.2345678f,
MyFloat2 = 1.2345678f,
MyInt = 5
};
Console.WriteLine(t.MyDateTime.ToFormattedString());
Console.WriteLine(t.MyFloat.ToFormattedString());
Console.WriteLine(t.MyFloat2.ToFormattedString());
Console.WriteLine(t.MyInt.ToFormattedString());
What I Have Done So Far
I have successfully created the custom attribute CustomDisplayFormatAttribute and have applied it to my elements, however I am unable to get that attribute out without knowledge of my TestAttribute class.
My first thought was to use an extension method to handle it, hence the ToFormattedString() function.
That being said, ideally I would be able to call a function like ToFormattedString() and have it handle looking up the display format and applying the value to it.
My Questions
Is this possible using C#
How can I get this (or similar) functionality.
It is not possible to retrieve the TestAttribute class or its properties when you are in the ToFormattedString() method. An alternative would be to pass the method an extra argument which is an expression to get the property. I've heard that processing Linq expression is expensive, you would need to test if this is true in your case:
public interface IHaveCustomDisplayFormatProperties
{
}
public class TestAttribute : IHaveCustomDisplayFormatProperties
{
[CustomDisplayFormatAttribute(DataFormatString = "{0}")]
public int MyInt { get; set; }
[CustomDisplayFormatAttribute(DataFormatString = "{0:0.000}")]
public float MyFloat { get; set; }
[CustomDisplayFormatAttribute(DataFormatString = "{0:0.0}")]
public float MyFloat2 { get; set; }
[CustomDisplayFormatAttribute(DataFormatString = "{0:dd/MM/yyyy}")]
public DateTime MyDateTime { get; set; }
}
public static class IHaveCustomDisplayFormatPropertiesExtensions
{
public static string FormatProperty<T, U>(this T me, Expression<Func<T, U>> property)
where T : IHaveCustomDisplayFormatProperties
{
return null; //TODO: implement
}
}
which could be used like this:
TestAttribute t = new TestAttribute()
{
MyDateTime = DateTime.Now,
MyFloat = 1.2345678f,
MyFloat2 = 1.2345678f,
MyInt = 5
};
Console.WriteLine(t.FormatProperty(x => x.MyDateTime));
Console.WriteLine(t.FormatProperty(x => x.MyFloat));
Console.WriteLine(t.FormatProperty(x => x.MyFloat2));
Console.WriteLine(t.FormatProperty(x => x.MyInt));

Categories

Resources