Determine if a property is a complex object using PropertyDescriptor - c#

I'm trying to do some validation in a WCF service, and for that I'm using WCFDataAnnotations which I found through this post
Problem is that it doesn't validate recursively, so for a nested object it doesn't work. Let's say this
[DataContract]
public class Model
{
[DataMember]
[Required(ErrorMessage = "RequiredOne is required")]
public string RequiredOne { get; set; }
[DataMember]
[StringLength(10, ErrorMessage = "Not Required should be at most 10 characters long")]
public string NotRequired { get; set; }
[DataMember]
[Required(ErrorMessage = "ChildModel is required")]
public ChildModel ChildModel { get; set; }
}
[DataContract]
public class ChildModel
{
[DataMember]
[Required(ErrorMessage = "RequiredValue is required")]
public string RequiredValue { get; set; }
[DataMember]
public string NotRequiredValue { get; set; }
}
It won't get the childModel RequiredValue as precisely that, required.
So I was taking a look into the source code of that dll and trying to make it work. The actual code is
public class DataAnnotationsObjectValidator : IObjectValidator
{
public IEnumerable<ValidationResult> Validate(object input)
{
if (input == null) return Enumerable.Empty<ValidationResult>();
return from property in TypeDescriptor.GetProperties(input).Cast<PropertyDescriptor>()
from attribute in property.Attributes.OfType<ValidationAttribute>()
where !attribute.IsValid(property.GetValue(input))
select new ValidationResult
(
attribute.FormatErrorMessage(string.Empty),
new[] { property.Name }
);
}
}
So my thought are changing this to something like this
public IEnumerable<ValidationResult> Validate(object input)
{
if (input == null) return Enumerable.Empty<ValidationResult>();
var validationResults = new List<ValidationResult>();
foreach (var prop in TypeDescriptor.GetProperties(input).Cast<PropertyDescriptor>())
{
foreach (var att in prop.Attributes.OfType<ValidationAttribute>())
{
//This doesn't work, it's one of the several
//attempts I've made
if (prop.ComponentType.IsClass)
Validate(prop.ComponentType);
if (!att.IsValid(prop.GetValue(input)))
{
validationResults.Add(new ValidationResult(
att.FormatErrorMessage(string.Empty),
new[] { prop.Name }
));
}
}
}
return validationResults;
}
The intention is check if any of the properties is a complex one and if it's the case validate itself recursively, but I'm not sure how to check that given the "props" are casted to TypeDescriptors.
Thanks

Seems to me that the following code should do the trick:
public IEnumerable<ValidationResult> Validate(object input)
{
return ValidateWithState(input, new HashSet<object>());
}
private IEnumerable<ValidationResult> ValidateWithState(object input, HashSet<object> traversedInputs)
{
if (input == null || traversedInputs.Contains(input))
{
return Enumerable.Empty<ValidationResult>();
}
var validationResults = new List<ValidationResult>();
foreach (var prop in TypeDescriptor.GetProperties(input).Cast<PropertyDescriptor>())
{
foreach (var att in prop.Attributes.OfType<ValidationAttribute>())
{
if (!att.IsValid(prop.GetValue(input)))
{
validationResults.Add(new ValidationResult(
att.FormatErrorMessage(string.Empty),
new[] { prop.Name }
));
}
traversedInputs.Add(input);
if (prop.PropertyType.IsClass || prop.PropertyType.IsInterface))
{
validationResults.AddRange(ValidateWithState(prop.GetValue(input), traversedInputs));
}
}
return validationResults;
}
Might not be the most elegant solution, but i think it'll work.

