PropertyInfo List is null when using reflection with generics - c#

I am using a method to try and iterate through an object to get all of the object's properties, some of which are lists of objects.
This all works except for the part where I check for objects within the initial object's properties. If it finds a list of objects I want it to iterate through them.
Annoyingly, I'm getting null on my list of whatever type.
I'm now getting an error in VS because pt isn't instantiated, but it would be at run time.
Below is the if statement I'm using to try and catch whatever object/List is being parsed.
Am I going the right (roundabout) way or doing this or is this completely wrong?
Problematic Code - if statement null list:
public static string DeconstructLists<T>(T obj, string body)
{
Type type = obj.GetType();
PropertyInfo[] properties = type.GetProperties();
foreach (PropertyInfo property in properties)
{
if (property.PropertyType == typeof(string) || property.PropertyType == typeof(int) || property.PropertyType == typeof(bool))
body += property.Name + " = " + property.GetValue(obj, null) + Environment.NewLine;
else
{
if (typeof(IEnumerable).IsAssignableFrom(property.PropertyType))
{
//null list exception
var list = (IEnumerable)property.GetValue(obj, null);
foreach (var item in list)
{
DeconstructLists(item, body);
}
// stack overflow exception
//var props = property.PropertyType.GetGenericArguments()[0];
//foreach (var p in props.GetProperties())
//{
// DeconstructLists(p, body);
//}
}
}
}
return body;
}
Post create method:
public ActionResult Create(Company cmp)
{
if (ModelState.IsValid)
{
db.companys.Add(cmp);
db.SaveChanges();
// send email
SendEmail(cmp);
return RedirectToAction("Thankyou", "Home", new { form="ASN" });
}
return View(cmp);
}
Send email method:
public static void SendEmail(Company cm)
{
string _body = "";
string _subject = "ASN Form Request";
_body = DeconstructLists<Company>(cm, _body);
using (SmtpClient msgClient = new SmtpClient())
{
msgClient.EnableSsl = false;
msgClient.DeliveryMethod = SmtpDeliveryMethod.Network;
msgClient.UseDefaultCredentials = false;
msgClient.Credentials = new NetworkCredential
{
UserName = "",
Password = ""
};
msgClient.Host = "";
msgClient.Port = 0;
using (MailMessage msg = new MailMessage())
{
msg.To.Add(""); // to add
msg.From = new MailAddress("");// from add
msg.Subject = _subject;
msg.Body = _body;
// preparing the message to be sent
msgClient.Send(msg);
}
}
}
Class objects:
public class Company
{
public int companyId { get; set; }
public string name { get; set; }
public string telephone { get; set; }
public string regNumber { get; set; }
public virtual IList<Asn> asns { get; set; }
public virtual IList<Contact> contacts { get; set; }
}
public class Contact
{
public int contactId { get; set; }
public int companyId { get; set; }
public Company company { get; set; }
public string name { get; set; }
public string telephone { get; set; }
}
public class Asn
{
public int asnId { get; set; }
public int companyId { get; set; }
public Company company { get; set; }
public bool userBehalf { get; set; }
public bool something { get; set; }
}

If it finds a list of objects I want it to iterate through them.
You don't need a list for iteration, the minimum iteratable type is IEnumerable. In fact your if statement is checking just that
if (typeof(IEnumerable).IsAssignableFrom(property.PropertyType))
{
// ...
}
So why not just using inside
var list = (IEnumerable)property.GetValue(obj, null);
foreach (var item in list)
{
DeconstructLists(item, body);
}
EDIT: See this, it works, then see why yours does not:
using System;
using System.Collections;
using System.Collections.Generic;
namespace Tests
{
class Test
{
static void Main(string[] args)
{
var company = new Company
{
companyId = 1,
name = "ACME",
};
company.asns = new List<Asn>
{
new Asn { asnId = 1, companyId = company.companyId, company = company },
new Asn { asnId = 2, companyId = company.companyId, company = company },
};
company.contacts = new List<Contact>
{
new Contact { contactId = 1, companyId = company.companyId, company = company, name = "Contact1" },
new Contact { contactId = 2, companyId = company.companyId, company = company, name = "Contact2" }
};
var body = DeconstructLists(company, "");
}
public static string DeconstructLists<T>(T obj, string body)
{
var type = obj.GetType();
var properties = type.GetProperties();
foreach (var property in properties)
{
if (property.PropertyType == typeof(string) || property.PropertyType == typeof(int) || property.PropertyType == typeof(bool))
body += property.Name + " = " + property.GetValue(obj, null) + Environment.NewLine;
else
{
if (typeof(IEnumerable).IsAssignableFrom(property.PropertyType))
{
body = body + property.Name + ": {" + Environment.NewLine;
var list = (IEnumerable)property.GetValue(obj, null);
foreach (var item in list)
{
body = body + item.GetType().Name + ": {" + DeconstructLists(item, "") + "}" + Environment.NewLine;
}
body = body + "}" + Environment.NewLine;
}
}
}
return body;
}
}
public class Company
{
public int companyId { get; set; }
public string name { get; set; }
public string telephone { get; set; }
public string regNumber { get; set; }
public virtual IList<Asn> asns { get; set; }
public virtual IList<Contact> contacts { get; set; }
}
public class Contact
{
public int contactId { get; set; }
public int companyId { get; set; }
public Company company { get; set; }
public string name { get; set; }
public string telephone { get; set; }
}
public class Asn
{
public int asnId { get; set; }
public int companyId { get; set; }
public Company company { get; set; }
public bool userBehalf { get; set; }
public bool something { get; set; }
}
}

