Why is this foreach loop missing a property from the class? - c#

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 :)

Related

how to access the class properties based on condition

I have below the object and its internal objects and I have added custom attributes to the property names which are required for the report.
public class Space
{
public SpaceIdentity SpaceIdentity { get; set; } = new();
public SpaceGeometry SpaceGeometry { get; set; } = new();
public AirBalance AirBalance { get; set; } = new();
public EngineeringChecks EngineeringChecks { get; set; } = new();
}
public class SpaceIdentity
{
public int ElementId { get; set; } // Not required
[DisplayNameWithUnits(DisplayName = "Space Number", IsIncludedInReport2 = true)]
public string Number { get; set; }
[DisplayNameWithUnits(DisplayName = "Space Name", IsIncludedInReport2 = true, IsIncludedInReport1 = true)]
public string Name { get; set; }
[DisplayNameWithUnits(DisplayName = "Room Number", IsIncludedInReport1 = true)]
public string RoomNumber { get; set; }
[DisplayNameWithUnits(DisplayName = "Room Name", IsIncludedInReport1 = true)]
public string RoomName { get; set; }
}
public class SpaceGeometry
{
public Vertex LocationPoint { get; set; } // this is not required
[DisplayNameWithUnits(DisplayName = "Space Area", Units = "(ft²)", IsIncludedInReport1 = true)]
public double FloorArea { get; set; }
}
.....
Here I am building an excel report, which I want to use property display name's as header column names of that report. Here are some of the properties attribute information used in multiple reports. What I did was I added a bool condition attribute like (isIncludedInReport1) and loop through the properties of space and loop through the properties of inner object(SpaceGeometry) to get a particular property name and its attribute values based on this boolean condition.
What I am looking for here is without adding these bool attributes, is there any way to access the property names based on condition. I thought about adding interfaces, but that is not possible here because I have multiple inner classes having properties that I need to include in a single report.
Could anyone please let me know is there any other way to achieve this?
Update:
var columnResult = new OrderedDictionary();
GetReportHeaderColumnName(typeof(Space), columnResult);
public static void GetReportHeaderColumnName(Type type, OrderedDictionary headerNameByUnit)
{
var properties = type.GetProperties();
foreach (var propertyInfo in properties)
{
if (propertyInfo.PropertyType.IsClass && !propertyInfo.PropertyType.FullName.StartsWith("System."))
{
if (propertyInfo.PropertyType == typeof(Overridable<double>))
{
AddReportHeaderName(headerNameByUnit, propertyInfo);
}
else
{
GetReportHeaderColumnName(propertyInfo.PropertyType, headerNameByUnit);
}
}
else
{
AddReportHeaderName(headerNameByUnit, propertyInfo);
}
}
}
protected static void AddReportHeaderName(OrderedDictionary columnResult, PropertyInfo propertyInfo)
{
if (propertyInfo.GetCustomAttributes(typeof(DisplayNameWithUnitsAttribute), true).Any())
{
var displayNameWithUnitsAttribute = (DisplayNameWithUnitsAttribute)Attribute.GetCustomAttribute(propertyInfo, typeof(DisplayNameWithUnitsAttribute));
if (displayNameWithUnitsAttribute.IsIncludedInReport2)
{
columnResult.Add(displayNameWithUnitsAttribute.DisplayName, displayNameWithUnitsAttribute.Units);
}
}
}
Another way that doesn't use reflection is to just use a list of ReportProperty
public record ReportProperty<T>(
Func<T, string> ValueFunc,
string DisplayName,
string? Unit = null
);
List<ReportProperty<Space>> report1 = new(){
new( s => s.SpaceIdentity.Number, "Space Number"),
new( s => s.SpaceIdentity.RoomNumber, "Room Number"),
new( s => s.SpaceIdentity.RoomName, "Room Name"),
new( s => s.SpaceGeometry.FloorArea.ToString(), "Floor Area", "Ft2"),
};
What do you want to achieve by making it another way? I assume you want to make your code scalable so adding more reports is easier.
Do you still want to use attributes on the classes themselves? If so, you could make a new attribute, and tag your properties like so:
public class SpaceIdentity
{
public int ElementId { get; set; } // Not required
[DisplayNameWithUnits(DisplayName = "Space Number")]
[IncludeInReport(2)]
public string Number { get; set; }
[DisplayNameWithUnits(DisplayName = "Space Name")]
[IncludeInReport(1)]
[IncludeInReport(2)]
public string Name { get; set; }
[DisplayNameWithUnits(DisplayName = "Room Number")]
[IncludeInReport(1)]
public string RoomNumber { get; set; }
[DisplayNameWithUnits(DisplayName = "Room Name")]
[IncludeInReport(1)]
public string RoomName { get; set; }
}
Or do you want to separate the report from the classes, so your report is defined in another file? If so, maybe a 2-dimensional list of property names would do the trick:
List<List<string>> Report1 = new()
{
new(){nameof(Space.SpaceIdentity), nameof(SpaceIdentity.Name)},
new(){nameof(Space.SpaceIdentity), nameof(SpaceIdentity.RoomNumber)},
new(){nameof(Space.SpaceIdentity), nameof(SpaceIdentity.RoomName)},
new(){nameof(Space.SpaceGeometry), nameof(SpaceGeometry.FloorArea)}
};