Now I am able to validate public List ChildModel.
With Reference to DevTrends.WCFDataAnnotations, ValidatingParameterInspector.cs class,
(http://wcfdataannotations.codeplex.com/SourceControl/latest#DevTrends.WCFDataAnnotations/ValidatingParameterInspector.cs).
I am sure ValidateCollection can be further modified to check collection within ChildModel.Currently it check upto one level only .
My Example,
[DataContract]
public class Model
{
[DataMember]
[Required(ErrorMessage = "RequiredOne is required")]
public string RequiredOne { get; set; }
[DataMember]
[StringLength(10, ErrorMessage = "Not Required should be at most 10 characters long")]
public string NotRequired { get; set; }
[DataMember]
[Required(ErrorMessage = "ChildModel is required")]
public List<ChildModel> ChildModel { get; set; }
}
Original code do not validate List,So I created one more function ValidateCollection
Manipulated the object[] inputs to extract each ChildModel class and put it back in object[] inputs like model class reside in object[] inputs.
public object BeforeCall(string operationName, object[] inputs)
{
var validationResults = new List<ValidationResult>(); ErrorMessageGenerator.isValidationFail = false;
ErrorMessageGenerator.ErrorMessage = string.Empty;
***inputs=ValidateCollection( operationName, inputs);***
foreach (var input in inputs)
{
foreach (var validator in _validators)
{
var results = validator.Validate(input);
validationResults.AddRange(results);
}
}
if (validationResults.Count > 0)
{
return _errorMessageGenerator.GenerateErrorMessage(operationName, validationResults);
}
return null;
}
private object[] ValidateCollection(string operationName, object[] inputs)
{
object[] inputs1 = inputs;
try
{
foreach (var input in inputs)
{
foreach (var property in input.GetType().GetProperties())
{
IEnumerable enumerable = null;
if (property.PropertyType.Name.Contains("List"))
{
enumerable = property.GetValue(input, null) as IEnumerable;
int j = 0;
object[] o1 = new object[inputs.Count() + enumerable.OfType<object>().Count()];
for (int k = 0; k < inputs.Count(); k++)
{
o1[k] = inputs[k];
}
foreach (var item in enumerable)
{
o1[inputs.Count() + j] = item;
j = j + 1;
if (j == (o1.Length - inputs.Count()))
inputs = o1;
}
}
}
}
return inputs;
}
catch
{
return inputs1;
}
}

Related

custom response object for model validation in asp.net core

I want custom object in response of API having [required] data annotation on model properties like this:
{
"resourceType": "OperationOutcome",
"issue": [
{
"severity": "fatal",
"code": "required",
"location": [
"/f:AllergyIntolerance/f:status"
]
}
]
}
Is it possible to do it or I would have to code it.
Because model validation happens before action is called, is there any way I can do it?
To create custom request and respond samples for your api, you can use Swashbuckle.AspNetCore.Swagger and to improve validations on your models you can use FluentValidations Sample. Good Luck!
first for simplify define your models like these :
public class ResponseModel
{
public string resourceType { get; set; }
public List<ResponseIssueModel> issue { get; set; } = new List<ResponseIssueModel>();
}
public class ResponseIssueModel
{
public string severity { get; set; }
public string code { get; set; }
public List<string> locations { get; set; } = new List<string>();
}
Then on your actions you can return this :
var response = new ResponseModel();
response.resourceType = "OperationOutcome";
response.issue.Add(new ResponseIssueModel
{
severity = "fatal",
code = "required",
locations = { "/f:AllergyIntolerance/f:status" }
});
return Ok(response);
you can use Builder Pattern for easy create response object
If you want to validate your model in controller,you could try with TryValidateModel method as mentioned in the document:
I tried as below:
in controller:
var model = new TestModel() { Id=1,nestedModels=new List<NestedModel>() { new NestedModel() { Prop1="P11"} } };
var isvalid=TryValidateModel(model);
var errorfiledlist = new List<string>();
if (!isvalid)
{
foreach (var value in ModelState.Values)
{
foreach (var error in value.Errors)
{
errorfiledlist.Add(MidStrEx(error.ErrorMessage,"The "," field"));
}
}
}
var jsonstring = JsonSerializer.Serialize(model);
foreach (var field in errorfiledlist)
{
var oldstr = String.Format("\"{0}\":null", field);
var newstr = String.Format("\"{0}\":\"required\"", field);
jsonstring = jsonstring.Replace(oldstr, newstr);
};
var obj = JsonSerializer.Deserialize<Object>(jsonstring);
return Ok(obj);
MidStrEx method:
public static string MidStrEx(string sourse, string startstr, string endstr)
{
string result = string.Empty;
int startindex, endindex;
try
{
startindex = sourse.IndexOf(startstr);
if (startindex == -1)
return result;
string tmpstr = sourse.Substring(startindex + startstr.Length);
endindex = tmpstr.IndexOf(endstr);
if (endindex == -1)
return result;
result = tmpstr.Remove(endindex);
}
catch (Exception ex)
{
}
return result;
}
Models:
public class TestModel
{
public int Id { get; set; }
[Required]
public string Prop { get; set; }
public List<NestedModel> nestedModels { get; set; }=new List<NestedModel>();
}
public class NestedModel
{
public string Prop1 { get; set; }
[Required]
public string Prop2 { get; set; }
}
The result:

What is a more effective and efficient way to check for matching attributes and fields

I have the following model that has these fields:
[Key]
public string Id { get; set; }
[IsSearchable]
public string Code{ get; set; }
[IsSearchable]
public string Name { get; set; }
[IsSearchable]
public string Address { get; set; }
[IsSearchable]
public string PostCode { get; set; }
[IsFilterable]
public int? Setting{ get; set; }
[IsFilterable, IsSortable]
public Location Location { get; set; }
I am writing a method to compare whether values in a database match this model. So far it looks like this:
private bool CompareEquality(Index resultBody, Type indexType)
{
var properties = indexType.GetProperties(BindingFlags.Instance | BindingFlags.Public);
List<PropertyInfo> searchableProperties = new List<PropertyInfo>();
List<PropertyInfo> filterableProperties = new List<PropertyInfo>();
List<PropertyInfo> sortableProperties = new List<PropertyInfo>();
if (properties.Count() == resultBody.Fields.Count)
{
foreach (var property in properties)
{
var isSearchableAttribute = property.GetCustomAttribute<IsSearchableAttribute>();
var isFilterableAttribute = property.GetCustomAttribute<IsFilterableAttribute>();
var isSortableAttribute = property.GetCustomAttribute<IsSortableAttribute>();
if (isSearchableAttribute != null)
{
searchableProperties.Add(property);
}
if (isFilterableAttribute != null)
{
filterableProperties.Add(property);
}
if (isSortableAttribute != null)
{
sortableProperties.Add(property);
}
}
CheckAttributeEquality(searchableProperties, filterableProperties, sortableProperties);
}
return false;
}
The CheckAttributeEquality method:
private bool CheckAttributeEquality(List<PropertyInfo> searchableProperties, List<PropertyInfo> filterableProperties, List<PropertyInfo> sortableProperties)
{
if (searchableProperties.Count == 4 && filterableProperties.Count == 2 && sortableProperties.Count == 1)
{
CheckPropertyFields(searchableProperties, filterableProperties, sortableProperties);
return true;
}
return false;
}
As I started to write a method to check that the field names match, like so:
foreach (var property in searchableProperties)
{
if (property.Name == "Id" ||)
{
...
}
if (property.Name == "Code")
{
...
}
// etc
I realised how messy and long-winded this whole approach is. I am not hugely experienced in C# and would appreciate any advice as to how I can refactor this up a little bit? I want to check for attribute and name matches.
you could use the Typedescriptor (using System.ComponentModel) for that. Try this:
var pdc = TypeDescriptor.GetProperties( this ); //or your model object, if its not "this"
foreach (var property in searchableProperties)
{
var descriptors = pdc[ property.Name ];
// check if your searchable descriptor is there, and do error handling
}
Once it works, you could also try to solve it with LINQ.

Reflection to get List<object> data

I'm trying to loop through a DetailClass objects inside a List using reflection just like for string fields, but I can't figure out how.
class DetailClass
{
public string FieldDetail1 { get; set; }
public string FieldDetail2 { get; set; }
public string FieldDetail3 { get; set; }
}
class SomeClass
{
public string Field1 { get; set; }
public string Field2 { get; set; }
public string Field3 { get; set; }
public List<DetailClass> Artikli { get; set; }
}
private static PropertyInfo[] GetProperties(object obj)
{
return obj.GetType().GetProperties();
}
var myData = new SomeClass();
var prop = GetProperties(myData);
foreach (var item in prop)
{
if (item.PropertyType == typeof(string))
{
var name = item.Name,
var value = item.GetValue(myData).ToString()));
}
//how to get name and value for data inside List<DetailClass>?
}
You were trying to enumerate properties of the parent class
GetValue needs the a reference to the class you are dealing with
Code
var myData = new SomeClass();
myData.Artikli = new List<DetailClass>() { new DetailClass() { FieldDetail1 = "asd", FieldDetail2 = "sdfd", FieldDetail3 = "sdfsg" } };
foreach (var obj in myData.Artikli)
{
foreach (var item in obj.GetType().GetProperties())
{
if (item.PropertyType == typeof(string))
{
var name = item.Name;
var val = item.GetValue(obj);
Console.WriteLine(name + ", " + val);
}
}
}
Demo Here
Additional Resources
PropertyInfo.GetValue Method (Object)
Returns the property value of a specified object.
Parameters
obj
Type: System.Object
The object whose property value will be returned.
You can use your method recursively to get inside all layer of properties
You can check if
item.PropertyType.GetInterfaces().Contains(typeof(IEnumerable))
and if true cast (IEnumerable)item.GetValue(myData) and iterate on the result
recursively.
Just like TheDude answered, you can use a recursive method like so;
private void Recursion(object obj)
{
var props = GetProperties(obj);
foreach (var item in props)
{
if (item.PropertyType == typeof(string))
{
var name = item.Name;
var value = item.GetValue(obj)?.ToString();
}
else if (item.PropertyType == typeof(List<DetailClass>))
{
var test = (List<DetailClass>) item.GetValue(obj);
foreach (var t in test)
{
Recursion(t);
}
}
}
}
And do whatever you want with the name and values in the list.

