C# access custom attributes of a partial class on MVC with EF - c#

My situation is the following:
I'm coding a MVC website on Visual Studio 2013 using database-first approach with Entity Framework.
EF automatically generates the models. But I need to add custom attributes (~NOT~ necessarily for data validation but also for internal processes) and, via reflection, access those custom attributes.
Let's say I have
public partial class Application {
public int AppID {get; set;}
public string Name {get; set;}
//etc...
}
I've tried the following approaches:
• On a different file I continue the partial class:
public partial class Application {
[MyAttributeOne]
public int AppID { get; set; }
[DataType(DataType.Text)]
[MyAttributeTwo]
public string Name { get; set; }
}
• Use the MetaData class
public class ApplicationMetadata {
[MyAttributeOne]
public int SolutionID { get; set; }
[DataType(DataType.Text)]
[MyAttributeTwo]
public string Name { get; set; }
}
[MetadataType(typeof(ApplicationMetadata))]
public partial class Application { }
• Inherit the class with attributes:
public class ApplicationMetadata {
[MyAttributeOne]
public int SolutionID { get; set; }
[DataType(DataType.Text)]
[MyAttributeTwo]
public string Name { get; set; }
}
public partial class Application : ApplicationMetadata { }
• And the 'Buddy class' approach where I do basically the previous 2 approaches but instead I define the class with the attributes inside the 'Application' class.
Am I doing something wrong? Or is this simply impossible?
I need to be able to make the following code work:
foreach (PropertyInfo propertyInfo in currentObject.GetType().GetProperties())
{
foreach (CustomAttributeData attrData in propertyInfo.GetCustomAttributesData())
{
if (typeof(attrData) == typeof(MyAttributeOne))
//stuff
else if (typeof(attrData) == typeof(MyAttributeTwo))
//different stuff
else
//yet more stuff
}
}
Thank you very much for the attention!
Regards.

OK, this is a little involved but it's fairly simple. This is also really a bit of a brain dump but it does work and gives you enough to work with. Lets set up with some basics:
//A couple of custom attributes
public class MyAttributeOne : Attribute { }
public class MyAttributeTwo : Attribute { }
//A metadata class where we can use the custom attributes
public sealed class MyEntityMetadata
{
//This property has the same name as the class it is referring to
[MyAttributeOne]
public int SomeProperty { get; set; }
}
//And an entity class where we use System.ComponentModel.DataAnnotations.MetadataType
//to tell our function where the metadata is stored
[MetadataType(typeof(MyEntityMetadata))]
public class MyEntity
{
public int SomeProperty { get; set; }
}
OK, still with me? Now we need a function to process the properties in the same way you did earlier:
public void DoStuff(object currentObject)
{
//Lets see if our entity class has associated metadata
var metaDataAttribute = currentObject.GetType()
.GetCustomAttributes()
.SingleOrDefault(a => a is MetadataTypeAttribute) as MetadataTypeAttribute;
PropertyInfo[] metaProperties = null;
//Cache the metadata properties here
if (metaDataAttribute != null)
{
metaProperties = metaDataAttribute.MetadataClassType.GetProperties();
}
//As before loop through each property...
foreach (PropertyInfo propertyInfo in currentObject.GetType().GetProperties())
{
//Refactored this out as it's called again later
ProcessAttributes(propertyInfo.GetCustomAttributes());
//Now check the metadata class
if (metaProperties != null)
{
//Look for a matching property in the metadata class
var metaPropertyInfo = metaProperties
.SingleOrDefault(p => p.Name == propertyInfo.Name);
if (metaPropertyInfo != null)
{
ProcessAttributes(metaPropertyInfo.GetCustomAttributes());
}
}
}
}
And of course, here is the refactored method to process the attributes:
private void ProcessAttributes(IEnumerable<Attribute> attributes)
{
foreach (var attr in attributes)
{
if (attr is MyAttributeOne)
{
Console.WriteLine("MyAttributeOne found");
}
else if (attr is MyAttributeTwo)
{
Console.WriteLine("MyAttributeTwo found");
}
else
{
}
}
}

Related

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; }
}

How to initialize fields of class with data from file