Maybe you could try something like this in your if statement ?
var genericType = property.PropertyType.GetGenericArguments()[0];
foreach (var prop in genericType.GetProperties())
{
DeconstructLists(prop, body);
}
This topic may also help you: How to get the type of T from a member of a generic class or method?

Related

Formatting data in C#

How would you format the following data outcome in C#.
My controller class returns the data like the one below; I used Dapper as ORM.
The problem here is it returns the number of teams times teams members (eg, Teams X Team Members)instead of team members within a team;
[HttpGet("GetMyTeamsDemo")]
public async Task <List<JTeam>> GetMyTeamsDemo(int UId)
{
List<JTeam> teams = new List<JTeam>();
JTeam jteam = new JTeam();
List<JMember> members = new List<JMember>();
var result = await _userDataStore.GetMyTeams(UId);
foreach (var item in result)
{
jteam.TeamId = item.Id;
jteam.TeamName = item.TeamName;
jteam.TeamsCode = item.TeamsCode;
jteam.Description = item.Description;
jteam.CreatedById = item.CreatedById;
jteam.CreatedByName = item.CreatedByName;
jteam.TeamProfilePhoto = item.TeamProfilePhoto;
jteam.CoverPhoto = item.CoverPhoto;
jteam.DateCreated = item.DateCreated;
var res = await _userDataStore.GetTeamMembersByTeamId(item.Id);
if (res is not null)
{
foreach (var item1 in res)
{
members.Add(new JMember { MemberId = item1.MemberId, MemberName = item1.MemberName, TeamId = item1.TeamId });
//teams.Members.Add(new JTeam.JMember { MemberId = itar.MemberId, MemberName = itar.MemberName, TeamId = itar.TeamId });
jteam.Members = members;
teams.Add(jteam);
}
}
jteam = new JTeam();
}
return teams;
}
public class JTeam
{
public int TeamId { get; set; }
public string TeamName { get; set; }
public string TeamsCode { get; set; }
public string Description { get; set; }
public int CreatedById { get; set; }
public string CreatedByName { get; set; }
public string TeamProfilePhoto { get; set; }
public string CoverPhoto { get; set; }
public DateTime DateCreated { get; set; }
public JMember Members { get; set; }
}
public class JMember
{
public int MemberId { get; set; }
public int TeamId { get; set; }
public string MemberName { get; set; }
}
foreach (var item1 in res)
{
...
jteam.Members = members;
teams.Add(jteam);
}
You are adding the team to the list within the foreach for the team members instead of after the members have been added to the team. It's the same object in memory, so when the JSON serializer loops through the "teams" it has multiple references to the same team and adds it multiple times to the JSON.
Move teams.Add(jteam); outside of the inner foreach loop (and the if that contains it).
I would also initialize jteam within the loop instead of defining it outside of the loop and resetting it at the end.

Sending email through http with LinkedResource property

