Serilog - ForContext treat dictionary value as complex object - c#

I'm trying to log model passed to controller by doing something like this:
_log
.ForContext("Id", "XXXXXX-XXXX-XXXX-XXXXXXXXX")
.ForContext("Email", email)
.ForContext("UserId", userId)
.ForContext("Parameters", parameters)
.ForContext("Errors", errors.ToArray())
.ForContext("ActionArguments", actionArguments)
.Information(message);
where actionArguments is of type IDictionary<string, object> actionArguments. This is interpreted like
{
someProperty: "Some.Namespace.Dtos.Something.MyTypeDto"
}
and I really wish someProperty to be expanded to what complex type represents. Is it possible? How to do it?

By default, Serilog will serialize objects with types it does not understand (including your own custom types) by just calling ToString() to get a simple representation. The default implementation of ToString() for an object just returns the full name of the type, which is why the type name of your DTO is showing up in the log context.
What you're after is called 'destructuring' in Serilog. In the documentation, this is defined as:
Destructuring is the process of taking a complex .NET object and converting it into a structure, which may later be represented as say, a JSON object or XML blob
There's an optional argument destructureObjects that you can provide to the ILogger.ForContext method that tells Serilog to try and pull out additional information from the object:
.ForContext("ActionArguments", actionArguments, destructureObjects: true)
By default it will destructure recursively using reflection, effectively walking down all of the object's properties and any properties of those properties, etc. until it finds a type that it knows how to format.
Just be careful that you're not logging any sensitive information by using this approach; Serilog will do its best to include every value it finds.

Related

C#: Pass reflection type into class constructor

