Data Annotation only validates when using get accessor on field - c#

When validating data annotations, why does it only seem to validate (correctly) when using a get accessor on the property you want to validate?
Given the example below, the Name property will always be considered valid, even though it is unassigned, however the Address property will only be considered valid if it assigned a non-empty string value, all because it's using a get accessor. Why is this?
Surely TryValidateObject will basically just use test.Address to access the value whether it's through a get accessor or not.
class Program
{
static void Main(string[] args)
{
var test = new Test();
var _errors = new List<ValidationResult>();
bool isValid = Validator.TryValidateObject(
test, new ValidationContext(test), _errors, true);
}
}
public class Test
{
[Required]
public string Name; // Returns true even when unassigned / null / empty string
[Required]
public string Address { get; } // Only returns true when assigned a non-empty string
}

The reason is Validator.TryValidateObject will only check properties and class-level annotations, but your Name is a field, not a property (see here that documentation of Validator class does not mention fields). If you apply any other attribute (like Range) to a field - it also won't work with Validator.TryValidateObject. The reason why RequiredAttribute can be applied to a field is because RequiredAttribute is an entity on it's own and is not related\tied to Validator class in any way. Any other validation mechanism can validate public fields, it's just specific Validator class which does not.

Related

Get property default value as expression using reflection