I am trying to compose a email that has a body with a embedded image using LinkedResource. When I try to Post it to our api that connects to the smtp I get this exception:
System.AggregateException: InvalidOperationException: Timeouts are not supported on this stream.
JsonSerializationException: Error getting value 'ReadTimeout' on 'System.IO.FileStream'.
This is my code what do I need to change to make this work.
class Program
{
static void Main(string[] args)
{
Mailer();
}
public static void Mailer()
{
var imageOne = new LinkedResource("PumpkinSpice.jpg", "image/jpeg");
imageOne.ContentId = "psl";
var imageLink = $"<img src=\"cid:{imageOne.ContentId}\"/>";
var listOfResources = new List<LinkedResource> {imageOne};
var email = new MailInfo()
{
ToAddresses = "mcjibbles#foo.com",
CcAddresses = "jibbles#bar.com",
BccAddresses = null,
Subject = "test email",
Body = "Email body" + imageLink,
Resources = listOfResources,
IsHtml = true
};
using (var client = CreateClient())
{
client.PostAsJsonAsync($"api/email", email).Wait();
}
}
private static HttpClient CreateClient()
{
return new HttpClient(new HttpClientHandler { UseDefaultCredentials = true })
{
BaseAddress = new Uri("http://fakesite.com/")
};
}
}
class MailInfo
{
public string ToAddresses { get; set; }
public string CcAddresses { get; set; }
public string BccAddresses { get; set; }
public string Subject { get; set; }
public string Body { get; set; }
public bool IsHtml { get; set; }
public List<LinkedResource> Resources { get; set; }
}
Api Code
public class EmailController : ApiController
{
[HttpPost]
public void SendEmail(EmailInformation information)
{
using (var message = new MailMessage())
{
message.Subject = information.Subject;
var toAddresses = SplitAddresses(information.ToAddresses);
foreach (var address in toAddresses)
{
message.To.Add(new MailAddress(address));
}
message.IsBodyHtml = information.IsHtml;
message.Body = information.Body;
using (var htmlView = AlternateView.CreateAlternateViewFromString(message.Body, null, "text/html"))
{
foreach (var resource in information.Resources)
{
htmlView.LinkedResources.Add(resource);
}
using (var client = new SmtpClient(WebConfigurationManager.AppSettings["Smtp"]))
{
message.From = information.FromAddress;
client.Send(message);
}
}
}
}
public class EmailInformation
{
public string ToAddresses { get; set; }
public string FromAddress { get; set; }
public string CcAddresses { get; set; }
public string BccAddresses { get; set; }
public string Subject { get; set; }
public string Body { get; set; }
public bool IsHtml { get; set; }
public List<LinkedResource> Resources { get; set; }
}
}

C#: Unable to get Historical Yahoo data (one day interval) for maximum range

