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
Related
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.
I'm looking for a way to dynamically create nunit test cases based off config file. In my App.config, I have a list of filenames and its expected operation. I would like to be able to add a new entry and have a test case be created dynamically based off that. My code is as follows:
App.config
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<!--syntax is filename#operation where 0 = Valid File, 1 = Missing Column, 2 = Invalid File-->
<add key="FILENAME_0" value="C:\Users\me\Desktop\file_0.csv#0" />
<add key="FILENAME_1" value="C:\Users\me\Desktop\file_1.csv#1" />
<add key="FILENAME_2" value="C:\Users\me\Desktop\file_2.csv#2" />
</appSettings>
</configuration>
In my test fixture, I initialize a list of test case data, which is created by parsing the App.config file as follows:
[TestFixture]
public class MyTests
{
public enum Operation
{
ValidFile = 0,
MissingColumns = 1,
InvalidFile = 2
};
/// <summary>
/// Gets test case data with the file name and an enum of the expected operation.
/// </summary>
private static IEnumerable<TestCaseData> FilenameCases()
{
int i = 0;
bool moreFilesToTest = true;
var files = new Dictionary<string, Operation>();
while (moreFilesToTest)
{
string filenameAndOp = ConfigurationManager.AppSettings[$"FILENAME_{i}"];
if (filenameAndOp == null)
moreFilesToTest = false;
else
{
string filename = filenameAndOp.Split('#')[0];
int operation = Int32.Parse(filenameAndOp.Split('#')[1]);
Operation op = (Operation)operation;
files.Add(filename, op);
}
i++;
}
foreach (var pair in files)
yield return new TestCaseData(pair.Key, pair.Value);
}
[Test, TestCaseSource("FilenameCases")]
public void ShouldLoadFiles(string FILENAME, Operation expected)
{
// ... run test cases
}
}
This works, however, would need to rebuild the project each time in order for any changes in the App.config to be recognized. The code also only works if the TestCaseData is static. What is the correct way to have new test cases be created dynamically based off the configuration file.
After researching this post, I have found out that the issue is not with the code, but rather the the configuration file. As I was running the test via the command line, the test .dll was not referencing my App.config, but another file.
When I compiled my project, I noticed it produced a config file based off the name of the project. So a test project titled MyTestProject would produce a file called MyTestProject.dll.config. Therefore, when I made changes to that config file, my test cases reflected the changes, without recompiling the project.
Credit to both Charlie and Amittai Shapira, as NUnit doesn't support dynamic test cases and TestCaseSource needs to be static. However, the code I tried was the best work around, as the end result did produced dynamic test cases.
TestCaseSource methods need to be static. There are a few ways around this if you need it but I see no problem with using static in your case. Just because a method is static, that doesn't mean it has to always return the same values.
There is no reason for you to recompile your code when you change the config file. However, you have to reload the test assembly in order for the new values in the config to be read. In some environments, like under VS, it may be easier to just recompile in order to force that to happen.
Essentially, I'd say you have already found the best way to do this, although my personal tendency would be to use a flat file rather than the config file.
I'm trying to unit test a custom ConfigurationSection I've written, and I'd like to load some arbitrary configuration XML into a System.Configuration.Configuration for each test (rather than put the test configuration xml in the Tests.dll.config file. That is, I'd like to do something like this:
Configuration testConfig = new Configuration("<?xml version=\"1.0\"?><configuration>...</configuration>");
MyCustomConfigSection section = testConfig.GetSection("mycustomconfigsection");
Assert.That(section != null);
However, it looks like ConfigurationManager will only give you Configuration instances that are associated with an EXE file or a machine config. Is there a way to load arbitrary XML into a Configuration instance?
There is actually a way I've discovered....
You need to define a new class inheriting from your original configuration section as follows:
public class MyXmlCustomConfigSection : MyCustomConfigSection
{
public MyXmlCustomConfigSection (string configXml)
{
XmlTextReader reader = new XmlTextReader(new StringReader(configXml));
DeserializeSection(reader);
}
}
You can then instantiate your ConfigurationSection object as follows:
string configXml = "<?xml version=\"1.0\"?><configuration>...</configuration>";
MyCustomConfigSection config = new MyXmlCustomConfigSection(configXml);
Hope it helps someone :-)
I think what you're looking for is ConfigurationManager.OpenMappedExeConfiguration
It allows you to open a configuration file that you specify with a file path (wrapped inside a ExeConfigurationFileMap)
If what the other poster said is true, and you don't wish to create a whole new XML file for testing, then I'd recommend you put your Configuration edits in the Test method itself, then run your tests against the freshly changed configuration data.
Looking at the members of the class, I'd say the answer is probably no*. I'm not sure why you'd want to do this anyway, rather than create your own XML configuration file.
*That's no, excluding messy reflection hacks
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.
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.