Reading event logs saved to XML format from .Net - c#

I'm trying to read an event log saved as an XML file from .Net / C#, the event log xml format looks (approximately) like this:
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<Events>
<Event xmlns='http://schemas.microsoft.com/win/2004/08/events/event'>...</Event>
<Event xmlns='http://schemas.microsoft.com/win/2004/08/events/event'>...</Event>
</Events>
Where the ... bit is a relatively complex series of types defined in a schema file event.xsd.
My plan was to use XSD.exe to generate a C# wrapper type for reading this event log XML from this schema file and then deserialise the XML using the XmlSerializer class, like so:
using (FileStream stream = File.OpenRead(filename))
{
XmlSerializer serialiser = new XmlSerializer(typeof(Events));
return (Events)serialiser.Deserialize(stream);
}
The trouble is that the schema file doesn't contain a definition for the Events element (because its not in the schema), and so the above doesn't compile as there is no Events type.
I've tried a couple of variations, including using the type EventType[] instead of Events (which resulted in the exception " was not expected."). I also attempting to craft my own C# container Events type:
public class Events
{
[XmlElement]
public EventType[] Items
{
get;
set;
}
}
However the above simply results in the Items array being null.
How can I read Events Logs saved to XML format from C#?

So I managed this by using the following class:
[Serializable]
[XmlType(AnonymousType = true)]
[XmlRoot(Namespace = "", IsNullable = false)]
public class Events
{
[XmlElement("Event", Namespace = "http://schemas.microsoft.com/win/2004/08/events/event")]
public EventType[] Items
{
get;
set;
}
}
I'm not entirely certain what it is that made this work where previously it failed (I suspect its the Namespace property), however I found this out by using xsd.exe to generate a schema from a saved event log file and and then again to generate C# classes from that file, like so:
xsd /c eventlog.xml
xsd /c eventlog.xsd eventlog_app1.xsd
(Because it writes two xsd files you need to name both of them on the command line). I then looked at the resulting C# and compared / experimented until it worked.

Related

cXML .net XMLReader error The parameter entity replacement text must nest properly within markup declarations

