I have a client that sends SomeComplexObject to a webservice.
I want the webservice to be unaware of the structure of the data,
so want the data to be deserialized to a dynamic that is passed then to a method that knows how to deal with it.
I use System.Web.Helpers.Json.Encode(), Decode() methods
and I have a problem when SomeComplexObject contains a collection.
It is deserialized to a DynamicJsonArray but it is somehow not accessible
for the consumer of the data.
These are Model types.
public class Aaa
{
public Bbb B { get; set; }
public List<Ccc> Cccs { get; set; }
}
public class Bbb
{
public long Key { get; set; }
public string Name { get; set; }
public DateTime DateOfBirth { get; set; }
}
public class Ccc
{
public string Name { get; set; }
public int Age { get; set; }
}
Let us say that I have a myAaa object of type Aaa with a property of type Bbb
and a list of 42 Ccc objects.
Now:
var MyAaaSerialized = System.Web.Helpers.Json.Encode(MyAaa);
Then I send it and then:
var MyAaaDeserialized = System.Web.Helpers.Json.Decode(MyAaaSerialized);
And finally:
This is the code of the consumer. Email and emailService are Postal classes.
dynamic email = new Email(template);
email.Data = MyAaaDeserialized;
email.User = user;
this.emailService.Send(email);
I can see in the sent email that properties of B object are accessible.
However, properties of members of Cccs list are not.
Of course with email.Data = MyAaa; everything works OK.
Is there any dead simple way to serialize/deserialize a complex object that contains a collection?
Thanks in advance,
If I haven't misunderstood your question, it can be resolved by using the Decode overload that uses generics as below. Just specifying the type (Aaa in this case) worked for me in the code below and it printed all ccc properties correctly:
var myAaa = new Aaa()
{
B = new Bbb() { Name = "someone", DateOfBirth = DateTime.Today.AddYears(-20) },
Cccs = Enumerable.Repeat<Ccc>(new Ccc() { Age = 20, Name = "someone else" }, 42).ToList()
};
var MyAaaSerialized = System.Web.Helpers.Json.Encode(myAaa);
var MyAaaDeserialized = System.Web.Helpers.Json.Decode<Aaa>(MyAaaSerialized);
dynamic data = MyAaaDeserialized;
foreach (Ccc newCccs in data.Cccs)
{
Console.WriteLine($"{newCccs.Name}\t{newCccs.Age}");
}
Ok, I was wrong. System.Web.Helpers.Json.Encode() and Decode() work correctly. I`ll tell you what happened:
I used to use Postal mailsender locally, i.e. I used to take my super-complex POCO object and pass it as a dynamic to Postal Email class. And it worked.
Now I want some webservice to send emails so I need to pass serialized data to it. I have sent a test email and I`ve seen that values of some of the fields are not present in the email message, but 'DynamicJsonObject' is there instead. So I assumed (wrong), that the structure of deserialized object is somehow corrupted.
But the truth is different:
The missing data is of complex type, say
public class Money
{
public double Amount {get; set;}
public string Currency {get; set;}
public override ToString()
{...}
}
So when I asked in Razor email template for:
#item.Prices.SalePriceGross locally,
it somehow must have used the ToString() method.
And of course in a serialized / deserialized object there is no knowledge about the ToString() method.
I am going to need to expose a property with the string I want to display or (better) access Amount and Currency explicitly and process them in Razor email template.
Thank you for help.
Related
I want to create a method that displays the information contained in an object, that will work dynamically, with any object. I'm having trouble handling properties that are other custom classes. In the example below the Person has Phones and Occupations which both are other classes. When the data is displayed, the value on the screen currently is:
TestReflection.Person
Name: Mary
Phones: TestReflection.Phones
Occupations: TestReflection.Occupations
It just displays the name of class, like TestReflection.Phones, rather than the data inside that object.
How can I change this code to show information like this instead?
TestReflection.Person
Name: Mary
Phones:
TestReflection.Phones
Type: 1
Number: 555XYZ
Occupations:
TestReflection.Occupations
Type: 5
Description: Secretary
Here is my code:
class Program
{
static void Main(string[] args)
{
List<Person> listPeson = new List<Person>();
var person1 = new Person();
person1.Name = "Mary";
person1.Phones = new Phones { new Phone { Type = 1, Number = "555XYZ" } };
person1.Occupations = new Occupations {new Occupation { Type = 5, Description = "Secretary" }};
listPeson.Add(person1);
DynamicExport(listPeson);
Console.ReadLine();
}
public static void DynamicExport<T>(List<T> listReg)
{
for (int i = 0; i < listReg.Count; i++)
{
Console.WriteLine(listReg[i].GetType());
foreach (var item in listReg[i].GetType().GetProperties())
{
Console.WriteLine($"{item.Name}: {item.GetValue(listReg[i], null)}");
}
}
}
}
class Person
{
public string Name { get; set; }
public Phones Phones { get; set; }
public Occupations Occupations { get; set; }
}
class Phones : List<Phone> { }
class Phone
{
public int Type { get; set; }
public string Number { get; set; }
}
class Occupations : List<Occupation> { }
class Occupation
{
public int Type { get; set; }
public string Description { get; set; }
}
I made some edits to your question - I hope I understood you correctly.
If you want to export data
If your question is really about displaying data, then there are better ways to do it than creating your own export method. The format you are trying to display looks similar to YAML. There's also JSON and XML. Using one of these libraries is probably better than writing your own method:
YamlDotNet NuGet package
Json.NET NuGet Package
System.Xml.Serialization.XmlSerializer class
If you want to learn more about reflection
Maybe you're interested in learning more about reflection, and the export is just an example to play around with it. In that case, let's look at this line:
Console.WriteLine($"{item.Name}: {item.GetValue(listReg[i], null)}");
$"{item.GetValue(listReg[i], null)}" ends up calling person1.Phones.ToString(). The default behavior of ToString just displays the type name. You could override that behavior, like this:
class Phones : List<Phone>
{
public override string ToString()
{
return Program.DynamicExportToString(this);
// ... where DynamicExportToString is a modified version of DynamicExport that
// builds and returns a string rather than sending it directly to the Console.
}
}
Maybe you want to be able to handle any class, even when you cannot override ToString in all of the classes you might export. Then you will need to put some additional logic in the DynamicExport method, because...
$"{item.Name}: {item.GetValue(listReg[i], null)}"
... doesn't work for every situation. We need to display different things depending on the type of the property.
Consider how you want to handle null values. Maybe something like $"{item.Name}: <null>"
Use your existing $"..." code if the type is...
a primitive type.
DateTime
String
... or a Nullable<> of one of those types.
If the type implements IEnumerable, loop over the contents of the collection and recursively call your export code for each element.
It's important to check for this interface after you've checked if the type is a String, because String implements IEnumerable.
Otherwise, recursively call your export code on this value.
When you call your export code recursively, it would be wise to guard against infinite loops. If the object you're trying to export contains a circular reference - you could quickly wind up with a StackOverflowException. To avoid this, maintain a stack of objects that have already been visited.
I think the above advice is generally applicable whenever you're using reflection to traverse an object graph - whether it's for serialization or any other purpose.
I hope this helps!
I am using C# MVC
I am importing a Json file from a location to an Rest API. In order to display the data. the program needs to know the name of the tags.
I would like to know if there is a way to get the tags from the files in an array of strings and declare them via program instead of manually inputting them.
Current C# Code
public class Person
{
public int Id { get; set; }
public int SomeID { get; set; }
public int Number { get; set; }
public string Name { get; set; }
public string Address { get; set; }
}
Current Json File:
[{"Id":212,"SomeID":71,"Number":0,"Name":"Mr Jones Mones","Address":"21, street, city"}
So as mentioned this code words fine. But, as I am importing data, the tags may contain different information and I would like to have the public declaration in a way that I can import any file to the API and be able to edit and export it.
Thank you
If you use JSON.net you can parse out json into a JObject then iterate over it's properties like so, however each property value can be any type of object so you'd need to write something to either recursively listed out nested objects or check if values are strings and only take them if they are etc.
var obj = JObject.Parse("{ \"test\": \"test\", \"test1\": \"test1\", \"test2\": \"test2\" }");
foreach (var prop in obj.Properties())
{
Console.WriteLine(prop.Name);
}
You can use JsonConvert like this:
Person person = JsonConvert.DeserializeObject<Person>(jsonString);
Read the docs for JsonConvert for more info.
I need to read a CSV file with FileHelpers based on type, automatically generated by my MVC model. The model looks like this:
public partial class Merchant
{
public long Id { get; set; }
public string Name { get; set; }
public Nullable<int> Category { get; set; }
public virtual MerchantCategory MerchantCategory { get; set; }
}
The last field is obviously generated by a foreign key in database, referring to table MerchantCategories.
Then I attempt to create an instance of FileHelperEngine with this type:
var engine = new FileHelperEngine<Merchant>();
And get the following exception:
The field: 'k__BackingField' has the type: MerchantCategory that is not a system type, so this field need a CustomConverter ( Please Check the docs for more Info).
Actually I don't need this field at all for my import, so I tried to ignore it in derived class:
[DelimitedRecord(",")]
public class MerchantForImport : Merchant {
[FieldHidden]
new public MerchantCategory MerchantCategory;
}
var engine = new FileHelperEngine<MerchantForImport>();
And still the same error. I don't need this field at all, I don't want to implement any FieldConverter for it, I never asked for this k__BackingField and it's nowhere to be found in my code!
I can't call FileHelperEngine.Options.RemoveField() because the exception is thrown by the constructor.
Where does that come from? How do I get rid of it?
From a design perspective, I think you are going about it the wrong way. You are trying to use the Merchant class for two incompatible uses. Instead you should have two separate classes.
FileHelpers is a library for describing csv files so that you can import them easily. You should have a MerchantFileSpec for describing your file. It's really not a proper C# class - it may have: dummy fields to represent unused columns; lots of attributes [FieldNullValue], [FieldQuoted], [FieldConverter]; etc. It works best with public fields (a FileHelpers limitation which is not C# best practice), etc. It is a convenience syntax for describing the import file. It should not include any business logic or special constructors, or backing fields. Keep it as simple as possible.
Then you can have your MVC-generated Merchant class which is separate. Its purpose is to describe the merchant as required by the MVC framework, with foreign keys, ids, whatever.
Then you use a FileHelperEngine<MerchantFileSpec> to read the records into an array and map it to an enumerable of Merchant (via Linq or a library like AutoMapper).
Something like:
/// Your MVC-generated class. Add methods, getters, setters, whatever.
/// FileHelpers doesn't use this class.
class Merchant
{
public long Id { get; set; }
public string Name { get; set; }
public Nullable<int> Category { get; set; }
public virtual MerchantCategory MerchantCategory { get; set; }
}
/// This is the class FileHelpers will use
/// This class describes the CSV file only. Stick to whatever
/// syntax conventions are required by FileHelpers.
[DelimitedRecord(";")]
class ProductMerchantFileSpec
{
[FieldQuoted(QuoteMode.OptionalForRead)]
public long Id;
[FieldQuoted(QuoteMode.OptionalForRead)]
public string Name;
[FieldQuoted(QuoteMode.OptionalForRead)]
// Handle non-US formats such as , decimal points
// convert from inches to centimetres?
// you get the idea...
[FieldConverter(MyCustomizedCategoryConverter)] // you get the idea
public int Category;
}
class Program
{
static void Main(string[] args)
{
var engine = new FileHelperEngine<ProductMerchantFileSpec>();
var productMerchantRecords = engine.ReadFile(filePath);
var productMerchants = productMerchantRecords
.Select(x => new Merchant() { Id = x.Id, Name = x.Name, Category = x.Category });
}
}
I received this error specifically because my object (i.e. Merchant) was missing a column that existed in the source file. I was able to work around the issue prior to realizing the missing column by adding a new property to my object class public string[] MyProperty { get; set; }. This work-around help me realize a column was missing.
i.e..
public partial class Merchant
{
public long id { get; set; }
..
..
..
public string[] MyProperty { get; set; }
}
I have high and low but still cannot find the solution to my problem so I gonna post it here.
this is my ViewModel
public class CustomerDetail
{
FirstName {get;set;}
LastName {get;set;}
ID {get;set;}
Email {get;set;}
}
This is how I pass my model to another method in another folder (project), I am actually passing the model to a reportViewer to export as pdf
fileExporter.Initialize(CustomerDetail).Export();
FileExportBase
public abstract class FileExportBase
{
protected string someotherobject {get;set;}
protected string someotherobject1 {get;set;}
protected CustomerDetail customerProfile { get; set; }
public abstract FileExportBase Initialize(params object[] args);
public abstract OperationResult Export();
public class CustomerDetail
{
FirstName {get;set;}
LastName {get;set;}
ID {get;set;}
Email {get;set;}
}
}
This is how I retrieve my data
public override FileExportBase Initialize(params object[] args)
{
someotherobject = args[0].ToString();
someotherobject1 = args[1].ToString();
//This is where I am facing the problem
customerProfile = (CustomerDetail)args[2];
return this;
}
So What should I do so I have to cast the object back to the model type that I initially declared it before passing it as object?
In your circumstance, you would more than likely want to do:
var customer = new CustomerDetail();
customer = args[2];
Now this code has an assortment of problems, for instance you're relying on an index, which may change, thus breaking your code. You may want to potentially refactor, in a manner that accepts your CustomerDetail directly.
public class Initialize<T>(T model, params string[] parameters) where T : class, new()
{
// Do Something
}
With the use of generic's, you can now simply do:
var customer = new T();
customer = model;
This alleviates the risk with the index, while being flexible. Then with a small bit of reflection you can easily ascertain the Properties and Field Index's within your T.
Not sure if this helps you, but based on what I see and understand of your problem this may be valid food for thought. If you add some more clarification, I may be able to better assist.
This is a very complicated question concerning how to serialize data via a web service call, when the data is not-strongly typed. I'll try to lay it out as best possible.
Sample Storage Object:
[Serializable]
public class StorageObject {
public string Name { get; set; }
public string Birthday { get; set; }
public List<NameValuePairs> OtherInfo { get; set; }
}
[Serializable]
public class NameValuePairs {
public string Name { get; set; }
public string Value { get; set; }
}
Sample Use:
[WebMethod]
public List<StorageObject> GetStorageObjects() {
List<StorageObject> o = new List<StorageObject>() {
new StorageObject() {
Name = "Matthew",
Birthday = "Jan 1st, 2008",
OtherInfo = new List<NameValuePairs>() {
new NameValuePairs() { Name = "Hobbies", Value = "Programming" },
new NameValuePairs() { Name = "Website", Value = "Stackoverflow.com" }
}
},
new StorageObject() {
Name = "Joe",
Birthday = "Jan 10th, 2008",
OtherInfo = new List<NameValuePairs>() {
new NameValuePairs() { Name = "Hobbies", Value = "Programming" },
new NameValuePairs() { Name = "Website", Value = "Stackoverflow.com" }
}
}
};
return o;
}
Return Value from Web Service:
<StorageObject>
<Name>Matthew</Name>
<Birthday>Jan 1st, 2008</Birthday>
<OtherInfo>
<NameValuePairs>
<Name>Hobbies</Name>
<Value>Programming</Value>
</NameValuePairs>
<NameValuePairs>
<Name>Website</Name>
<Value>Stackoverflow.com</Value>
</NameValuePairs>
</OtherInfo>
</StorageObject>
What I want:
<OtherInfo>
<Hobbies>Programming</Hobbies>
<Website>Stackoverflow.com</Website>
</OtherInfo>
The Reason & Other Stuff:
First, I'm sorry for the length of the post, but I wanted to give reproducible code as well.
I want it in this format, because I'm consuming the web services from PHP. I want to easily go:
// THIS IS IMPORANT
In PHP => "$Result["StorageObject"]["OtherInfo"]["Hobbies"]".
If it's in the other format, then there would be no way for me to accomplish that, at all. Additionally, in C# if I am consuming the service, I would also like to be able to do the following:
// THIS IS IMPORANT
In C# => var m = ServiceResult[0].OtherInfo["Hobbies"];
Unfortunately, I'm not sure how to accomplish this. I was able to get it this way, by building a custom Dictionary that implemented IXmlSerializer (see StackOverflow: IXmlSerializer Dictionary), however, it blew the WSDL schema out of the water. It's also much too complicated, and produced horrible results in my WinFormsTester application!
Is there any way to accomplish this ? What type of objects do I need to create ? Is there any way to do this /other than by making a strongly typed collection/ ? Obviously, if I make it strongly typed like this:
public class OtherInfo {
public string Hobbies { get; set; }
public string FavoriteWebsite { get; set; }
}
Then it would work perfectly, I would have no WSDL issues, I would be able to easily access it from PHP, and C# (.OtherInfo.Hobbies).
However, I would completely lose the point of NVP's, in that I would have to know in advance what the list is, and it would be unchangeable.. say, from a Database.
Thanks everyone!! I hope we're able to come up with some sort of solution to this. Here's are the requirements again:
WSDL schema should not break
Name value pairs (NVP's) should be serialized into attribute format
Should be easy to access NVP's in PHP by name ["Hobbies"]
Should be easy to access in C# (and be compatible with it's Proxy generator)
Be easily serializable
Not require me to strongly type the data
Now, I am /completely/ open to input on a better/different way to do this. I'm storing some relatively "static" information (like Name), and a bunch of pieces of data. If there's a better way, I'd love to hear it.
This is like dynamic properties for a object.
C# is not quite a dynamic language unlike javascript or maybe PHP can parse the object properties on the fly. The following two methods are what I can think of. The second one might fit into your requirements.
The KISS Way
The Keep It Simple Stupid way
public class StorageObject {
public string Name { get; set; }
public string Birthday { get; set; }
public List<string> OtherInfo { get; set; }
}
You can have name value pairs which is separated by '|'
OtherInfo = {"Hobbies|Programming", "Website|Stackoverflow.com"}
Serialized forms
<StorageObject>
<Name>Matthew</Name>
<Birthday>Jan 1st, 2008</Birthday>
<OtherInfo>
<string>Hobbies|Programming</string>
<string>Website|Stackoverflow.com</string>
</OtherInfo>
</StorageObject>
The Dynamic Way in C#
Make the name value pair part become an XML element so that you can build it dynamically.
public class StorageObject {
public string Name { get; set; }
public string Birthday { get; set; }
public XElement OtherInfo { get; set; } // XmlElement for dot net 2
}
You can easily build up OtherInfo object as element centric
e.g.
XElement OtherInfo = new XElement("OtherInfo");
OtherInfo.Add( ..Hobbies xelement & text value..);
OtherInfo.Add( ..WebSite xelement & text value..);
The serialized form will be
<OtherInfo>
<Hobbies>Programming</Hobbies>
<Website>Stackoverflow.com</Website>
</OtherInfo>
or build it as attribute centric
XElement OtherInfo = new XElement("OtherInfo");
OtherInfo.Add( ..nvp xattribute Hobbies & value..);
OtherInfo.Add( ..nvp xattribute WebSite & value..);
<OtherInfo>
<nvp n="Hobbies" v="Programming" />
<nvp n="Website" v="Stackoverflow.com" />
</OtherInfo>
For any dynamic language, it can access to the properties directly.
For the rest, they can access the value by read the XML. Reading XML is well supported by most of framework.
This is what I've settled on.
Class Structure:
public class StorageObject {
public string Name { get; set; }
public string Birthday { get; set; }
[XmlAnyElement("Info")] // this prevents double-nodes in the XML
public XElement OtherInfo { get; set; }
}
Usage:
StorageObject o = new StorageObject();
o.OtherInfo.Add(new XElement("Hobbies","Programming");
o.OtherInfo.Add(new XElement("Website","Stackoverflow.com");
Output:
<Info>
<Hobbies>Programming</Hobbies>
<Website>Stackoverflow.com</Website>
</Info>
I would like to thank everyone for their assistance, I really appreciate the help and ideas.
As a completely different take on this, why not think about doing it completely differently. Have one web service method to return the serialized storage object, minus the OtherInfo and another method to return the list of properties (keys) for OtherInfo, and a third to return the list of values for any key. Granted, it will take more round trips to the web service if you want all of the data, but the solution will be much simpler and more flexible.
[Serializable]
public class StorageObject {
public string Name { get; set; }
public string Birthday { get; set; }
[Nonserializable]
public Dictionary<string,List<string>> OtherInfo { get; set; }
}
[WebMethod]
public List<StorageObject> GetStorageObjects() {
// returns list of storage objects from persistent storage or cache
}
[WebMethod]
public List<string> GetStorageObjectAttributes( string name )
{
// find storage object, sObj
return sObj.Keys.ToList();
}
[WebMethod]
public List<string> GetStorageObjectAtributeValues( sting name, string attribute )
{
// find storage object, sObj
return sObj[attribute];
}
Have a look into the System.Xml.Serialization.XmlSerializerAssemblyAttribute attribute. This lets you specify a custom class-level serializer. You'll be able to spit out whatever XML you like.
A quick way to get up to speed on these is to use sgen.exe to generate one and have a peek at it with Reflector.
-Oisin
I'm not sure this would solve your problem (it would in C#, but maybe not in PHP), but try using Dictionary<string,List<string>> OtherInfo instead of List<NameValuePairs>. Then "Hobbies" and "Websites" would be your keys and the values would be the list of hobbies or web sites. I'm not sure how it would serialize, though.
You would be able to reference the lists of hobbies as:
List<string> hobbies = storageObject.OtherInfo["Hobbies"];
[EDIT] See here for a generic XML serializable dictionary. This derived class is the one you would need to use instead of generic Dictionary.