I have used below Yahoo API to get historical data for,
Index: GDX
Range: max
Interval: 1d
https://query1.finance.yahoo.com/v7/finance/chart/GDX?range=max&interval=1d&indicators=quote&includeTimestamps=true&includePrePost=false&corsDomain=finance.yahoo.com
Here, I have two scenarios
I want last 25 years of data, if any index has data more than 25 years. (For instance, MSFT)
I want data from the first date's data to current date, if any index has data less than 25 years. (For instance, GDX)
But, I got result for one month only. I'm unable to get interval of one day data. please refer the below image.
I solved the problem by checking the firstTradaDate value from meta tag of the Yahoo API response.
This firstTradeDate hold the value of the first trading date of each Index.
In my case,
GDX's first trade date was: 1148284800 (Unix Time stamp) equivalent to GMT: Monday, May 22, 2006 8:00:00 AM.
So, I just simply pass the parameters as,
Index: GDX
Range: 25y
Interval: 1d
Now, Iterate the response array and check the time stamp from the response against firstTradeDate whether it is less than. If it is less than firstTradeDate, then I simply continue the loop. Or else, I added the record as valid one.
Here below is my code snippet
public class YahooJson2CsvController : ApiController
{
public HttpResponseMessage GetJson2Csv(string code, string range, string interval)
{
try
{
AppendLog("============================================");
AppendLog("Starting to download { CODE: " + code + "; RANGE: " + range + "; INTERVAL: " + interval + " }");
var csvBasePath = HttpContext.Current.Server.MapPath("~/YahooCSV/");
var objCsvBasePath = new DirectoryInfo(csvBasePath);
if (!objCsvBasePath.Exists) { objCsvBasePath.Create(); AppendLog("YahooCSV folder created"); }
var csvYesterdayPath = HttpContext.Current.Server.MapPath("~/YahooCSV/" + DateTime.Now.AddDays(-1).ToString("MMddyyyy") + "/");
var objCsvYesterdayPath = new DirectoryInfo(csvYesterdayPath);
if (objCsvYesterdayPath.Exists) { objCsvYesterdayPath.Delete(true); AppendLog("Deleted yesterday's download folder"); }
var csvTodayPath = HttpContext.Current.Server.MapPath("~/YahooCSV/" + DateTime.Now.ToString("MMddyyyy") + "/");
var objCsvTodayPath = new DirectoryInfo(csvTodayPath);
if (!objCsvTodayPath.Exists) { objCsvTodayPath.Create(); AppendLog("Created today's download folder"); }
if (string.IsNullOrEmpty(code.Trim())) return new HttpResponseMessage(HttpStatusCode.BadRequest);
if (string.IsNullOrEmpty(range.Trim())) return new HttpResponseMessage(HttpStatusCode.BadRequest);
if (string.IsNullOrEmpty(interval.Trim())) return new HttpResponseMessage(HttpStatusCode.BadRequest);
var wc = new WebClient();
var url = ConfigurationManager.AppSettings["YahooURL"].Replace("#C", code).Replace("#R", range).Replace("#I", interval);
var str = wc.DownloadString(url);
if (string.IsNullOrEmpty(str)) { AppendLog("No content for current code"); return new HttpResponseMessage(HttpStatusCode.NoContent); }
AppendLog("Downloaded content for current code");
var data = JsonConvert.DeserializeObject<RootObject>(str);
if (data == null) { AppendLog("Empty deserialized object"); return new HttpResponseMessage(HttpStatusCode.NoContent); }
var result = new List<string>();
var quotesInfo = data.chart.result.First();
for (var i = 0; i < quotesInfo.timestamp.Count; i++)
{
if (quotesInfo.meta.firstTradeDate != null && quotesInfo.timestamp[i] < quotesInfo.meta.firstTradeDate) continue;
var quotesStr = new List<string>();
var quoteData = quotesInfo.indicators.quote.First();
var quoteAdjData = quotesInfo.indicators.unadjclose.First();
quotesStr.Add(UnixTimeStampToDateTime(quotesInfo.timestamp[i]).ToString(CultureInfo.InvariantCulture));
quotesStr.Add(quoteData.open[i].HasValue ? quoteData.open[i].ToString() : string.Empty);
quotesStr.Add(quoteData.high[i].HasValue ? quoteData.high[i].ToString() : string.Empty);
quotesStr.Add(quoteData.low[i].HasValue ? quoteData.low[i].ToString() : string.Empty);
quotesStr.Add(quoteData.close[i].HasValue ? quoteData.close[i].ToString() : string.Empty);
quotesStr.Add(quoteData.volume[i] != null ? quoteData.volume[i].ToString() : string.Empty);
quotesStr.Add(quoteAdjData.unadjclose[i].HasValue ? quoteAdjData.unadjclose[i].ToString() : string.Empty);
result.Add(string.Join(",", quotesStr));
}
if (result.Count <= 0) { AppendLog("No valid content to deserialize"); return new HttpResponseMessage(HttpStatusCode.NoContent); }
AppendLog("Deserialized successful");
var tempFileName = code + "_" + DateTime.Now.ToString("MMddyyyyHHmmss") + ".csv";
File.WriteAllLines(csvTodayPath + tempFileName, result);
AppendLog("Created temp csv file to download");
var memStream = new MemoryStream();
using (var fileStream = File.OpenRead(csvTodayPath + tempFileName))
{
memStream.SetLength(fileStream.Length);
fileStream.Read(memStream.GetBuffer(), 0, (int)fileStream.Length);
}
var csvResult = new HttpResponseMessage(HttpStatusCode.OK) { Content = new StreamContent(memStream) };
csvResult.Content.Headers.ContentType = new MediaTypeHeaderValue("text/csv");
csvResult.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment") { FileName = code + ".csv" };
AppendLog("Downloaded: " + tempFileName);
return csvResult;
}
catch (Exception ex)
{
AppendLog(ex.Message);
return new HttpResponseMessage(HttpStatusCode.ExpectationFailed);
}
}
public static DateTime UnixTimeStampToDateTime(double unixTimeStamp)
{
var dtDateTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
dtDateTime = dtDateTime.AddSeconds(unixTimeStamp).ToUniversalTime();
return dtDateTime;
}
public static void AppendLog(string Log)
{
StreamWriter sw = File.AppendText(System.AppDomain.CurrentDomain.BaseDirectory + "Log.log");
sw.WriteLine(DateTime.Now.ToString("yyyy-MM-dd hh:mm:ss") + " :: " + Log);
sw.Close();
sw.Dispose();
}
}
public class Pre
{
public string timezone { get; set; }
public int end { get; set; }
public int start { get; set; }
public int gmtoffset { get; set; }
}
public class Regular
{
public string timezone { get; set; }
public int end { get; set; }
public int start { get; set; }
public int gmtoffset { get; set; }
}
public class Post
{
public string timezone { get; set; }
public int end { get; set; }
public int start { get; set; }
public int gmtoffset { get; set; }
}
public class CurrentTradingPeriod
{
public Pre pre { get; set; }
public Regular regular { get; set; }
public Post post { get; set; }
}
public class Meta
{
public string currency { get; set; }
public string symbol { get; set; }
public string exchangeName { get; set; }
public string instrumentType { get; set; }
public int? firstTradeDate { get; set; }
public int gmtoffset { get; set; }
public string timezone { get; set; }
public string exchangeTimezoneName { get; set; }
public CurrentTradingPeriod currentTradingPeriod { get; set; }
public string dataGranularity { get; set; }
public List<string> validRanges { get; set; }
}
public class Quote
{
public List<object> volume { get; set; }
public List<double?> low { get; set; }
public List<double?> high { get; set; }
public List<double?> close { get; set; }
public List<double?> open { get; set; }
}
public class Unadjclose
{
public List<double?> unadjclose { get; set; }
}
public class Unadjquote
{
public List<double?> unadjopen { get; set; }
public List<double?> unadjclose { get; set; }
public List<double?> unadjhigh { get; set; }
public List<double?> unadjlow { get; set; }
}
public class Indicators
{
public List<Quote> quote { get; set; }
public List<Unadjclose> unadjclose { get; set; }
public List<Unadjquote> unadjquote { get; set; }
}
public class Result
{
public Meta meta { get; set; }
public List<int> timestamp { get; set; }
public Indicators indicators { get; set; }
}
public class Chart
{
public List<Result> result { get; set; }
public object error { get; set; }
}
public class RootObject
{
public Chart chart { get; set; }
}