How to use reflection to show properties of Web Service with a List<CustomClass>

I successfully implemented the answer on this question. However, it does not work when I have a List. When I ran this through, here's what I got back:
AccountDataResult: 'AccountFound'
AccountList: 'CustomWebService.TestApplication.ServiceMethods.CustomClassOutputData+DataList[]'
This list has X items inside and I need to list out all of the properties for each one. What is the best method to achieve this?
Here is what I have today:
void AddPropertiesToTextbox(object output, TextBox txt)
{
PropertyInfo[] piData = null;
piData = Utility.GetPublicProperties(output.GetType());
foreach (PropertyInfo pi in piData)
{
if (pi.Name.ToLower() != "extensiondata")
{
textResult.Text += string.Format("{0}: '{1}'", pi.Name, pi.GetValue(output, null));
textResult.Text += Environment.NewLine;
}
}
}
Here is my web service Model:
[DataContract]
public class OutputData
{
[DataContract]
public class AccountData
{
[DataMember]
public string AccountStatus { get; set; }
[DataMember]
public string FirstName { get; set; }
[DataMember]
public string LastName { get; set; }
}
[DataContract]
public enum AccountDataResults
{
[EnumMember]
None,
[EnumMember]
AccountFound,
[EnumMember]
NoAccounts
}
[DataMember]
public List<AccountData> DataList { get; set; }
[DataMember]
public AccountDataResults AccountDataResult { get; set; }
}
You can check the PropertyInfo's PropertyType type definition by calling GetGenericTypeDefinition()
if(pi.PropertyType.GetGenericTypeDefinition() == typeof(List<>))
{
// recurse through properties
}
Demo:
class Program
{
static void Main(string[] args)
{
object test = new Test { DemoProperty = "Some", DemoListProperty = new List<int> { 1, 2, 3, 4 } };
Type type = typeof(Test);
foreach (var pi in type.GetProperties())
{
if (pi.PropertyType.IsGenericType && pi.PropertyType.GetGenericTypeDefinition() == typeof(List<>))
{
IEnumerable objects = pi.GetGetMethod().Invoke(test,null) as IEnumerable;
foreach (var o in objects)
{
Console.WriteLine(o); //may want to do recursion here and iterate over these properties too
}
}
}
}
}
class Test
{
public string DemoProperty { get; set; }
public List<int> DemoListProperty { get; set; }
}
In order to get this working I had to scrap my initial setup. Here is what I am doing now. This works PERFECTLY for me! This was used to pull all properties i care about into a result list.
Get all properties for any type:
public static PropertyInfo[] GetPublicProperties2(this Type type)
{
return type.GetProperties(BindingFlags.Public | BindingFlags.Instance);
}
I then have this method that is generic for any property which calls itself when there is an array.
void AddPropertiesToTextbox(object output, string rootNamespace = "")
{
PropertyInfo[] piData = null;
piData = Utility.GetPublicProperties2(output.GetType());
foreach (PropertyInfo pi in piData)
{
if (pi.PropertyType.IsArray == true)
{
Array subOutput = (Array)pi.GetValue(output);
for (int i = 0; i < subOutput.Length; i++)
{
textResult.Text += string.Format("{0}------------------------{0}", Environment.NewLine);
object o = subOutput.GetValue(i);
AddPropertiesToTextbox(o, pi.Name);
}
}
else
{
if (pi.Name.ToLower() != "extensiondata")
{
if (string.IsNullOrWhiteSpace(rootNamespace) == false) {
textResult.Text += string.Format("{2}.{0}: '{1}'", pi.Name, pi.GetValue(output, null), rootNamespace);
textResult.Text += Environment.NewLine;
} else {
textResult.Text += string.Format("{0}: '{1}'", pi.Name, pi.GetValue(output, null));
textResult.Text += Environment.NewLine;
}
}
}
}
}