Is there something I need to configure in the XmlReaderSettings to encourage .net (4.8, 6, 7) to handle some cXML without throwing the following exception:
Unhandled exception. System.Xml.Schema.XmlSchemaException: The parameter entity replacement text must nest properly within markup declarations.
Sample cXML input
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE cXML SYSTEM "http://xml.cxml.org/schemas/cXML/1.2.041/cXML.dtd">
<cXML payloadID="donkeys#example.com" timestamp="2023-02-13T01:01:01Z">
<Header>
</Header>
<Request deploymentMode="production">
</Request>
</cXML>
Sample Application
using System.Xml;
using System.Xml.Linq;
namespace Donkeys
{
internal class Program
{
static void Main()
{
XmlReaderSettings settings = new()
{
XmlResolver = new XmlUrlResolver(),
DtdProcessing = DtdProcessing.Parse,
ValidationType = ValidationType.DTD,
};
FileStream fs = File.OpenRead("test.xml"); // sample cXML from question
XmlReader reader = XmlReader.Create(fs, settings);
XDocument.Load(reader); // this blows up
}
}
}
I'm looking to use the XmlUrlResolver to cache the DTDs but without ignoring the validation I get the error above but i'm not really sure why?
So far I've tried different validation flags but they don't validate at all unless I use ValidationType.DTD which goes pop.
The actual resolver seems to work fine; if I subclass it, it is returning the DTD (as a MemoryStream) as expected.
I can add an event handler to ignore the issue but this feels lamer than I'd like.
using System.Xml;
using System.Xml.Linq;
namespace Donkeys
{
internal class Program
{
static void Main()
{
XmlReaderSettings settings = new()
{
XmlResolver = new XmlUrlResolver(),
DtdProcessing = DtdProcessing.Parse,
ValidationType = ValidationType.DTD,
IgnoreComments = true
};
settings.ValidationEventHandler += Settings_ValidationEventHandler;
FileStream fs = File.OpenRead("test.xml");
XmlReader reader = XmlReader.Create(fs, settings);
XDocument dogs = XDocument.Load(reader);
}
private static void Settings_ValidationEventHandler(object? sender, System.Xml.Schema.ValidationEventArgs e)
{
// this seems fragile
if (e.Message.ToLower() == "The parameter entity replacement text must nest properly within markup declarations.".ToLower()) // and this would be a const
return;
throw e.Exception;
}
}
}
I've spent some time over the last few days looking into this and trying to get my head around what's going on here.
As far as I can tell, the error The parameter entity replacement text must nest properly within markup declarations is being reported incorrectly. My understanding of the spec is that this message means that you have mismatched < and > elements in the replacement text of a parameter entity in a DTD.
The following example is taken from this O'Reilly book sample page and demonstrates something that genuinely should reproduce this error:
<!ENTITY % finish_it ">">
<!ENTITY % bad "won't work" %finish_it;
Indeed the .NET DTD parser reports the same error for these two lines of DTD.
This doesn't mean you can't have < and > characters in parameter entity replacement text at all: the following two lines will declare an empty element with name Z, albeit in a somewhat round-about way:
<!ENTITY % Nested "<!ELEMENT Z EMPTY>">
%Nested;
The .NET DTD parser parses this successfully.
However, the .NET DTD parser appears to be objecting to this line in the cXML DTD, which defines the Object.ANY parameter entity:
<!ENTITY % Object.ANY '|xades:QualifyingProperties|cXMLSignedInfo|Extrinsic'>
There are of course no < and > characters in the replacement text, so the error is baffling.
This is by no means a new problem. I found this unanswered Stack Overflow question which basically reports the same problem. Also, this MSDN Forum post basically has the same problem, and it was asked in 2007. So is this unclear but intentional behaviour, or a bug that has been in .NET for 15+ years? I don't know.
For those who do want to look into things further, the following is about the minimum necessary to reproduce the problem. The necessary C# code to read the XML file can be taken from the question and adapted, I don't see the need to repeat it here:
example.dtd:
<?xml version="1.0" encoding="UTF-8"?>
<!ELEMENT A EMPTY>
<!ENTITY % Rest '|A' >
<!ELEMENT example (#PCDATA %Rest;)*>
example.xml:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE example SYSTEM "example.dtd">
<example/>
There are various ways to tweak this to get rid of the error. One way is to move the | character from the parameter entity into the ELEMENT example declaration. Replacing #PCDATA with another element (which you would also have to define) is another way.
But enough of the theory behind the problem. How can you actually move forwards with this?
I would take a local copy of the cXML DTD and adjust it to work around this error. You can download the DTD from the URL in your sample cXML input. The %Object.ANY; parameter entity is only used once in the DTD: I would replace this one occurrence with the replacement text, |xades:QualifyingProperties|cXMLSignedInfo|Extrinsic.
You then need to adjust the .NET XML parser to use your modified copy of the cXML DTD instead of fetching the the one from the given URL. You create a custom URL resolver for this, for example:
using System.Xml;
namespace Donkeys
{
internal class CXmlUrlResolver : XmlResolver
{
private static readonly Uri CXml1_2_041 = new Uri("http://xml.cxml.org/schemas/cXML/1.2.041/cXML.dtd");
private readonly XmlResolver urlResolver;
public CXmlUrlResolver()
{
this.urlResolver = new XmlUrlResolver();
}
public override object GetEntity(Uri absoluteUri, string role, Type ofObjectToReturn)
{
if (absoluteUri == CXml1_2_041)
{
// Return a Stream that reads from your custom version of the DTD,
// for example:
return File.OpenRead(#"SomeFilePathHere\cXML-1.2.401.dtd");
}
return this.urlResolver.GetEntity(absoluteUri, role, ofObjectToReturn);
}
}
}
This checks to see what URI is being requested, and if it matches the cXML URI, returns a stream that reads from your customised copy of the DTD. If some other URI is given, it passes the request to the nested XMLResolver, which then deals with it. You will of course need to use an instance of CXmlUrlResolver instead of XmlUrlResolver() when creating your XmlReaderSettings.
I don't know how many versions of cXML you will have to deal with, but if you are dealing with multiple versions, you might have to create a custom copy of the DTD for each version, and have your resolver return the correct local copy for each different URI.
A similar approach is given at this MSDN Forums post from 2008, which also deals with difficulties parsing cXML with .NET. This features a custom URL resolver created by subclassing XmlUrlResolver. Those who prefer composition over inheritance may prefer my custom URL resolver instead.

ASP.NET Core 3.1 using XmlSerializerFormatters()

I currently have an application based on .Net Core 2.2 which works. I need to move this project forward to .Net Core 3.1 but I cannot seem to get the XML Deserialized in the controller. In both apps I created a WCF connected service successfully. The WDSL now has more classes defined but are basically the same. I diffed the files and
Left handside is newly generated fill:
< [System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Tools.ServiceModel.Svcutil", "2.0.2")]
---
> [System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Tools.ServiceModel.Svcutil", "2.0.1-preview-30310-0943")]
This repeats with every class in Reference.cs. My problem is my Postman tests fail with the new controllers. By using Calculatus Eliminatus I have managed to track down the difference, The old parsing would accept:
<?xml version="1.0" encoding="UTF-8"?>
<ConnectedServiceRequestX xmlns="http://somename.com/api/01">
<Timestamp>2021-04-05T16:35:43</Timestamp>
<ApiKey>TopSecretKey</ApiKey>
<CustomerId>ABC</CustomerId>
</ConnectedServiceRequestX>
The new parser only works if the posted XML is like this:
<?xml version="1.0" encoding="UTF-8"?>
<ConnectedServiceRequestX>
<Timestamp xmlns="http://somename.com/api/01">2021-04-05T16:35:43</Timestamp>
<ApiKey xmlns="http://somename.com/api/01">TopSecretKey</ApiKey>
<CustomerId xmlns="http://somename.com/api/01">ABC</CustomerId>
</ConnectedServiceRequestX>
The new parser throws an exception when putting xmlns="http://somename.com/api/01" at class level XML Item. I need to support the older XML input as I have no ownership of the system accessing our service. This is a case where a big corporation is dictating the interface that they will use to access our data and we are a small outfit.
I am inclined to think there is some option I can supply to .XmlSerializerFormatters() such that the xmlns will default to what namespace is provided on the class level XML Item. Any help is appreciated.
The following should work :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Serialization;
namespace ConsoleApplication1
{
class Program
{
const string FILENAME = #"c:\temp\test.xml";
static void Main(string[] args)
{
XmlReader reader = XmlReader.Create(FILENAME);
XmlSerializer serializer = new XmlSerializer(typeof(ConnectedServiceRequestX));
ConnectedServiceRequestX request = (ConnectedServiceRequestX)serializer.Deserialize(reader);
}
}
[XmlRoot(Namespace = "http://somename.com/api/01")]
public class ConnectedServiceRequestX
{
public DateTime Timestamp { get; set; }
public string ApiKey { get; set; }
public string CustomerId { get; set; }
}
}
After studying the diffs between the Generated files (April 2019 and today) I noticed a one line difference preceding some of the classes. There are 60 plus classes in the C# file generated from the WDSL (I apologize for not noticing earlier). Anyhow, The line is as follows:
[XmlRootAttribute("ConnectedServiceRequestX", Namespace="http://somename.com/api/01", IsNullable = false)]
These lines we added at some point after the class generation but prior to their initial entry into source control. They require:
using System.Xml.Serialization;
to be added as well. What this does is allows the xmlns attribute to be placed in the outer tag (Class Item tag) and not have to be replicated in the inner tags as follows:
<?xml version="1.0" encoding="UTF-8"?>
<ConnectedServiceRequestX xmlns="http://somename.com/api/01">
<Timestamp>2021-04-05T16:35:43</Timestamp>
<ApiKey>TopSecretKey</ApiKey>
<CustomerId>ABC</CustomerId>
</ConnectedServiceRequestX>
Which is how my legacy tests with Postman were designed. I wrote (generated) this code when I was learning ASP.Net Web Services and I do not remember modifying the generated files to get the Connected Service / XML Post to work. So the short answer is when generating code (adding a Connected Service - WCF/WDSL) with Visual Studio there still may be some modifications to the Reference.cs file which allow more friendly XML to be posted to the endpoints.
I am now up and running (ASP.NET Core 3.1) using my legacy tests which give me confidence to go to production with the updated app. I hope this helps others.

There is an error in XML document when deserializing(C#)

I have the following xml:
XML
I have the following C# class
C# GlobalClass
I am trying to convert the xml content into C# custom object like that:
string xmlFilePath = Android.OS.Environment.ExternalStorageDirectory.ToString() + "/Settings4/settings.xml";
XmlSerializer deserializer = new XmlSerializer(typeof(GlobalClass));
TextReader textReader = new StreamReader(xmlFilePath);
GlobalClass globalVariables;
globalVariables = (GlobalClass)deserializer.Deserialize(textReader);
textReader.Close();
But I get
There is an error in XML document
on the line of code
globalVariables = (GlobalClass)deserializer.Deserialize(textReader);
I make GlobalClass inherit from Application because I want GlobalClass to be global that is to say I want to use its properties throughout all activities. What I'm doing wrong to recieve that error?
In your class put something like this [Serializable, XmlRoot("YourRoot")]
When you work with deserialization it might not found.
From MSDN
The name of the XML root element that is generated and recognized in
an XML-document instance. The default is the name of the serialized
class.

How to access property summary in code?

I have a class, with few properties, where some of the have an XML comment (summary).
What I want to do is display the summary info for the user in the application.
So I need to access summary text in code, to be able to do : Label1.Text = .......
how do I do that ?
public class MyObject
{
public int ID { get; set; }
/// <summary>
/// very very very very extensive information about the city
/// </summary>
public string City { get; set; }
public DateTime Date { get; set; }
public int Value { get; set; }
public int DiffToPrev { get; set; }
}
class Program
{
static void Main()
{
var a = new MyObject();
var t = a.GetType().GetProperty("City");
Console.WriteLine(t....................
Note that XML comments are not included in resulting exe/dll file, so you'll need to enable xml file generation and distribute them too. Consder using attributes to provide run-time available information on your properties/methods/classes/etc, as XML comments probably were not designed for what you are trying to do.
Anyway, XML comments have following format:
<?xml version="1.0"?>
<doc>
<assembly>
<name><!-- assembly name here --></name>
</assembly>
<members>
<!-- ... -->
<member name="M:Full.Type.Name.PropertyName">
<summary>
<!-- property summary here -->
</summary>
</member>
<!-- ... -->
</memebers>
</doc>
So if you still want it, you need to load your XML comments file and find XML node, describing your property (untested code, just to show approach):
var a = new MyObject();
var t = a.GetType().GetProperty("City");
string xmlMemberName = "M:" + a.FullName + t.Name;
var xmlDoc = new XmlDocument();
xmlDoc.Load("you_xml_comments_file.xml");
var membersNode = xmlDoc.DocumentElement["members"];
string summary = "";
if(membersNode != null)
{
foreach(XmlNode memberNode in membersNode.ChildNodes)
{
if(memberNode.Attributes["name"].Value == xmlMemberName)
{
summary = memberNode["summary"].InnerText;
break;
}
}
}
Console.WriteLine(summary);
Update: You can also include your XML comments file as an embedded resource so you'll never forget to distribute it, or even write a small tool, which transforms XML comments file into .resx XML resources file.
Incuding XML comments file as an embedded resource:
enable XML file generation in project properties
set XML output file path to project directory (instead of bin/Release or bin/Debug)
compile project
in project explorer enable "show all files" and inlude generated xml file
open file properties and set build action to "Embedded Resource"
Now your xml comments are included in assembly file as resource an can be loaded this way:
XmlDocument doc;
using(var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(
"your_default_namespace" + "." + "your_xml_file_name.xml"))
{
doc.Load(stream);
}
To generate .resx from comments XML file:
.resx format:
<root>
<!-- some header stuff which can be copy-pasted from any other valid .resx -->
<!-- ... -->
<data name="Your_Object_Full_Name_PropertyName" xml:space="preserve">
<value><!-- summary here --></value>
</data>
<!-- ... -->
</root>
Strings can be loaded from this .resx using ResourceManager class.
Although max has provided a very comprehensive answer, I just thought I would add a blog post that I compiled in relation to this question. I offer a solution that uses extension methods on MemberInfo to read the XML comments. My implementation uses XDocument and XPath queries to return the text. It works on methods, properties, types, events, fields and method parameters.
See here: http://www.brad-smith.info/blog/archives/220
You can check a flag in the project options to generate an XML-Documentation.
You can parse the generated file with the usual XML tools.
Depending on the use-case attributes might be a better fit. But in your case it seems to lead to useless redundancy.
But both are a bit problematic with localization. You typically want localizable strings not in the source, but in separate files.
this is not possible because comments do not get compiled
You can't access XML comment informations in your code. As said, these are comments, and are not processed by the compilator.
However they can be used to generate automatic documentation.
If you want to annotate your properties, use Attributes like this one included in the MVC framework

synchronizing XML nodes between class and file using C#

I'm trying to write an IXmlSerializable class that stays synced with an XML file. The XML file has the following format:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<logging>
<logLevel>Error</logLevel>
</logging>
...potentially other sections...
</configuration>
I have a DllConfig class for the whole XML file and a LoggingSection class for representing <logging> and its contents, i.e., <logLevel>. DllConfig has this property:
[XmlElement(ElementName = LOGGING_TAG_NAME,
DataType = "LoggingSection")]
public LoggingSection Logging { get; protected set; }
What I want is for the backing XML file to be updated (i.e., rewritten) when a property is set. I already have DllConfig do this when Logging is set. However, how should I go about doing this when Logging.LogLevel is set? Here's an example of what I mean:
var config = new DllConfig("path_to_backing_file.xml");
config.Logging.LogLevel = LogLevel.Information; // not using Logging setter, but a
// setter on LoggingSection, so how
// does path_to_backing_file.xml
// have its contents updated?
My current solution is to have a SyncedLoggingSection class that inherits from LoggingSection and also takes a DllConfig instance in the constructor. It declares a new LogLevel that, when set, updates the LogLevel in the base class and also uses the given DllConfig to write the entire DllConfig out to the backing XML file. Is this a good technique?
I don't think I can just serialize SyncedLoggingSection by itself to the backing XML file, because not all of the contents will be written, just the <logging> node. Then I'd end up with an XML file containing only the <logging> section with its updated <logLevel>, instead of the entire config file with <logLevel> updated. Hence, I need to pass an instance of DllConfig to SyncedLoggingSection.
It seems almost like I want an event handler, one in DllConfig that would notice when particular properties (i.e., LogLevel) in its properties (i.e., Logging) were set. Is such a thing possible?
Since you are implementing IXmlSerializable yourself, you can make DllConfig.Logging private. Then add a public method on DllConfig to set LogLevel instead of setting Logging.LogLevel directly. That will be the only way you can change the LogLevel. In the implementation of that public method, you can serialize the whole DllConfig to overwrite the xml.

Categories

Resources