I have an enumeration
public enum GTMType
{
[Display(Name = "CHANNEL_CHANNEL")]
ChannelChannel,
[Display(Name = "CHANNEL_WHOLESALE")]
ChannelWholesale,
[Display(Name = "ENTERPRISE_DIRECT")]
EnterpriseDirect,
[Display(Name = "ENTERPRISE_AGENT")]
EnterpriseAgent,
[Display(Name = "ENTERPRISE_SYSTEM_INTEGRATOR")]
EnterpriseSystemIntegrator
}
when I make an API call to another system to get data, The system returns the value which is a display attribute value.
public Account GetDataForAccountByID(string id)
{
AccountModel accountModel = GetDataFromAnotherSystem(id);
//after the call is successfull accountModel looks like
//{Email: "abc#xyz.com",GTMType:"CHANNEL_CHANNEL"}
var account = new Account
{
EmailAddress: = accountModel.Email,
GTMType = accountModel.GTMType
};
}
public class AccountModel
{
public string Email { get; set; }
public string GTMType { get; set; }
}
public class Account
{
public string EmailAddress { get; set; }
public GTMType GTMType { get; set;
}
How can I convert the string value which is of display attribute value can be converted into an enum.
You can use Enum.GetValues to iterate over possible values of the enum and then GetField representing the given value and get its attributes.
GTMType ParseGTMTypeFromAnotherSystem(string gtmType)
{
var type = typeof(GTMType);
foreach (var value in Enum.GetValues(type))
{
var fieldInfo = type.GetField(Enum.GetName(type, value));
DisplayAttribute[] attributes = fieldInfo.GetCustomAttributes(typeof(DisplayAttribute), false) as DisplayAttribute[];
if (attributes.Length != 1)
{
throw new Exception("Enum definition is wrong.");
}
var displayName = attributes[0].Name;
if (gtmType == displayName)
{
return value as GTMType;
}
}
throw new Exception("Unable to parse.");
}
You may want to handle the exception cases differently, e.g. iterate over all DisplayAttributes in case there's more than one and ignore fields that have none, or in case of a failed parse return a default value - depends on your use case.
Related
I'm basically trying to use reflection to flatten any class into a dictionary so that I can generically use and bind them in Blazor. I then need to be able to create an instance of the class and populate it with the data from the dictionary (which will have been updated by a component).
e.g
public class Order
{
public Guid Id { get; set; }
public Customer Customer { get; set; }
public string Address { get; set; }
public string Postcode { get; set; }
public List<string> Test { get; set; }
public List<Test> Test2 { get; set; }
}
public class Customer
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string FullName => $"{FirstName} {LastName}";
public Gender Gender { get; set; }
public List<string> Test { get; set; }
}
Should become:
{
"Id": "",
"Customer.FirstName": "",
"Customer.LastName": "",
"Customer.Gender": "",
"Customer.Test": "",
"Address": "",
"Postcode": "",
"Test": "",
"Test2": ""
}
For some reason when I iterate the properties of the Order class, Test2 is missed. The loop shows the property in the collection when I put a breakpoint, it just seems to skip it. I've never seen this happen before.
Code: https://dotnetfiddle.net/g1qyVQ
I also don't think the current code with handle further nested depth which I would like it to be able to work with any POCO object really.
Also if anyone knows a better way to do what I'm trying, I would love to find an easier way. Thanks
First of all, good job on linking the code sample. Without that, I would have passed by this question in about three seconds. :D
In GetAllProperties(), your entire loop is inside a giant try catch block, where the catch returns the dictionary as it is so far, without checking what the exception is. So if you don't get everything you expect, you've probably hit an error.
Amend the catch block:
catch (Exception ex) { Console.WriteLine(ex.ToString()); return result; }
Now, you can see the problem:
System.ArgumentException: An item with the same key has already been added. Key: Test
Your object has more than one property named "Test," but Keys in a Dictionary must be unique.
Summary: Errors aren't the enemy, they're your best friend. Don't use try / catch to bypass errors. If you do, you may get "mysterious, never seen that happen before!" results.
For anyone interested, here is where I'm at now:
https://dotnetfiddle.net/3ORKNs
using JsonFlatten;
using Newtonsoft.Json.Linq;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text.Json;
namespace RecursiveClassProperties
{
public static class Program
{
static void Main(string[] args)
{
var item = CreateDefaultItem(typeof(Order));
Console.WriteLine(JsonSerializer.Serialize(item, new JsonSerializerOptions { WriteIndented = true }));
var json = JsonSerializer.Serialize(item);
var properties = JObject.Parse(json).Flatten();
Console.WriteLine(JsonSerializer.Serialize(properties, new JsonSerializerOptions { WriteIndented = true }));
var formProperties = properties.ToDictionary(x => x.Key, x => new FormResponse(string.Empty));
Console.WriteLine(JsonSerializer.Serialize(formProperties, new JsonSerializerOptions { WriteIndented = true }));
}
private static object CreateFormItem(Type type, Dictionary<string, FormResponse> formProperties, object result = null)
{
result = CreateDefaultItem(type);
return result;
}
private static object CreateDefaultItem(Type type, object result = null, object nested = null, bool isBase = false)
{
void SetProperty(PropertyInfo property, object instance)
{
if (property.PropertyType == typeof(string)) property.SetValue(instance, string.Empty);
if (property.PropertyType.IsEnum) property.SetValue(instance, 0);
if (property.PropertyType == typeof(Guid)) property.SetValue(instance, Guid.Empty);
}
if (result is null)
{
result = Activator.CreateInstance(type);
isBase = true;
}
var properties = type.GetProperties();
foreach (var property in properties)
{
if (!Attribute.IsDefined(property, typeof(FormIgnoreAttribute)) && property.GetSetMethod() is not null)
{
if (property.PropertyType == typeof(string) || property.PropertyType.IsEnum || property.PropertyType == typeof(Guid))
{
if (isBase) SetProperty(property, result);
else if (nested is not null && nested.GetType() is not IList && !nested.GetType().IsGenericType) SetProperty(property, nested);
}
else
{
var _nested = default(object);
if (isBase)
{
property.SetValue(result, Activator.CreateInstance(property.PropertyType));
_nested = property.GetValue(result);
}
if (nested is not null)
{
property.SetValue(nested, Activator.CreateInstance(property.PropertyType));
_nested = property.GetValue(nested);
}
CreateDefaultItem(property.PropertyType, result, _nested);
}
}
}
return result;
}
}
[AttributeUsage(AttributeTargets.Property, Inherited = false, AllowMultiple = false)]
public class FormIgnoreAttribute : Attribute { }
public class FormResponse
{
public FormResponse(string value) => Value = value;
public string Value { get; set; }
}
public class Order
{
public Guid Id { get; set; }
public Customer Customer { get; set; }
public string Address { get; set; }
public string Postcode { get; set; }
public Test Test { get; set; }
public List<Gender> Genders { get; set; }
public List<string> Tests { get; set; }
}
public enum Gender
{
Male,
Female
}
public class Test
{
public string Value { get; set; }
public List<Gender> Genders { get; set; }
public List<string> Tests { get; set; }
}
public class Customer
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string FullName => $"{FirstName} {LastName}";
public Gender Gender { get; set; }
public Test Test { get; set; }
public List<Gender> Genders { get; set; }
public List<string> Tests { get; set; }
}
}
The idea is that I can assign values to formProperties, pass it to CreateFormItem() and get a populated object back. The reason I'm doing this is because I have a Blazor component Table which has a typeparam TItem, basically think of it as Table<TItem> for those unfamiliar with Blazor. The table is then supplied a list of objects which it can then render.
Flattening the object in this way will both allow me to easily display all properties and subproperties of the class in the table, but most importantly bind the input of a "new item" form which will return the new object to a delegate outside of the component (back in normal .NET) to submit to a creation controller (to put it in the DB). The reason having a Dictionary<string, FormResponse> is important is that with a generic type, you aren't able to bind the input of the form to the "model". You are however able to bind the input to a string property of a class, even if it's not a string. Hence FormResponse.Value.
I will next need to have CreateFormItem() return the object with the actual data from the form. Sorry if this is a bit longwinded, couldn't think of a more concise way to explain it.
Thanks :)
This is quite annoying and I only need to run this code ONCE. After that, all will get deleted (it's migration code)
I have a List<string> of JSON strings. They contain various different formats of JSON objects, which can look like this:
{
"id": 123
}
{
"id": "7521b497-abb7-46b8-bddc-177a6fd9f974",
"folderId": 123
}
{
"folderId": 123
}
and so on. I need to get the 123, which can be in the properties id and folderId. If I simply do:
class IdModel {
public int Id { get; set; }
}
//inside a function
var model = JsonConvert.DeserializeObject<IdModel>(json);
it will fail when it gets to the second JSON, because id is a GUID. Instead, it would need to look for FolderId, which means I can do something like this:
class IdModel {
public int Id { get; set; }
}
class FolderIdModel {
public int FolderId { get; set; }
}
//inside a function
int folderId;
try {
var model = JsonConvert.DeserializeObject<FolderIdModel>(json);
folderId = model.FolderId;
} catch {
var model = JsonConvert.DeserializeObject<IdModel>(json);
folderId = model.Id;
}
That would be "fine" for this scenario, but I probably have 10 different JSON objects, that all look different. FolderId > Id, because I always know FolderId is the correct one, unless it has no FolderId, in which case it MIGHT have an Id (should explode if neither FolderId or Id is correct).
My question is: Is there a smart way to deserialize to different models, without looking at the JSON? Remember Id can both be a GUID and an integer, depending on the JSON objects.
I know this is really bad and I'm sorry.
Yes you can use the type dynamic where it will match the outcome of your Json object and then validate wheter the value type is a Guid or a int like so :
int folderId;
var model = JsonConvert.DeserializeObject<dynamic>(json);
folderId = model.id != null && model.id is int ? model.id : model.folderId;
If there are more possible outcomes you can break this ternary operator and validate them singulary.
I'd just deserialize both with the same class, then parse them according to the property values. For example:
public class IdModel
{
public string Id { get; set; }
public string FolderId { get; set; }
public int Value
{
get
{
if (int.TryParse(Id, out int value))
{
return value;
}
else if (int.TryParse(FolderId, out value))
{
return value;
}
else
{
throw new Exception("This model has no valid id");
}
}
}
}
Usage:
string json1 = "{\"id\": 123}";
string json2 = "{\"id\": \"7521b497-abb7-46b8-bddc-177a6fd9f974\",\"folderId\": 123}";
string json3 = "{\"folderId\": 123}";
IdModel model1 = JsonConvert.DeserializeObject<IdModel>(json1); // model1.Value = 123
IdModel model2 = JsonConvert.DeserializeObject<IdModel>(json2); // model2.Value = 123
IdModel model3 = JsonConvert.DeserializeObject<IdModel>(json3); // model3.Value = 123
I would add an extra Property to the Model which gets the real id (FolderId or Id, depending on which is set correctly).
The model would look like this:
class Model
{
public string Id { get; set; }
public int? FolderId { get; set; }
public int RealFolderId
{
get
{
if (FolderId != null)
{
return FolderId.Value;
}
int id;
if (int.TryParse(Id, out id))
{
return id;
}
throw new Exception("This explodes");
}
}
}
The id is serialized as a string, so it will neither break when Id is a Guid nor when it is an int. The Exception is thrown when both, FolderId and Id have "incorrect values"; none is an int.
The rest of the code will be pretty simple:
var deserialized = JsonConvert.DeserializeObject<Model>(json);
int folderId = deserialized.RealFolderId;
You can try to use DeserializeAnonymousType method, something like that
var responseObject = JsonConvert.DeserializeAnonymousType(json, new { id = "" });
That convert it to required type (int or Guid) or use just object type
I have a class Student that contains the list of property 'TextPair' as shown below:
public class Student
{
public List<TextPair> Hobbies { get; set; }
public List<TextPair> Languages { get; set; }
public List<TextPair> Majors { get; set; }
}
public class TextPair
{
[StringLength(2, ErrorMessage = "The value length is invalid")]
public string Value { get; set; }
public string Text { get; set; }
}
Here, I validate the value for maximum length 2 using the StringLength AttributeValidator and decorate in the property 'Value' inside TextPair model.
The problem for me is that the length is always fixed and length is always mandatory.
In my use case, I want the different flavor of Value in different part of the application (or, different property of same type) to support different lengths.
I was looking for something like below where I could pass the validation in my class where I declare my property 'TextPair'
[i.e. I don't want to make the validation mandatory always and also not hard-code the value 2]
public class Student
{
//Any length of the value is accepted for hobbies
public List<TextPair> Hobbies{ get; set; }
[ValuesLength(Length = 2, ErrorMessage = "Language code length must be 2 characters max")]
public List<TextPair> Languages { get; set; }
[ValuesLength(Length = 128, ErrorMessage = "The major should be within 128 characters length")]
public List<TextPair> Majors{ get; set; }
}
Is there any efficient way to approach this solution?
Maybe try subclassing StringLengthAttribute to create your desired ValuesLength attribute. Please note that this code is not tested and is just suggestion to final implementation.
public class ValuesLength : StringLengthAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var isValid = true;
var pair = value as TextPair;
if (pair != null && pair.Value != null)
{
var pairValue = pair.Value;
isValid = pairValue.Length < MaximumLength && pairValue.Length > MinimumLength;
}
return IsValid ? ValidationResult.Success : new ValidationResult(ErrorMessage);
}
}
One of the solution approached is as follows:
public class Student
{
//Any length of the value is accepted for hobbies
public List<TextPair> Hobbies{ get; set; }
[ValuesLength(MaximumLength = 2, ErrorMessage = "Language code length must be 2 characters max")]
public List<TextPair> Languages { get; set; }
[ValuesLength(MaximumLength = 128, ErrorMessage = "The major should be within 128 characters length")]
public List<TextPair> Majors{ get; set; }
}
My Custom Attribute validation is checking the list and verifying if anyone of the element values are exceeding the provided length as:
public class ValuesLengthAttribute : ValidationAttribute
{
public int MaximumLength { get; set; }
public override Boolean IsValid(object value)
{
Boolean isValid = true;
var list = value as List<TextPair>;
if (list != null && list.Count>0)
foreach (var item in list)
{
if (item.Value.Length > MaximumLength)
isValid = false;
}
return isValid;
}
}
I am trying to access another model's(Property_Master) property in current model(PropertyDetails_Master) for conditional validation purpose by inheriting IValidatableObject. But When I create object of another model(object of Property_Master), It returns null values of property. How do I get value of property of another model?
In my code
public class Property_Master : IValidatableObject
{
//Another Properties
[DisplayName("Property Type")]
[Required(ErrorMessage = "* Please Select Property Type.")]
public string PR_PropertyType
{
get { return strPropertyType;}
set { strPropertyType=value;}
}
}
In another model, I want to use above PR_PropertyType for to check condition in PropertyDetails_Master model.
public class PropertyDetails_Master : IValidatableObject
{
Property_Master pm = new Property_Master();
[DisplayName("Flat No.")]
public string PR_FlatNo { get; set; }
[DisplayName("Floor No.")]
public Nullable<int> PR_Floor { get; set; }
[DisplayName("No. Of Bedrooms")]
[Required(ErrorMessage = "* Please Enter No. Of Bedrooms.")]
public Nullable<int> PD_Bedrooms { get; set; }
[DisplayName("No. Of Bathrooms")]
[Required(ErrorMessage = "* Please Enter No. Of Bathrooms.")]
public Nullable<int> PD_Bathrooms { get; set; }
if (pm.PR_PropertyType=="Flat")
{
if (this.PR_FlatNo == "" || this.PR_FlatNo == null)
{
yield return new ValidationResult("* Flat No. Should Filled", new[] { "PR_FlatNo" });
}
if (this.PR_Floor == 0 || this.PR_Floor == null)
{
yield return new ValidationResult("* Floor No. Should Filled", new[] { "PR_Floor" });
}
}
}
Now in above code when I check using debugger at pm.PR_PropertyType, it returns null. So, How can I access that PR_PropertyType property in PropertyDetails_Master class?
I'm having problems with [ObjectValidator]. So, i have:
public class UserBO
{
public int ID
{
get;
set;
}
[NotNullValidator(MessageTemplate = "Can't be null!")]
[RegexValidator(#"[a-z]|[A-Z]|[0-9]*", MessageTemplate = "Must be valid!", Ruleset = "validate_username")]
[StringLengthValidator(5, RangeBoundaryType.Inclusive, 25, RangeBoundaryType.Inclusive, Ruleset = "validate_username")]
public string username
{
get;
set;
}
and another class:
public class PersonBO
{
public int ID
{
get;
set;
}
[NotNullValidator(MessageTemplate="Can't be null!")]
[ObjectValidator(MessageTemplate = "Must be valid!", Ruleset="validate_obj_user")]
public UserBO User
{
get;
set;
}
...
Now can you tell me why the following test passes?
[TestMethod()]
public void PersonBOConstructorTest()
{
PersonBO target = new PersonBO()
{
User = new UserBO
{
ID = 4,
username = "asd"
}
};
ValidationResults vr = Validation.Validate<PersonBO>(target, "validate_obj_user");
Assert.IsTrue(vr.IsValid);
}
This should not be valid, because: User attribute (of UserBO type) contains username "asd" (3 characters), and i defined for it a StringLengthValidator (between 5 and 25 characters).. so why the test passes? that object is not valid
I can't understand.
Thanks.
You have to specify a ruleset in your ObjectValidator if you want rules from a set other than the default set applied.
[ObjectValidator("validate_username", MessageTemplate = "Must be valid!", Ruleset = "validate_obj_user")]
The above should work in this specific case. Alternatively, you could remove the ruleset parameter from the string length validator to leave it in the default set.