Get Values From Complex Class Using Reflection

I have a class, which is created and populated from an xml string, I've simplified it for example purposes:
[XmlRoot("Person")]
public sealed class Person
{
[XmlElement("Name")]
public string Name { get; set; }
[XmlElement("Location")]
public string Location { get; set; }
[XmlElement("Emails", Type = typeof(PersonEmails)]
public PersonEmails Emails { get; set; }
}
public class PersonEmails
{
[XmlElement("Email", Type = typeof(PersonEmail))]
public PersonEmail[] Emails { get; set; }
}
public class PersonEmail
{
[XmlAttribute("Type")]
public string Type { get; set; }
[XmlText]
public string Value { get; set; }
}
To extract the information, I'm trying to load them into another class, which is simply:
public class TransferObject
{
public string Name { get; set; }
public ObjectField[] Fields { get; set; }
}
public class ObjectField
{
public string Name { get; set; }
public string Value { get; set; }
}
I'm only populating "Fields" from the other object, which would simply be (Name = "Location", Value = "London"), but for Emails, (Name = "Email"+Type, Value = jeff#here.com)
Currently I can populate all the other fields, but I'm stuck with Emails, and knowing how to dig deep enough to be able to use reflection (or not) to get the information I need. Currently I'm using:
Person person = Person.FromXmlString(xmlString);
List<ObjectField> fields = new List<ObjectField>();
foreach (PropertyInfo pinfo in person.getType().GetProperties()
{
fields.Add(new ObjectField { Name = pinfo.Name, Value = pinfo.getValue(person, null).ToString();
}
How can I expand on the above to add all my emails to the list?
You are trying to type cast a complex values type to string value so you lost the data. Instead use following code:
class Program
{
static void Main(string[] args)
{
Person person = new Person();
person.Name = "Person One";
person.Location = "India";
person.Emails = new PersonEmails();
person.Phones = new PersonPhones();
person.Emails.Emails = new PersonEmail[] { new PersonEmail() { Type = "Official", Value = "xyz#official.com" }, new PersonEmail() { Type = "Personal", Value = "xyz#personal.com" } };
person.Phones.Phones = new PersonPhone[] { new PersonPhone() { Type = "Official", Value = "789-456-1230" }, new PersonPhone() { Type = "Personal", Value = "123-456-7890" } };
List<ObjectField> fields = new List<ObjectField>();
fields = GetPropertyValues(person);
}
static List<ObjectField> GetPropertyValues(object obj)
{
List<ObjectField> propList = new List<ObjectField>();
foreach (PropertyInfo pinfo in obj.GetType().GetProperties())
{
var value = pinfo.GetValue(obj, null);
if (pinfo.PropertyType.IsArray)
{
var arr = value as object[];
for (var i = 0; i < arr.Length; i++)
{
if (arr[i].GetType().IsPrimitive)
{
propList.Add(new ObjectField() { Name = pinfo.Name + i.ToString(), Value = arr[i].ToString() });
}
else
{
var lst = GetPropertyValues(arr[i]);
if (lst != null && lst.Count > 0)
propList.AddRange(lst);
}
}
}
else
{
if (pinfo.PropertyType.IsPrimitive || value.GetType() == typeof(string))
{
propList.Add(new ObjectField() { Name = pinfo.Name, Value = value.ToString() });
}
else
{
var lst = GetPropertyValues(value);
if (lst != null && lst.Count > 0)
propList.AddRange(lst);
}
}
}
return propList;
}
}
Check this snippet out:
if(pinfo.PropertyType.IsArray)
{
// Grab the actual instance of the array.
// We'll have to use it in a few spots.
var array = pinfo.GetValue(personObject);
// Get the length of the array and build an indexArray.
int length = (int)pinfo.PropertyType.GetProperty("Length").GetValue(array);
// Get the "GetValue" method so we can extact the array values
var getValue = findGetValue(pinfo.PropertyType);
// Cycle through each index and use our "getValue" to fetch the value from the array.
for(int i=0; i<length; i++)
fields.Add(new ObjectField { Name = pinfo.Name, Value = getValue.Invoke(array, new object[]{i}).ToString();
}
// Looks for the "GetValue(int index)" MethodInfo.
private static System.Reflection.MethodInfo findGetValue(Type t)
{
return (from mi in t.GetMethods()
where mi.Name == "GetValue"
let parms = mi.GetParameters()
where parms.Length == 1
from p in parms
where p.ParameterType == typeof(int)
select mi).First();
}
You can definately do it with Reflection... You can take advantage of the fact that a Type can tell you if it's an array or not (IsArray)... and then take advantage of the fact that an Array has a method GetValue(int index) that will give you a value back.
Per your comment
Because Emails is a property within a different class, recursion should be used. However the trick is knowing when to go to the next level. Really that is up to you, but
if it were me, I would use some sort of Attribute:
static void fetchProperties(Object instance, List<ObjectField> fields)
{
foreach(var pinfo in instance.GetType().GetProperties())
{
if(pinfo.PropertyType.IsArray)
{
... // Code described above
}
else if(pinfo.PropertyType.GetCustomAttributes(typeof(SomeAttribute), false).Any())
// Go the next level
fetchProperties(pinfo.GetValue(instance), fields);
else
{
... // Do normal code
}
}
}

Categories