How to get the complex field data in class using Reflection (C#)

I have a class as below
public class Orders
{
public Orders()
{
}
public Orders(long OrderId, string CustomerId,
int EmployeeId, double Freight, Info emp)
{
this.OrderID = OrderId;
this.CustomerID = CustomerId;
this.EmployeeID = EmployeeId;
this.Freight = Freight;
this.Employee = emp;
}
public long OrderID { get; set; }
public string CustomerID { get; set; }
public int EmployeeID { get; set; }
public double Freight { get; set; }
public Info Employee { get; set; }
}
public class Info
{
public string Address { get; set; }
public Info(string Add) {
this.Address = Add;
}
}
To extract the values i am doing as below.
public IEnumerable PerformFiltering(
IEnumerable dataSource, List<FilteredColumn> filteredColumns)
{
var paramExpression = dataSource.AsQueryable().Parameter();
Type type = dataSource.GetElementType();
if (type == null)
{
Type type1 = dataSource.GetType();
type = type1.GetElementType();
}
Type t = typeof(object);
foreach (var filteredColumn in filteredColumns)
{
Syncfusion.Linq.FilterType filterType = (Syncfusion.Linq.FilterType)Enum.Parse(
typeof(Syncfusion.Linq.FilterType),
filteredColumn.Operator.ToString(), true);
t = type.GetProperty(filteredColumn.Field).PropertyType;
Type underlyingType = Nullable.GetUnderlyingType(t);
}
}
I got the error
An exception of type 'System.NullReferenceException' occurred in Syncfusion.EJ.dll but was not handled in user code

Getting and setting the value of nested properties

I have these classes
public class Employee
{
public int ID { get; set; }
public string Name { get; set; }
public virtual Address Address { get; set; }
}
public class Address
{
public string Line1 { get; set; }
public string Line2 { get; set; }
}
public class Flat
{
public int ID { get; set; }
public string Name { get; set; }
public virtual Address Address { get; set; }
}
This is the code I am using to set the values on Flat class
var employee = new Employee() { ID = 1, Name = "Test", Address = new Address() {Line1 = "1", Line2 = "2" } };
Flat flat = new Flat();
Map(employee, flat);
static void Map<TI, VI>(TI source, VI result)
{
foreach (PropertyInfo item source.GetType().GetRuntimeProperties())
{
if (item.GetValue(source) != null)
{
if (result.GetType().GetRuntimeProperty(item.Name) != null)
{
Type type = result.GetType().GetRuntimeProperty(item.Name).PropertyType;
var innerObj = FormatterServices.GetUninitializedObject(type);
result.GetType().GetRuntimeProperty(item.Name).SetValue(result, innerObj);
Map(item.GetValue(source), innerObj);
}
else
{
Map(item.GetValue(source), result);
}
}
}
}
}
I would really appreciate if you could advise me if this is the right approach to map the nested properties. If this is not the case please provide alternatives.

Categories

Resources