I want initialize properties of my class with some data from file. I created DataSourceAttribute for classes and DataItemAttribute for properties. I also created abstract parent class for these classes for which I want initialize fields. See code below:
[AttributeUsage(AttributeTargets.Class)]
public class DataInitializerAttribute: Attribute
{}
[AttributeUsage(AttributeTargets.Property)]
public class DataItemAttribute: Attribute
{
public string FileName { get; set; }
public string Field { get; set; }
public string Value
{
get { return "someValue"; // here I will read file}
}
public DataItemAttribute(string fileName)
{
FileName = fileName;
}
}
public abstract class PropertyInitializer
{
protected PropertyInitializer()
{
var classAttribute = GetType().GetCustomAttribute<DataInitializerAttribute>();
if (classAttribute == null)
{
return;
}
var properties = GetType().GetProperties();
foreach (var property in properties)
{
var attribute = property.GetCustomAttribute<DataItemAttribute>();
if (attribute == null)
{
continue;
}
property.SetValue(this, attribute.Value);
}
}
}
Everything works good, but maybe it is possible to implement more elegant solution without inheriting of PropertyInitializer class?

How can I use a custom Attribute within a DynamicProxy Class

I tried to assign a custom Attribute to class that comes from a dynamic proxy
System.Data.Entity.DynamicProxies.Login_A2947F53...
Example class Login
public partial class Login
{
[CustomAttribute]
public virtual int Id
{
get;
set;
}
}
Now I try to access the Attribute using Generics and Reflection
public static void Process(TSource source)
{
foreach (PropertyInfo p in target.GetType().GetProperties(flags))
{
object[] attr = p.GetCustomAttributes(true); // <- empty
}
}
But there is no Attribute. Is that due to the DynmaicProxy or what did I do wrong here?
When I use a concrete class without dynamic proxy like this one, then I get the attributes.
public class TestObject
{
[CustomAttribute]
public virtual string Name { get; set; }
[CustomAttribute]
public virtual string Street { get; set; }
public virtual int Age { get; set; }
public virtual string Something { get; set; }
}
OK, this one was obvious after a closer look;
System.Data.Entity.DynamicProxies.Login_A2947F53...
is a dynamicProxy type and know nothing about any Attributes. So I have to use the something like:
foreach (PropertyInfo p in typeof(Login).GetProperties(flags))
instead of the dynamicProxy instance to get the type from. And finaly there are my Attributes.
Use BaseType.
public static void Process(TSource source)
{
foreach (PropertyInfo p in target.GetType().BaseType.GetProperties(flags))
{
object[] attr = p.GetCustomAttributes(true);
}
}

C# optional properties in C# 3.0 (2009)

I am wondering if C# supports optional properties as the following
public class Person
{
public string Name { get; set;}
public optional string NickName { get; set;}
...many more properties...
}
so that when I create a Person object I can easily check the validity of input values in a simple loop
public bool IsInputOK(Person person)
{
foreach( var property in person.GetType().GetProperties())
{
if( property.IsOptional())
{
continue;
}
if(string.IsNullOrEmpty((string)property.GetValue(person,null)))
{
return false;
}
}
return true;
}
I have searched on google but didn't get desired solution. Do I really have to hand code validation code for each property manually?
Thanks.
You can decorate these properties with attribute you define and mark the properties as optional.
[AttributeUsage(AttributeTargets.Property,
Inherited = false,
AllowMultiple = false)]
internal sealed class OptionalAttribute : Attribute
{
}
public class Person
{
public string Name { get; set; }
[Optional]
public string NickName { get; set; }
}
public class Verifier
{
public bool IsInputOK(Person person)
{
foreach (var property in person.GetType().GetProperties())
{
if (property.IsDefined(typeof(OptionalAttribute), true))
{
continue;
}
if (string.IsNullOrEmpty((string)property.GetValue(person, null)))
{
return false;
}
}
return true;
}
}
You may also want to take a look at Validation Application Block which has similar capabilities out of the box.
C# does not have an 'optional' keyword, and as #Mitch Wheat says, it's a horrible way to perform validation.
Why can't you just do the validation in the properties setter?
if you want to tag the props with attributes and not roll your own validation code, why not try a validation framework?
http://www.codeplex.com/ValidationFramework

How to validate Data Annotations with a MetaData class

I'm trying to validate a class using Data Annotations but with a metadata class.
[MetadataType(typeof(TestMetaData))]
public class Test
{
public string Prop { get; set; }
internal class TestMetaData
{
[Required]
public string Prop { get; set; }
}
}
[Test]
[ExpectedException(typeof(ValidationException))]
public void TestIt()
{
var invalidObject = new Test();
var context = new ValidationContext(invalidObject, null, null);
context.MemberName = "Prop";
Validator.ValidateProperty(invalidObject.Prop, context);
}
The test fails. If I ditch the metadata class and just decorated the property on the actual class it works fine. WTH am I doing wrong? This is putting me on the verge of insanity. Please help.
Answer
Here is a link to the post that helped me solve this issue. Apparently you have to register the matadata class first.
The metadata class must be public for the external validation to work.
[MetadataType(typeof(TestMetaData))]
public class Test
{
public string Prop { get; set; }
public class TestMetaData
{
[Required]
public string Prop { get; set; }
}
}
I believe defining the metadata class inside of your model class, like you did in your example, should work. Haven't tested it.

Categories

Resources