Json.Net Deserializing list of c# objects throwing error

I have a list of objects in below json format. I would like to deserialize using below code. It is throwing unable to convert to object error. I have tried below three options, but didnt help. jsoninput is a IEnumerable<string>converted into json object using ToJson().
Error:
{"Error converting value \"{\"id\":\"11ef2c75-9a6d-4cef-8163-94daad4f8397\",\"name\":\"bracing\",\"lastName\":\"male\",\"profilePictureUrl\":null,\"smallUrl\":null,\"thumbnailUrl\":null,\"country\":null,\"isInvalid\":false,\"userType\":0,\"profilePrivacy\":1,\"chatPrivacy\":1,\"callPrivacy\":0}\" to type 'Api.Models.UserInfo'. Path '[0]', line 1, position 271."}
var requests1 = JsonConvert.DeserializeObject<UsersInfo>(jsoninput);
var requests2 = JsonConvert.DeserializeObject<IEnumerable<UserInfo>>(jsoninput);
var requests3 = JsonConvert.DeserializeObject<List<UserInfo>>(jsoninput);
//Below are my classes,
public class UsersInfo
{
public List<UserInfo> UserInfoList { get; set; }
public UsersInfo()
{
UserInfoList = new List<UserInfo>();
}
}
public class UserInfo
{
public string Id { set; get; }
public string Name { set; get; }
public string LastName { set; get; }
public string ProfilePictureUrl { set; get; }
public string SmallUrl { set; get; }
public string ThumbnailUrl { get; set; }
public string Country { set; get; }
public bool IsInvalid { set; get; }
}
Below is my json object,
["{\"id\":\"11ef2c75-9a6d-4cef-8163-94daad4f8397\",\"name\":\"bracing\",\"lastName\":\"male\",\"profilePictureUrl\":null,\"smallUrl\":null,\"thumbnailUrl\":null,\"country\":null,\"isInvalid\":false}","{\"id\":\"318c0885-2720-472c-ba9e-1d1e120bcf65\",\"name\":\"locomotives\",\"lastName\":\"riddles\",\"profilePictureUrl\":null,\"smallUrl\":null,\"thumbnailUrl\":null,\"country\":null,\"isInvalid\":false}"]
Looping through individual items in json input and if i deserialize it like below, it works fine. But i want to deserialize the list fully. Note: jsoninput was a IEnumerable<string> before i convert in json object.
foreach (var re in jsoninput)
{
var request0 = JsonConvert.DeserializeObject<UserInfo>(re);
}
Please look at this fiddle: https://dotnetfiddle.net/XpjuL4
This is the code:
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
//Below are my classes,
public class UsersInfo
{
public List<UserInfo> UserInfoList { get; set; }
public UsersInfo()
{
UserInfoList = new List<UserInfo>();
}
}
public class UserInfo
{
public string Id { set; get; }
public string Name { set; get; }
public string LastName { set; get; }
public string ProfilePictureUrl { set; get; }
public string SmallUrl { set; get; }
public string ThumbnailUrl { get; set; }
public string Country { set; get; }
public bool IsInvalid { set; get; }
}
public class Program
{
public static void Main()
{
Console.WriteLine("Hello World");
Option1();
Option2();
}
public static void Option1(){
string json = #"{""UserInfoList"":[
{""id"":""11ef2c75 - 9a6d - 4cef - 8163 - 94daad4f8397"",""name"":""bracing"",""lastName"":""male"",""profilePictureUrl"":null,""smallUrl"":null,""thumbnailUrl"":null,""country"":null,""isInvalid"":false},
{ ""id"":""318c0885-2720-472c-ba9e-1d1e120bcf65"",""name"":""locomotives"",""lastName"":""riddles"",""profilePictureUrl"":null,""smallUrl"":null,""thumbnailUrl"":null,""country"":null,""isInvalid"":false}
]}";
var obj = JsonConvert.DeserializeObject<UsersInfo>(json);
obj.UserInfoList.ForEach(e => Console.WriteLine(e.Id));
}
public static void Option2(){
string json = #"[
{""id"":""11ef2c75 - 9a6d - 4cef - 8163 - 94daad4f8397"",""name"":""bracing"",""lastName"":""male"",""profilePictureUrl"":null,""smallUrl"":null,""thumbnailUrl"":null,""country"":null,""isInvalid"":false},
{ ""id"":""318c0885-2720-472c-ba9e-1d1e120bcf65"",""name"":""locomotives"",""lastName"":""riddles"",""profilePictureUrl"":null,""smallUrl"":null,""thumbnailUrl"":null,""country"":null,""isInvalid"":false}
]";
var obj = JsonConvert.DeserializeObject<List<UserInfo>>(json);
obj.ForEach(e => Console.WriteLine(e.Id));
}
}
Both work, and are basically very close to what you are doing. You can either serialize it as a list (based on your json, I think that's the closest to your use case, and that's Option 2).
However, put extra attention to the JSON. I had to re-parse your JSON to make it work (https://jsonformatter.org/json-parser is a nice website to do it). For the sake of explaining the example, in C#, # means raw string, and in raw string, quotes are escaped with double quotes "".
I would expect that the business logic generating this JSON is not correct, if the JSON you pasted is the direct result from it.
EDIT
Given the OP's comment:
Thanks Tu.ma for your thoughts. The other method returns
IEnumerable which is nothing but
Dictionary.Where(x => x.Value == null).Select(x =>
x.Key).ToHashSet(). The values in Dictionary are -> Key
is String, Value is UserInfo object serialized. So, in that case i
should deserialize one by one? If not, i should serialize entire list
in one shot? Am i right? – Raj 12 hours ago
The problem is in the way you are generating the list of UsersInfo. The result from Dictionary<string,string>.Where(x => x.Value == null).Select(x =>
x.Key).ToHashSet() is a bunch of strings, not of objects, so you need to serialize them one by one.
If you are worried about the linearity of the approach, you could consider running through it in parallel. Of course, you need to judge if it fits your application.
var userInfoStrings = Dictionary<string,string>.Where(x => x.Value == null).Select(x => x.Key).ToHashSet();
var UserInfoList = userInfoStrings.AsParallel().Select (u => JsonConvert.DeserializeObject<UsersInfo>(u)).ToList();

Separate Model with List

I want to return a list of links to a web page when it loads. Right now I have a model called SsoLink.cs bound to the page. I would like to return a list, so I have created another model called SsoLinks.cs that has a List. In my helper function, I keep getting "object not set to an instance of an object".
SsoLink.cs
public class SsoLink
{
public enum TypesOfLinks
{
[Display(Name="Please Select a Type")]
Types,
Collaboration,
[Display(Name="Backups & Storage")]
Backups_Storage,
Development,
[Display(Name="Cloud Services")]
Cloud_Services,
[Display(Name="Human Resources")]
Human_Resources,
Analytics
}
public string Id { get; set; }
public string Name { get; set; }
public string Url { get; set; }
public string Owner { get; set; }
public string OwnerEmail { get; set; }
public string LinkDescription { get; set; }
public TypesOfLinks LinkType { get; set; }
}
SsoLinks.cs
public class SsoLinks
{
public List<SsoLink> Links {get; set;}
}
GetLinksHelper.cs
public partial class SsoLinkHelper
{
public static SsoLinks GetLinks()
{
var ssoList = new SsoLinks();
try
{
//search the index for all sso entries
var searchResponse = _client.Search<SsoLink>(s => s
.Index(_ssoLinkIndex)
.Size(500)
.Query(q => q
.MatchAll()
)
);
if (searchResponse.Documents.Count == 0)
{
return ssoList;
}
ssoList.Links.AddRange(searchResponse.Hits.Select(hit => new SsoLink() {Id = hit.Source.Id, Name = hit.Source.Name, Url = hit.Source.Url, Owner = hit.Source.Owner}));
return ssoList;
}
catch (Exception e)
{
Log.Error(e, "Web.Helpers.SsoLinkHelper.GetLinks");
return ssoList;
}
}
}
While debugging, It is failing at SsoLinks.Links.AddRange(etc). How can I add a new SsoLink to the ssoList for every item found in my query?
Edit: Here is a screenshot of the error while debugging.
The null reference exception looks like it comes from ssoList.Links being null when calling AddRange on it, so it needs to be initialized to a new instance of List<SsoLink> before calling AddRange().
Russ's answer led me down the right path, I ended up just needing to change my view to:
#model List<SharedModels.Models.SsoLink>
rather than
#model SharedModels.Models.SsoLink
and do away with the SsoLinks model.

what's an elegant method to transfer/cast a poco of 1 type to a poco of another type - different namespace but same name and fields?

Let's say you have a Domain layer which returns a User object:
public class User
{
public string FirstName{get;set;}
public string LastName{get;set;}
}
Let's say you have an identical class defined in your service layer. What's an elegant method to easily transfer/cast the Domain User object into a service User object?
"Elegant" is subjective. I might just write an extension that converts one to the other.
public static class MappingExtensions
{
public ThisNameSpace.User ToThisUser(this OtherNameSpace.User source)
{
return new ThisNameSpace.User
{
FirstName = source.FirstName,
LastName = source.LastName,
UserId = source.UserId
}
}
}
To me that's the simplest.
You could also use Automapper (add from Nuget.)
Do a one-time configuration:
AutoMapper.Mapper.Initialize(c=>c.CreateMap<User,Other.User>());
and then you can call it to map an instance of User to a new instance of Other.User.
var other = AutoMapper.Mapper.Map<Other.User>(user);
It works without specifying the mapping for individual properties if the property names and types are identical.
You can use reflection:
using System.Windows.Forms;
using System.Reflection;
namespace First
{
public class User
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string UserID { get; set; }
public User()
{
}
}
}
namespace Second
{
public class User
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string UserID { get; set; }
public User()
{
}
}
}
namespace YetAnotherNamespace
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
First.User user = new First.User()
{
FirstName = "John",
LastName = "Public",
UserID = "jpublic#mydomain.com"
};
Second.User newUser = ConvertUser(user);
}
public Second.User ConvertUser(First.User oldUser)
{
Second.User newUser = new Second.User();
foreach (PropertyInfo prop in oldUser.GetType().GetProperties())
{
string propertyName = prop.Name;
object propertyValue = prop.GetValue(oldUser);
newUser.GetType().GetProperty(propertyName).SetValue(newUser, propertyValue);
}
return newUser;
}
}
}
In this code sample, when I instantiate the form, I create a First.User and convert it to a Second.User.
This is tested and working (as a strongly-typed method). I think you can make it more generic and accept and return an "object", and it will just throw an exception if the properties don't match up. Also keep in mind that reflection tends to be slow - this may not be the most scalable solution.
Another approach would be serializing to json and then deserializing to the other type.

