I have an ASP.NET core 2.1 MVC project and I am retrieving data from a Serlilog MSSqlServr Sink that stores values in a Properties field as an XML data type. I want to deserialize that data into a viewmodel so I can present the individual elements as data in a view.
Here is an example of the XML from the Properties field in the database Logs table.
<properties>
<property key="EventId">
<structure type="">
<property key="Id">404</property>
</structure>
</property>
<property key="ActionId">0592d9e8-f4fd-459f-96b3-2b787d01a754</property>
<property key="ActionName">API.Controllers.CompletionsController.GetCompletion (PS.API)</property>
<property key="RequestId">0HLJ2IL5A9:00000001</property>
<property key="RequestPath">/api/completions/0</property>
<property key="CorrelationId" />
<property key="ConnectionId">0HLJ2IL59</property>
<property key="MachineName">RD0003FF1</property>
<property key="ThreadId">117</property>
</properties>
I set up a class for the decoding as follows;
using System.Xml.Serialization;
namespace PS.Models.ApiLogs
{
[XmlRoot("properties")]
public class LogProperties
{
[XmlElement("SourceContext")]
public string SourceContext { get; set; }
[XmlElement("ActionId")]
public string ActionId { get; set; }
[XmlElement("ActionName")]
public string ActionName { get; set; }
[XmlElement("RequestId")]
public string RequestId { get; set; }
[XmlElement("RequestPath")]
public string RequestPath { get; set; }
[XmlElement("CorrelationId")]
public string CorrelationId { get; set; }
[XmlElement("ConnectionId")]
public string ConnectionId { get; set; }
[XmlElement("MachineName")]
public string MachineName { get; set; }
[XmlElement("ThreadId")]
public string ThreadId { get; set; }
}
}
And in my controller, I have the following code;
var serializer = new XmlSerializer(typeof(LogProperties));
LogProperties logProperties;
using (TextReader reader = new StringReader(log.Properties))
{
logProperties = (LogProperties)serializer.Deserialize(reader);
}
But the logProperties variable does not contain anything, so I assume I have the XML attributes incorrect in the LogProperties class.
I have spent quite a bit of time searching for a solution and I reviewed all of the related posts while entering this question but I am not able to find an example where the XML is using "property key=" or how to deal with a "key=" attribute property (if that is the correct term)
Any ideas?
[UPDATE 2/21/19]
I ended up using @jdweng suggestion as it was the least complex and gave me exactly what I wanted.
I created 2 classes (as I like to keep my class files separated as a personal pref.). The classes are below;
using System.Collections.Generic;
using System.Xml.Serialization;
namespace PS.Models.ApiLogs
{
[XmlRoot("properties")]
public class LogProperties
{
[XmlElement("property")]
public List<LogProperty> Property { get; set; }
}
}
and
using System.Xml.Serialization;
namespace PS.Models.ApiLogs
{
[XmlRoot("property")]
public class LogProperty
{
[XmlAttribute("key")]
public string Key { get; set; }
[XmlText]
public string Value { get; set; }
}
}
Then in my Controller, I have the following for the Detail method;
var response = await _client.GetLogAsync(id, $"api/logs", token);
if (response == null)
{
return NotFound($"Unable to find a record for Log ID [{id}].");
}
var log = _mapper.Map<DetailLogViewModel>(response.Record);
var serializer = new XmlSerializer(typeof(LogProperties));
LogProperties logProperties;
using (TextReader reader = new StringReader(log.Properties))
{
logProperties = (LogProperties)serializer.Deserialize(reader);
}
var logWithProperties = new DetailLogWithPropertiesViewModel
{
Id = log.Id,
Message = log.Message,
TimeStamp = log.TimeStamp,
Exception = log.Exception,
XmlProperties = logProperties
};
return View(logWithProperties);
My DetailLogWithPropertiesViewModel is below;
public class DetailLogWithPropertiesViewModel
{
public int Id { get; set; }
[Display(Name = "Message")]
public string Message { get; set; }
[Display(Name = "Level")]
public string Level { get; set; }
[Display(Name = "Time Stamp")]
public DateTimeOffset TimeStamp { get; set; }
[Display(Name = "Exception")]
public string Exception { get; set; }
[Display(Name = "Properties")]
public string Properties { get; set; }
public LogProperties XmlProperties { get; set; }
}
And the relevant portion of my Detail.cshtml is below;
<div class="card-body ml3 mr3">
@foreach (var logProperty in Model.XmlProperties.Property)
{
<div class="row">
<div class="col-4 bg-light border border-primary">
<span class="font-weight-bold">@logProperty.Key</span>
</div>
<div class="col-8 bg-secondary border border-left-0 border-primary">
<span>@logProperty.Value</span>
</div>
</div>
}
</div>
The XML that is generated by Serilog and stored in the MS SQL Database has a variable number of properties, which I now understand are represented as key/value pairs. So this method allows me to ensure that all of the provided properties are displayed in the log viewer of the website.
Try following :
[XmlRoot("properties")]
public class LogProperties
{
[XmlElement("property")]
public List<LogProperty> property { get; set; }
}
[XmlRoot("property")]
public class LogProperty
{
[XmlAttribute("key")]
public string key { get; set; }
[XmlText]
public string value { get; set; }
}
1) Copy your XML to clipboard...
2) ...Open Visual Studio and create an empty cs file...
3) ...go to EDIT > Paste Special > XML to classes ( might require ASP.NET webdevelopment to be installed )...
3) ...will result in this code:
/// <remarks/>
[System.SerializableAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true)]
[System.Xml.Serialization.XmlRootAttribute(Namespace = "", IsNullable = false)]
public partial class properties
{
private propertiesProperty[] propertyField;
/// <remarks/>
[System.Xml.Serialization.XmlElementAttribute("property")]
public propertiesProperty[] property
{
get
{
return this.propertyField;
}
set
{
this.propertyField = value;
}
}
}
/// <remarks/>
[System.SerializableAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true)]
public partial class propertiesProperty
{
private propertiesPropertyStructure structureField;
private string[] textField;
private string keyField;
/// <remarks/>
public propertiesPropertyStructure structure
{
get
{
return this.structureField;
}
set
{
this.structureField = value;
}
}
/// <remarks/>
[System.Xml.Serialization.XmlTextAttribute()]
public string[] Text
{
get
{
return this.textField;
}
set
{
this.textField = value;
}
}
/// <remarks/>
[System.Xml.Serialization.XmlAttributeAttribute()]
public string key
{
get
{
return this.keyField;
}
set
{
this.keyField = value;
}
}
}
/// <remarks/>
[System.SerializableAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true)]
public partial class propertiesPropertyStructure
{
private propertiesPropertyStructureProperty propertyField;
private string typeField;
/// <remarks/>
public propertiesPropertyStructureProperty property
{
get
{
return this.propertyField;
}
set
{
this.propertyField = value;
}
}
/// <remarks/>
[System.Xml.Serialization.XmlAttributeAttribute()]
public string type
{
get
{
return this.typeField;
}
set
{
this.typeField = value;
}
}
}
/// <remarks/>
[System.SerializableAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true)]
public partial class propertiesPropertyStructureProperty
{
private string keyField;
private ushort valueField;
/// <remarks/>
[System.Xml.Serialization.XmlAttributeAttribute()]
public string key
{
get
{
return this.keyField;
}
set
{
this.keyField = value;
}
}
/// <remarks/>
[System.Xml.Serialization.XmlTextAttribute()]
public ushort Value
{
get
{
return this.valueField;
}
set
{
this.valueField = value;
}
}
}
4) Yes, much code o_O. Use an XmlDeserializer(typeof(properties))
If you make a sample instance of your LogProperties class and serialize it like this:
var serializer = new XmlSerializer(typeof(LogProperties));
LogProperties logProperties = new LogProperties()
{
SourceContext = "MySourceContext",
ActionId = "MyActionId",
ActionName = "MyActionName"
};
StringBuilder sb = new StringBuilder();
using (StringWriter writer = new StringWriter(sb))
{
serializer.Serialize(writer, logProperties);
}
Console.WriteLine(sb.ToString());
This is what you get:
<?xml version="1.0" encoding="utf-16"?>
<properties xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<SourceContext>MySourceContext</SourceContext>
<ActionId>MyActionId</ActionId>
<ActionName>MyActionName</ActionName>
</properties>
So the class you have doesn't fit the XML you have very well. It is always useful to try serialization in both directions.
Below is a way you can serialize the XML you have into the class you have (getting a clear separation of your object model and your persistence format). It uses the XDocument class from the System.Xml.Linq namespace.
// Must escape all quotes !
string xmlSample = @"
<properties>
<property key=""EventId"">
<structure type = """">
<property key=""Id"">404</property>
</structure>
</property>
<property key=""ActionId""> 0592d9e8 - f4fd - 459f - 96b3 - 2b787d01a754</property>
<property key=""ActionName""> API.Controllers.CompletionsController.GetCompletion(PS.API)</property>
<property key=""RequestId""> 0HLJ2IL5A9: 00000001</property>
<property key=""RequestPath"">/api/completions/0</property>
<property key=""CorrelationId"" />
<property key=""ConnectionId"">0HLJ2IL59</property>
<property key=""MachineName"">RD0003FF1</property>
<property key=""ThreadId"">117</property>
</properties>";
StringReader reader = new StringReader(xmlSample);
XDocument xdoc = XDocument.Parse(xmlSample);
LogProperties log = new LogProperties();
foreach (XElement prop in xdoc.Root.Elements())
{
switch (prop.Attribute("key").Value)
{
case "ActionId" : log.ActionId = prop.Value; break;
case "ActionName" : log.ActionName = prop.Value; break;
// and so on
}
}
Note that with this approach:
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.