I've got a generic method that takes an arbitrary JObject (from JSON.net) and converts it into the generically typed object.
So, let's simplify my conversion method to look like the following:
private async Task<T> ConvertToObject(JObject obj) {
//Lots of other stuff yielding in the creation of newObj
var result = newObj.ToObject<T>();
return result;
}
Now this method works fine as-is, but I want to modify the method so I can properly do the same for complex properties within the generic object that aren't available in my JObject (e.g. I have to look them up separately, do this same conversion and apply to this object result).
My approach so far is to loop through all the properties, identify the complex types, perform the lookup to retrieve their values from my data store, then execute the above against them (potentially recursively if they too have any complex objects), then write that value back out to this complex property, repeat for any others and then as before, return that result.
I'm retrieving the properties via Reflection:
var properties = result.GetType().GetProperties();
foreach (var property in properties) {
if (IsSimple(property.PropertyType)
continue;
//Do external lookup
var convertedValue = new ConversionTool<>().Lookup(query);
}
Now, that last line is where I'm having my problem. I'm expected to pass a class name into this, not just a type, but I only know the type at runtime per the reflection methods above. I found a post at http://www.paulbatum.com/2008/08/less-fragile-way-to-invoke-generic.html detailing how to make this work if I were simply passing the generic type into a method and he explains the issue with using Activator.CreateInstance in the comments, but it seems like I know the type I'd want to put there - I just don't know it until it runs (and I retrieve it via reflection).
This seems like an issue that ORMs would run into when it comes to populating complex properties of their entities, but I'm having a difficult time finding how they're doing this.
Given that the caller of this method is aware at runtime what the intended type is, how might I go about passing that type into the generic class constructor so I might call it recursively for each complex member of a given type?

Serialize an object without the object's information

I am in a situation I need to serialize only the values of the members of an object to a file.
for example, if the object contains 3 string members I would want the output of the serialization to be only this 3 strings without the serialization metadata the Binaryformatter adds, like the version, culture, and assembly name of the object.
One option is just to write each members of the object directly, but I want to avoid this because I have lots of classes that need to be serialize like this and I don't want to write a function that handles this differently for each class.
Requirements:
I want to be able to write to any type of file, I want the values of the members to be translated to bytes (into a buffer or directly into the file) and to be able to write those bytes at a specific position in the file.
Is there a way or an API of serializing only the value of the members of an object?
Matan,
Here is some code I put together for an entirely different purpose, but it takes an unknown object and serializes it into an XML file (updated to include better coding practices).
void objectToXMLFile(String fn, object o)
{
XmlTextWriter textWriter = new XmlTextWriter(fn, null);
System.Type type = o.GetType();
PropertyInfo[] piList = type.GetProperties();
textWriter.WriteStartDocument();
textWriter.WriteStartElement("attributeList");
foreach (PropertyInfo pi in piList)
{
textWriter.WriteStartElement("attribute");
textWriter.WriteStartElement("name");
textWriter.WriteString(pi.Name);
textWriter.WriteEndElement();
textWriter.WriteStartElement("value");
textWriter.WriteString(pi.GetValue(o).ToString());
textWriter.WriteEndElement();
textWriter.WriteStartElement("dataType");
textWriter.WriteString(pi.PropertyType.Name);
textWriter.WriteEndElement();
textWriter.WriteEndElement();
}
textWriter.WriteEndElement();
textWriter.WriteEndDocument();
textWriter.Close();
}
If you do end up using Reflection to only write the wanted properties, you may want to consider some performance implications.
Using Reflection is slow. Reflecting the type itself has a high price, and then dynamically invoking the PropertyInfo by calling PropertyInfo.GetValue is very slow.
An alternative is to build an Expression tree that calls the properties and writes their values, and then compile this expression into an Action. You can cache these Action instances in a dictionary keyed by the Type of the object you are serializing, and invoke the right one when you want to serialize the object.
That would be much much faster and will also not create so much load on the GC.
Another alternative you may consider is code generation at build time - you can generate a class to serialize your target type quickly and cheaply. In some scenarios this is a good choice.

Is there a way to use ParameterInfo and PropertyInfo Interchangeably?

To me they are very similar structures. I was hoping there was a way to cast or convert one to the other easily.
I'm using reflection to do some magic. I've chosen the path to use parametrized constructors to create some user selected objects which they fill in values for the parameters using a UI.
The problem is one of the objects takes in a structure as a param and I can't get at the structures properties as parameter infos just property infos.
But I don't want to just reproduce the parameter info code I have now for property infos. It be nice if I could pass in a property info as a parameter info. Everything is really similar except for some names of some properties; ParameterType as opposed to PropertyType and what not.
I may have to do my own conversion or write my own class that houses the properties that I need and just use that custom object instead. Cheers.
No, there is not.
Those two classes represent two very different concepts.
A property is an attribute on an Type. The PropertyInfo class will allow you to set or get the value and will tell you additional information about the Property.
A parameter is an attribute of a method signature (an accessor on a type can have a parameter as well). The ParameterInfo class represents this concept and can tell you the Type of the parameter, the position in the method signature, whether it is an out or ref parameter, etc. See: MSDN doc. A ParameterInfo is not directly associated to a Type.

Can I Deserialize a JSON string into an object if I only know the parameters of the objects' constructor?

This is like a follow-up question to this one.
Basically what I'm doing is exposing some fields on some UI to some user.
These fields are established based on the parameter list of a given objects constructor. The user has the options to choose which object the UI is displaying by, oh I don't know, let's say picking an object from a drop down list or something.
Once the fields are filled in and the user submits, I know the Object Type I need to create, I know the parameter names its constructor takes and the parameter types. I get a JSON string on the server (C# code behind).
The object potentially can have many more properties, public or private, than what the constructor exposes.
Can I use still use JavaScript or JSON deserialization to get my object if the JSON string doesn't contain data for all properties?
I'm not sure if I can specify default values somehow by property attributes or something...
Thanks.
Json.NET supports creating a type via a parametrized constructor provided there is only one constructor and the parameter names match the names of the properties on the JSON object.
The DataContract deserializer doesn't use the type's constructors when populating the object at all. It actually populates the object using the fields/setters/getters. Also, if you don't supply values for some of the properties they will initialize to their default value unless you wire up DataContract events such as OnDeserializing, OnDeserialize, etc. So for example if the type has a property called FirstName of type string (obviously) and the json doesn't have anything defined for a FirstName, when you deserialize you will get null as the default value since you did not supply this field.

RuntimeType:http://schemas.datacontract.org/2004/07/System' is not expected

Ok so I got DataContractSerializer working with my object graph. See my previous questions for more information.
Serialization / Derialization of a tree structure
The deserializer has no knowlege of any type that maps to this contract
However, one of my fields, _UserPropertyDefinitions, is defined as shown below.. It defines a list of custom properties that this user can add to objects in the data structure. The string is a unique key to identify the property, and Type is the type of the property which is always a primative type like Bool, Int, String etc etc..
Each object has a corresponding Dictionary(String key, Object value) collection to store the values it has set for any of the "User Properties"
[DataMember]
private Dictionary<string, Type> _UserPropertyDefinitions;
My object graph serializes fine when this property is an empty collection, yet once I add a custom property to this collection I get the following exception when trying to serialize with DataContractSerializer.
Type 'System.RuntimeType' with data
contract name
'RuntimeType:http://schemas.datacontract.org/2004/07/System'
is not expected. Add any types not
known statically to the list of known
types - for example, by using the
KnownTypeAttribute attribute or by
adding them to the list of known types
passed to DataContractSerializer.
If I remove the DataMember attribute for this field the I can serialize/deserialize with out getting an exception, but of course I loose the settings I've created in this field.
I'm pretty sure that Type isn't going to serialize very well - and arguably it doesn't belong in a data-contract anyway, since (being implementation specific) it defeats one of the main aims of a data-contract...
However, I expect the best approach would be to swap that for a Dictionary<string,string>, using the Type's AssemblyQualifiedName or FullName.

Categories

Resources