In order to build a custom transpiler, I'm trying to get the default value of all the properties inside a class as an expression, and not as a value itself.
Let me bring you some examples to clarify what I'm trying to do and what I've done/tried/investigated so far.
Source code could be the following one:
const string DATETIME_NOW = "____DATETIME_NOW____";
public class Person {
[DefaultValue("Foo")]
public string Name { get; set; } = "Foo";
[DefaultValue(DateTime.Now)] // This is not doable: "An attribute argument must be a constant expression"
public DateTime DateOfBirth { get; set; } = DateTime.Now;
[DefaultValue(DATETIME_NOW)]
public string DateOfBirthStringed { get; set; } = DATETIME_NOW; // which acts like DateTime.Now.ToString()
}
The ultimate goal of the transpiler, is to obtain a Javascript class that looks like this:
class Person {
name: string = "Foo";
dateOfBirth: Date = new Date(Date.now());
dateOfBirthStringed : Date = Date.now();
}
My current, and working, implementation is the use of DefaultValue attribute with some constants strings used when the default value is an expression (e.g. DateOfBirthStringed).
What I'm doing is using reflection on Person, getting all the PropertyInfo, looking for their DefaultValue attribute, and then checking if the given default value are some fixed constants like DATETIME_NOW.
This works, but I've a couple of problems:
The type in attribute DefaultValue could be different from the type of the property.. No type check :(
If I only have the DefaultValue, when I write new Person(), the default values are not actually set from the attribute.
Therefore, I need to write the default value after { get; set; }, but:
Or I wrote both attribute and default value, but then I should manually mantain synchronized them.
I write only the default value, but then I've no way to get it with reflection.
About point 3.2, why I can't get the default value via reflection?
Suppose the Person class defined above; if I use reflection, I need to instantiate it, but once instantiated, Person.DateOfBirth has an actual DateTime value, and I cannot know it was coming from DateTime.Now.
Also, if Person would be abstract.. Well, no way to instantiate it.
So, basically, the only way I could perfectly transpile the code is to read the code as a tree, something like parseTreeCode(typeof(Person)). At that point, I should navigate the tree and be able to do everything I want.
I did find Roslyn, which allows me to parse C# code, but.. It parses "stringed" code, and not code that belongs to the same project. I thought "well, get the file where Person is defined, and parse the whole file", but unfortunately, once the program is running, I cannot get the file where Person is defined.. I mean, I can do typeof(Person).Assembly, and getting the Assembly.. But it would be an assembly, so it would not be good.
At this point, I'm thinking that there is no real solution for this, but maybe I'm missing some C# packages/features that could help me

C# [Required()] annotation doesn't throw exception when it should

I'm using [Required()] above a property in a simple class:
public class A
{
[Required()]
public string Str { get; set; }
public int Salary { get; set; }
}
In Main(), I create an instance of the class,
WITHOUT setting the property:
static void Main(string[] args)
{
A a = new A();
}
I expected to get an exception, because I didn't set any value
to Str property, but I don't get any.
Did I miss the purpose of [Required]?
Did I miss the purpose of [Required]?
Very much so. Let's read the docs:
The RequiredAttribute attribute specifies that when a field on a form is validated, the field must contain a value
So we're talking about validation specifically: it's part of the various classes inside the System.ComponentModel.DataAnnotations namespace, which are mostly to do with valiation.
Principally, look at the Validation class, which lets you validate the properties on an object, according to the attributes you've put on them. This infrastructure is used in various places, such as in ASP.NET, or EF.
The Required attribute is for validation (e.g. in ASP.NET), not for throwing runtime exceptions.

How to unset a property on a Delta object

I am using Azure Mobile App Services and in the PATCH method I receive as body an object of type Delta (see MSDN).
I receive some fields with null values that I would like to remove from the Delta input object: how can I do this action?
e.g. I have a JSON input like
{
"Content":"test",
"CreatedAt":null
...
}
this maps an Entity that inherits from Microsoft.Azure.Mobile.Server.EntityData
e.g.
public class MyBean : EntityData
{
public MyBean() { }
public string Content { get; set; }
}
I would like to remove the field "CreatedAt", which by the way is declared in the EntityData parent object, part of the Microsoft library (therefore I don't have direct access to it).
I don't think you should try to remove the CreatedAt, but instead, take the incoming Delta and create a new one. You could either include the fields you wanted or exclude the ones you don't want.
var newDelta = new Delta<MyBean>();
foreach(var fieldName in patchDelta.GetChangedPropertyNames()){
if(fieldName != "CreatedAt"){
if(patchDelta.TryGetPropertyValue(fieldName, out object fieldValue)){
newDelta.TrySetPropertyValue(fieldNAme,fieldValue);
}
}
}
If you are using the Newtonsoft.Json to serialize the Entity then you can use conditional serialization of a property.
To conditionally serialize a property, add a method that returns
boolean with the same name as the property and then prefix the method
name with ShouldSerialize. The result of the method determines whether
the property is serialized. If the method returns true then the
property will be serialized, if it returns false then the property
will be skipped.
public class MyBean : EntityData
{
public MyBean() { }
public string Content { get; set; }
public bool ShouldSerializeCreatedAt()
{
return false;
// Or you can add some condition to whether serialize the property or not on runtime
}
}

XML serialization and DefaultValue("") related problem in c#

my class property has default value which will be serialize.
public class DeclaredValue
{
[XmlElement(ElementName = "Amount", DataType = "double", IsNullable = false), DefaultValue(999)]
public double Amount { get; set; }
[XmlElement(ElementName = "Reference2", DataType = "string", IsNullable = false), DefaultValue("")]
public string Reference2 { get; set; }
}
so we create instance of DeclaredValue class and provide value for Reference2 property and do not assign anything for Amount. so when we serialize the class DeclaredValue then no tag found for amount in my xml. i mention default value for amount "999" then why it does not work in serialization. i want that if do not assign anything for amount then amoun tag should be there in my xml with default value.
to do this what way i need to decorate the amount property that it always comes with default value in xml after serialization if user do not assign anything to this property.
please guide me what i need to change in the code to get my desired output.
Per the note on MSDN:
A DefaultValueAttribute will not cause
a member to be automatically
initialized with the attribute's
value. You must set the initial value
in your code.
Somewhat surprisingly the DefaultValue only regulates the writing of an object, members that are equal to their DefaultValue will not be written out.
You must still initialize members before or after loading yourself, for example in the constructor.
Let me thoroughly describe what is happening.
When XmlSerializer Deserialize() method is called, it creates a new object using a default constructor. It doesn't apply any DefaultValueAttributes to this object, I beleave, because of assumption that default ctor should "know better" how to initialize values by default. From this point of view - it is logical.
XmlSerializer doesn't serialize members which values are the same as marked by DefaultValue attribute. From some point of view such behavior is driven by logic too.
But when you do not initialize members in ctor and call deserialize method, XmlSerializer see no corresponding xml field, but it see that the field/property has DefaultValueAttribute, serializer just leave such value (according to the assumption that the default constructor knows better how to initialize a class "by defaults"). And you've got your zeros.
Solution
To initialize a class members by these DefaultValueAttributes (sometimes it is very handy to have this initialization values just in place) you can use such simple method:
public YourConstructor()
{
LoadDefaults();
}
public void LoadDefaults()
{
//Iterate through properties
foreach (var property in GetType().GetProperties())
{
//Iterate through attributes of this property
foreach (Attribute attr in property.GetCustomAttributes(true))
{
//does this property have [DefaultValueAttribute]?
if (attr is DefaultValueAttribute)
{
//So lets try to load default value to the property
DefaultValueAttribute dv = (DefaultValueAttribute)attr;
try
{
//Is it an array?
if (property.PropertyType.IsArray)
{
//Use set value for arrays
property.SetValue(this, null, (object[])dv.Value);
}
else
{
//Use set value for.. not arrays
property.SetValue(this, dv.Value, null);
}
}
catch (Exception ex)
{
//eat it... Or maybe Debug.Writeline(ex);
}
}
}
}
}
This "public void LoadDefaults()", can be decorated as an Extension to object or use as some static method of a helper class.
As Henk Holterman mentionned, this attribut doesn't set the default value automatically. Its purpose is mostly to be used by visual designers to reset a property to its default value.
As others mentioned, the DefaultValue attribute doesn't initialize the property. You could use a simple loop to set all properties:
foreach (var property in GetType().GetProperties())
property.SetValue(this, ((DefaultValueAttribute)Attribute.GetCustomAttribute(
property, typeof(DefaultValueAttribute)))?.Value, null);
Even though ?.Value could return null, it works with non-nullable types, I tested this.
If only few of your properties have a default value, you should maybe only set the value if it is there.
If all properties should have a default value, remove the ? to get an error if you forgot one.
Most likely, arrays won't work, see MajesticRa's solution how to handle that.

Validator.ValidateObject with "validateAllProperties" to true stop at first error

I have a custom class (to be simple) :
using System;
using System.ComponentModel.DataAnnotations;
public class MyClass {
[Required]
public string Title { get; set;}
[Required]
public string Description { get; set;}
}
I want to validate this object, and get an exception with everything not correct.
If I do :
void Validate() {
var objectToValidate = new MyClass { }; // Both properties are null at this time
var ctx = new ValidationContext(objectToValidate, null, null);
Validator.ValidateObject(objectToValidate, ctx, true);
}
A ValidationException is thrown but it shows only the first error, even I specify true to the validateAllProperties parameter.
if I refactor a bit my code :
void Validate() {
var objectToValidate = new MyClass { }; // Both properties are null at this time
var ctx = new ValidationContext(objectToValidate, null, null);
var errors = new List<ValidationResult>();
var isValid = Validator.TryValidateObject(objectToValidate, ctx, errors, true);
if(!isValid) {
throw new AggregateException(
errors.Select((e)=>new ValidationException(e.ErrorMessage)
);
}
}
I can actually have all my errors.
Why does the first code snippet works as expected ? Did I do something wrong?
Validator.ValidateObject goes through all validation attributes and throws ValidationException for the first one that fails. I don't think one can get the list of all properties that failed this way.
The MSDN documentation is a bit confusing here, it says:
The ValidateObject method evaluates each ValidationAttribute attribute that is associated with the object type. If validateAllProperties is set to true, the method validates the property values of the object.
TryValidateObject method manual continues:
It also checks whether each property that is marked with RequiredAttribute is provided.
I confirm that it always checks all the Required attributes, regardless of validateAllProperties. But if you have, for example, a Range validation attribute it will only check it when validateAllProperties is true (because this is when it validates the property value). A bit confusing, but as you noticed, TryValidateObject shows all the failed properties and this is what I'm using for my project as well.

Categories