Home > Blockchain >  How to determine if a property is required?
How to determine if a property is required?

Time:02-11

Given a class something like this:

public class MyClass : ValidationValues
{
    public string Foo { get; set; }

    [Required(ErrorMessage = "Bar is required.")]
    public string Bar { get; set; }
    
    // and many more
}

public class ValidationValues
{
    public bool IsValid { get; set; } = true;
    public string InvalidReason { get; set; }
}

I need to determine if a property is required while looping over it as a generic list. By looking into the Watch, I've figured out one way, but it feels clunky, and I'm thinking it should be simpler.

For some context, this logic is inside of an Azure Function. So no Views, no MVC, etc. The function is a Blob Storage trigger that picks up a .CSV file with a | delimited list which gets deserialized into a List<MyClass>. We do not want to enforce the Required attributes at deserialization because we want more granular control.

So given a file like this:

value1 | |
value2 | something

What eventually gets sent back to the user is something like this:

[
    {
        "foo": "value1",
        "bar": "",
        "isValid": false,
        "InvalidReason" : "Bar is required"
    },
    {
        "foo": "value2",
        "bar": "something",
        "isValid": true,
        "InvalidReason" : ""
    }
]

Here's what I have now:

foreach (T item in itemList) // where 'itemList' is a List<T> and in this case T is MyClass
{
    foreach (PropertyInfo property in item.GetType().GetProperties())
    {
        if (property.CustomAttributes.ToList()[0].AttributeType.Name == "RequiredAttribute")
        {
             // validate, log, populate ValidationValues
        }
    }
}

This is the part I don't like:

property.CustomAttributes.ToList()[0].AttributeType.Name == "RequiredAttribute"

Sometimes when I figure out a coding challenge, I tell myself, "This is the way". But in this case, I'm pretty sure this isn't the way.

CodePudding user response:

You can rewrite that line using GetCustomAttibute:

using System.Reflection;
foreach (T item in itemList) // where 'itemList' is a List<T> and in this case T is MyClass
{
    foreach (PropertyInfo property in item.GetType().GetProperties())
    {
        var attribute = property.GetCustomAttibute<RequiredAttribute>();
    }
}

CodePudding user response:

Reflection is slow - or at least, relatively slow. So; the main important thing here is: don't do this per instance; you could either cache it per Type (from GetType(), or you could just use T and never even check .GetType() per instance, depending on your intent. This includes caching the properties that exist for a given type, and which are required. However, the real bonus points are to use meta-programming to emit - either at runtime, or at build-time via a "generator" - a method that *does exactly what you want, without any loops, tests, etc; i.e. in this case it might emit a method that does the equivalent of

void ValidateMyClass(MyClass obj)
{
    if (string.IsNullOrWhitespace(obj.Bar))
    {
        DoSomething("Bar is required.");
    }
}

This can be done in a variety of ways, including the Expression API, the emit API (ILGenerator), emitting C# and using CSharpCodeProvider, or the "generators" API.

  • Related