Querying for object based on property of inside object

I have some objects stored in a LiteDB database. I'm trying to get a result of all CostBasisTradeSessionObjects that include Marked objects with a particular name, MarkedNameString. I find the Marked object easily enough, but I dont now how to query for object in object.
public string Marked
{
public ObjectId MarkedId { get; set; }
public String Name { get; set; }
}
public class CostBasisTradeSessionObject
{
public ObjectId CostBasisTradeSessionId { get; set; }
public bool IsActive { get; set; }
public DateTime SessionStarted { get; set; }
public DateTime SessionClosed { get; set; }
public Marked Marked { get; set; }
}
using (var db = new LiteDatabase(#"CostBasesTradeSessionsDatabase.db"))
{
var costBasisTradeSessionObjects = db.GetCollection("costBasisTradeSessionObjects");
Marked marked = db.GetCollection<Marked>("markeds").Find(Query.EQ("Name", "<MarkedNameString>")).Single();
}
So I try to get an result with CostBasisTradeSessionObject objects that includes the marked object returned in var marked.
So I tried a couple of things
var cb = costBasisTradeSessionObjects.Include(x => x.Marked).Equals(marked);
and justing jusing the MarkedNameString directory
var results = costBasisTradeSessionObjects.(Query.("Marked.name", "MarkedNameString"));
or
var results = costBasisTradeSessionObjects.Find(x => x.Marked.Name.Equals("MarkedNameString"));
but all the things I tried return an empty result or dont work.
Regards
I believe you're looking for the Where() method. You can filter your search by your Name property, and return an IEnumerable of CostBasisTradeSessionObject.
var results = costBasisTradeSessionObjects
.Where(x => x.Marked.Name == "MarkedNameString");

Categories