I'm trying to incorporate into a project a custom NUnit (3) test runner.
I'm currently using this kind of code (pulled straight from https://docs.nunit.org/articles/nunit-engine/Getting-Started.html):
var engine = TestEngineActivator.CreateInstance();
var package = new TestPackage("my.test.assembly.dll");
var runner = engine.GetRunner(package);
XmlNode testResult = runner.Run(listener: null, TestFilter.Empty);
The code runs fine and I get a test result, in the form of a test-run xml node (example at the end of the question).
I expected to find some helper to deserialize this kind of XML in the (quite numerous) NUnit assemblies, but it seems that every single one is treating the data as an XML document, reading and writing single attributes/elements.
Is there some "official" way (e.g. a Nuget package, a framework type, etc) to deserialize this XML into standard model classes?
Example of the result I get:
<test-run id="0" name="TestLibrary.Example.dll"
fullname="...\tests\TestLibrary.Example.dll"
runstate="Runnable" testcasecount="5" result="Passed" total="5" passed="5" failed="0"
inconclusive="0" skipped="0" asserts="5" engine-version="3.11.1.0"
clr-version="4.0.30319.42000" start-time="2023-01-11 13:53:13Z" end-time="2023-01-11 13:53:13Z"
duration="0.133984">
<command-line><![CDATA["myexe.exe"]]></command-line>
<test-suite type="Assembly" id="0-1008" name="TestLibrary.Example.dll"
fullname="TestLibrary.Example.dll" runstate="Runnable" testcasecount="5" result="Passed"
start-time="2023-01-11 13:53:13Z" end-time="2023-01-11 13:53:13Z" duration="0.053977"
total="5" passed="5" failed="0" warnings="0" inconclusive="0" skipped="0" asserts="5">
<environment framework-version="3.11.0.0" clr-version="4.0.30319.42000"
os-version="Microsoft Windows NT 10.0.19045.0" platform="Win32NT"
cwd="..." machine-name="machine"
user="user" user-domain="domain" culture="it-IT" uiculture="en-US"
os-architecture="x64" />
<settings>
<setting name="ImageRuntimeVersion" value="4.0.30319" />
<setting name="ImageTargetFrameworkName" value=".NETFramework,Version=v4.7.2" />
<setting name="ImageRequiresX86" value="False" />
<setting name="ImageRequiresDefaultAppDomainAssemblyResolver" value="False" />
<setting name="RuntimeFramework" value="net-4.0" />
<setting name="NumberOfTestWorkers" value="16" />
</settings>
<properties>
<property name="_PID" value="8204" />
<property name="_APPDOMAIN" value="domain-4461bdb5-TestLibrary.Example.dll" />
</properties>
<test-suite type="TestSuite" id="0-1009" name="TestLibrary" fullname="TestLibrary"
runstate="Runnable" testcasecount="5" result="Passed"
start-time="2023-01-11 13:53:13Z" end-time="2023-01-11 13:53:13Z" duration="0.038980"
total="5" passed="5" failed="0" warnings="0" inconclusive="0" skipped="0" asserts="5">
<test-suite type="TestSuite" id="0-1010" name="Example"
fullname="TestLibrary.Example" runstate="Runnable" testcasecount="5"
result="Passed" start-time="2023-01-11 13:53:13Z"
end-time="2023-01-11 13:53:13Z" duration="0.038561" total="5" passed="5"
failed="0" warnings="0" inconclusive="0" skipped="0" asserts="5">
<test-suite type="TestFixture" id="0-1000" name="ExampleTestFixture"
fullname="TestLibrary.Example.ExampleTestFixture"
classname="TestLibrary.Example.ExampleTestFixture" runstate="Runnable"
testcasecount="5" result="Passed" start-time="2023-01-11 13:53:13Z"
end-time="2023-01-11 13:53:13Z" duration="0.035698" total="5" passed="5"
failed="0" warnings="0" inconclusive="0" skipped="0" asserts="5">
<test-suite type="ParameterizedMethod" id="0-1004" name="ParameterizedTest"
fullname="TestLibrary.Example.ExampleTestFixture.ParameterizedTest"
classname="TestLibrary.Example.ExampleTestFixture" runstate="Runnable"
testcasecount="2" result="Passed" start-time="2023-01-11 13:53:13Z"
end-time="2023-01-11 13:53:13Z" duration="0.028787" total="2"
passed="2" failed="0" warnings="0" inconclusive="0" skipped="0"
asserts="2">
<test-case id="0-1002" name="Example1"
fullname="TestLibrary.Example.ExampleTestFixture.Example1"
methodname="ParameterizedTest"
classname="TestLibrary.Example.ExampleTestFixture"
runstate="Runnable" seed="1295788249" result="Passed"
start-time="2023-01-11 13:53:13Z" end-time="2023-01-11 13:53:13Z"
duration="0.022636" asserts="1">
<output><![CDATA[Running ExampleTestFixture.SetupMethod
Running ExampleTestFixture.ParameterizedTest
]]></output>
</test-case>
<test-case id="0-1003" name="ParameterizedTest(2,3,5)"
fullname="TestLibrary.Example.ExampleTestFixture.ParameterizedTest(2,3,5)"
methodname="ParameterizedTest"
classname="TestLibrary.Example.ExampleTestFixture"
runstate="Runnable" seed="891989160" result="Passed"
start-time="2023-01-11 13:53:13Z" end-time="2023-01-11 13:53:13Z"
duration="0.000241" asserts="1">
<output><![CDATA[Running ExampleTestFixture.SetupMethod
Running ExampleTestFixture.ParameterizedTest
]]></output>
</test-case>
</test-suite>
<test-case id="0-1001" name="SimpleTest"
fullname="TestLibrary.Example.ExampleTestFixture.SimpleTest"
methodname="SimpleTest"
classname="TestLibrary.Example.ExampleTestFixture" runstate="Runnable"
seed="202878501" result="Passed" start-time="2023-01-11 13:53:13Z"
end-time="2023-01-11 13:53:13Z" duration="0.001015" asserts="1">
<output><![CDATA[Running ExampleTestFixture.SetupMethod
Running ExampleTestFixture.SimpleTest
]]></output>
</test-case>
<test-suite type="ParameterizedMethod" id="0-1007" name="TestWithSource"
fullname="TestLibrary.Example.ExampleTestFixture.TestWithSource"
classname="TestLibrary.Example.ExampleTestFixture" runstate="Runnable"
testcasecount="2" result="Passed" start-time="2023-01-11 13:53:13Z"
end-time="2023-01-11 13:53:13Z" duration="0.000261" total="2"
passed="2" failed="0" warnings="0" inconclusive="0" skipped="0"
asserts="2">
<test-case id="0-1005" name="Example1"
fullname="TestLibrary.Example.ExampleTestFixture.Example1"
methodname="TestWithSource"
classname="TestLibrary.Example.ExampleTestFixture"
runstate="Runnable" seed="1768044693" result="Passed"
start-time="2023-01-11 13:53:13Z" end-time="2023-01-11 13:53:13Z"
duration="0.000162" asserts="1">
<output><![CDATA[Running ExampleTestFixture.SetupMethod
Running ExampleTestFixture.TestWithSource
]]></output>
</test-case>
<test-case id="0-1006" name="TestWithSource(2,3,5)"
fullname="TestLibrary.Example.ExampleTestFixture.TestWithSource(2,3,5)"
methodname="TestWithSource"
classname="TestLibrary.Example.ExampleTestFixture"
runstate="Runnable" seed="858491452" result="Passed"
start-time="2023-01-11 13:53:13Z" end-time="2023-01-11 13:53:13Z"
duration="0.000051" asserts="1">
<output><![CDATA[Running ExampleTestFixture.SetupMethod
Running ExampleTestFixture.TestWithSource
]]></output>
</test-case>
</test-suite>
</test-suite>
</test-suite>
</test-suite>
</test-suite>
</test-run>
Short answer: No, there are no such classes availble.
NUnit itself never makes use of it's XML output. It's just that: output. In fact, it's considered the primary output from your test run and contains all the info available about each test.
The NUnit documentation contains information about the format of this output (see https://docs.nunit.org/articles/nunit/technical-notes/usage/Test-Result-XML-Format.html) but it may not be completely up to date.
Essentially, the NUnit team has drawn a boundary around the project, leaving the interpretation of the XML result up to the user. Many programs, both open and closed source, make use of the XML result to provide different kinds of output. Someone could create the kind of system you ask about, providing a set of model classes to represent the test run, but I'm not aware of anyone who has published such a system.
Note that you can, if you wish, integrate your own code to re-format or display the content of the XML file with the engine by writing an engine extension. The IResultWriter extension point exists for this purpose. See https://docs.nunit.org/articles/nunit-engine/extensions/creating-extensions/Result-Writers.html
Well, following the advice from Charlie Poole I created my own deserialization types. In case one day someone else should have the same necessity, I'm pasting here my work in progress:
public enum RunState
{
NotRunnable,
Runnable,
Explicit,
Skipped,
Ignored
}
public enum TestResult
{
Inconclusive,
Passed,
Warning,
Skipped,
Failed,
Error
}
[Serializable, XmlRoot("test-run", IsNullable = false)]
public class TestRun
{
[XmlAttribute("id")]
public int Id { get; set; }
[XmlAttribute("name")]
public string Name { get; set; }
[XmlAttribute("fullname")]
public string Fullname { get; set; }
[XmlAttribute("runstate")]
public RunState RunState { get; set; }
[XmlAttribute("testcasecount")]
public int TestCaseCount { get; set; }
[XmlAttribute("result")]
public TestResult Result { get; set; }
[XmlAttribute("total")]
public int Total { get; set; }
[XmlAttribute("passed")]
public int Passed { get; set; }
[XmlAttribute("failed")]
public int Failed { get; set; }
[XmlAttribute("inconclusive")]
public int Inconclusive { get; set; }
[XmlAttribute("skipped")]
public int Skipped { get; set; }
[XmlAttribute("asserts")]
public int Asserts { get; set; }
[XmlAttribute("engine-version")]
public string EngineVersion { get; set; }
[XmlAttribute("clr-version")]
public string ClrVersion { get; set; }
[XmlAttribute("start-time")]
public string StartTime { get; set; }
[XmlAttribute("end-time")]
public string EndTime { get; set; }
[XmlAttribute("duration")]
public decimal Duration { get; set; }
[XmlElement("command-line")]
public string CommandLine { get; set; }
[XmlElement("test-suite", typeof(TestSuite))]
public List<TestSuite> Tests { get; set; }
[XmlElement("filter")]
public XmlNode Filter { get; set; }
}
[Serializable]
public class TestBase
{
[XmlAttribute("id")]
public string Id { get; set; }
[XmlAttribute("name")]
public string Name { get; set; }
[XmlAttribute("fullname")]
public string FullName { get; set; }
[XmlAttribute("runstate")]
public RunState RunState { get; set; }
[XmlAttribute("type")]
public string Type { get; set; }
[XmlAttribute("result")]
public TestResult Result { get; set; }
[XmlAttribute("start-time")]
public string StartTime { get; set; }
[XmlAttribute("end-time")]
public string EndTime { get; set; }
[XmlAttribute("duration")]
public decimal Duration { get; set; }
[XmlAttribute("asserts")]
public int Asserts { get; set; }
[XmlArray(ElementName = "properties"), XmlArrayItem("property", typeof(NameValuePair), IsNullable = false)]
public List<NameValuePair> Properties { get; set; }
[XmlArray(ElementName = "assertions"), XmlArrayItem("assertion", typeof(TestAssertion), IsNullable = false)]
public List<TestAssertion> Assertions { get; set; }
[XmlElement("output")]
public string Output { get; set; }
[XmlElement("reason")]
public TestReason Reason { get; set; }
}
public class TestFailure
{
[XmlElement("message")]
public string Message { get; set; }
[XmlElement("stack-trace")]
public string StackTrace { get; set; }
}
public class TestAssertion
{
[XmlAttribute("result")]
public TestResult Result { get; set; }
[XmlElement("message")]
public string Message { get; set; }
[XmlElement("stack-trace")]
public string StackTrace { get; set; }
}
public class TestReason
{
[XmlElement("message")]
public List<string> Messages { get; set; }
}
[Serializable, XmlRoot("test-suite")]
public class TestSuite : TestBase
{
[XmlAttribute("testcasecount")]
public int TestCaseCount { get; set; }
[XmlAttribute("total")]
public int Total { get; set; }
[XmlAttribute("passed")]
public int Passed { get; set; }
[XmlAttribute("failed")]
public int Failed { get; set; }
[XmlAttribute("warnings")]
public int Warnings { get; set; }
[XmlAttribute("inconclusive")]
public int Inconclusive { get; set; }
[XmlAttribute("skipped")]
public int Skipped { get; set; }
[XmlElement("environment", typeof(TestEnvironment))]
public TestEnvironment Environment { get; set; }
[XmlArray(ElementName = "settings"), XmlArrayItem("setting", typeof(NameValuePair), IsNullable = false)]
public List<NameValuePair> Settings { get; set; }
[XmlElement("test-suite", typeof(TestSuite))]
[XmlElement("test-case", typeof(TestCase))]
public List<TestBase> Tests { get; set; }
}
[Serializable, XmlRoot("test-case")]
public class TestCase : TestBase
{
[XmlAttribute("methodname")]
public string MethodName { get; set; }
[XmlAttribute("classname")]
public string ClassName { get; set; }
[XmlAttribute("seed")]
public long Seed { get; set; }
[XmlAttribute("label")]
public string Label { get; set; }
[XmlAttribute("site")]
public string Site { get; set; } = "Test";
}
[Serializable]
public class TestEnvironment
{
[XmlAttribute("framework-version")]
public string FrameworkVersion { get; set; }
[XmlAttribute("clr-version")]
public string ClrVersion { get; set; }
[XmlAttribute("os-version")]
public string OsVersion { get; set; }
[XmlAttribute("platform")]
public string Platform { get; set; }
[XmlAttribute("cwd")]
public string Cwd { get; set; }
[XmlAttribute("machine-name")]
public string MachineName { get; set; }
[XmlAttribute("user")]
public string User { get; set; }
[XmlAttribute("user-domain")]
public string UserDomain { get; set; }
[XmlAttribute("culture")]
public string Culture { get; set; }
[XmlAttribute("uiculture")]
public string UiCulture { get; set; }
[XmlAttribute("os-architecture")]
public string OsArchitecture { get; set; }
}
[Serializable]
public class NameValuePair
{
[XmlAttribute("name")]
public string Name { get; set; }
[XmlAttribute("value")]
public string Value { get; set; }
}
Except (at least) for the Filter property of the TestRun, which is out of scope for my needs and has a pretty complex (i.e. nested) structure, it seems to work well enough. For sure there are a lot of possible improvements (proper date parsing, as an example), but it's very easy to use:
var serializer = new XmlSerializer(typeof(TestRun));
using (var stringReader = new StringReader(testResult.OuterXml))
{
var testrun = (TestRun) serializer.Deserialize(stringReader);
}
I have yet to unit test every single property, but is seems to be already a pretty good bootstrap for anyone else attempting this task.
Related
[CollectionDataContract(Namespace = "http://schemas.datacontract.org/2004/07/ServicesTools.WebServices")]
public class AcquisitionDetails : List<Acquisition>
{
}
[DataContract(Name = "Acquisition", Namespace = "http://schemas.datacontract.org/2004/07/ServicesTools.WebServices")]
public class Acquisition
{
[DataMember]
public string GeoCode { get; set; }
[DataMember]
public string OrderDate { get; set; }
[DataMember]
public string Supplier { get; set; }
[DataMember]
public string PartNo { get; set; }
[DataMember]
public int QtyExpected { get; set; }
[DataMember]
public uint PONumber { get; set; }
}
in a different question, we got this XML working:
<?xml version="1.0" encoding="utf-8"?>
<AcquisitionDetails>
<Acquisition>
<GeoCode>PHX</GeoCode>
<OrderDate>3/10/2020 12:00:00 AM</OrderDate>
<PartNo>20L6S52C11</PartNo>
<QtyExpected>71</QtyExpected>
<PONumber>1990001996</PONumber>
<Supplier>Lenovo (International) BV</Supplier>
</Acquisition>
</AcquisitionDetails>
Code in the Controller:
[Consumes("application/xml")]
[Produces("application/xml")]
[HttpPost]
public ActionResult Post(AcquisitionDetails ad)
{
return Ok(ad);
}
BUT, there's a but;
when I sent this in POSTMAN, the PONumber for example, is empty
<AcquisitionDetails xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07/ServicesTools.WebServices">
<Acquisition>
<GeoCode>PHX</GeoCode>
<OrderDate>3/10/2020 12:00:00 AM</OrderDate>
<PONumber>0</PONumber>
<PartNo>20L6S52C11</PartNo>
<QtyExpected>71</QtyExpected>
<Supplier>Lenovo (International) BV</Supplier>
</Acquisition>
</AcquisitionDetails>
This is the case, until I move it up a bit, but then
<Acquisition>
<GeoCode i:nil="true" />
<OrderDate i:nil="true" />
<PONumber>1990001996</PONumber>
<PartNo>20L6S52C11</PartNo>
<QtyExpected>71</QtyExpected>
<Supplier>Lenovo (International) BV</Supplier>
</Acquisition>
anyway, long story short.. I cannot change the sequence of the incoming XML, nor do I want to be forced to receive the XML in said sequence; how can I instruct the model to parse the XML as it comes?
========update=====
[DataContract(Name = "Acquisition", Namespace = "http://schemas.datacontract.org/2004/07/ServicesTools.WebServices")]
public class Acquisition
{
[DataMember(Name = "GeoCode")]
public string GeoCode { get; set; }
[DataMember(Name = "OrderDate")]
public string OrderDate { get; set; }
[DataMember(Name = "Supplier")]
public string Supplier { get; set; }
[DataMember(Name = "PartNo")]
public string PartNo { get; set; }
[DataMember(Name = "QtyExpected")]
public int QtyExpected { get; set; }
[DataMember(Name = "PONumber")]
public Int64 PONumber { get; set; }
}
but, the results are still:
Input:
<?xml version="1.0" encoding="utf-8"?>
<AcquisitionDetails xmlns="http://schemas.datacontract.org/2004/07/ServicesTools.WebServices">
<Acquisition>
<GeoCode>PHX</GeoCode>
<OrderDate>3/10/2020 12:00:00 AM</OrderDate>
<PONumber>1990001996</PONumber>
<PartNo>20L6S52C11</PartNo>
<QtyExpected>71</QtyExpected>
<Supplier>Lenovo (International) BV</Supplier>
</Acquisition>
<Acquisition>
<GeoCode>PHX</GeoCode>
<OrderDate>3/9/2020 12:00:00 AM</OrderDate>
<PartNo>AA549668</PartNo>
<QtyExpected>20</QtyExpected>
<PONumber>1990001879</PONumber>
<Supplier>SHI INTERNATIONAL CORP</Supplier>
</Acquisition>
</AcquisitionDetails>
Output:
<AcquisitionDetails xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07/ServicesTools.WebServices">
<Acquisition>
<GeoCode>PHX</GeoCode>
<OrderDate>3/10/2020 12:00:00 AM</OrderDate>
<PONumber>1990001996</PONumber>
<PartNo>20L6S52C11</PartNo>
<QtyExpected>71</QtyExpected>
<Supplier>Lenovo (International) BV</Supplier>
</Acquisition>
<Acquisition>
<GeoCode>PHX</GeoCode>
<OrderDate>3/9/2020 12:00:00 AM</OrderDate>
<PONumber>0</PONumber>
<PartNo>AA549668</PartNo>
<QtyExpected>20</QtyExpected>
<Supplier>SHI INTERNATIONAL CORP</Supplier>
</Acquisition>
</AcquisitionDetails>
Are you try order for it? I am not sure but may be helpfull for you
[DataMember(Name = "GeoCode",Order =1)]
Specify Name in your DataMember attribute for all parameters - then order of parameters should not matter. For example:
public class Acquisition
{
[DataMember(Name = "GeoCode"]
public string GeoCode { get; set; }
}
I have an XML file which has many children:
<Docs>
<Doc>
<DocType>x</DocType>
<DocNumber>xxx</DocNumber>
<DocNumberSeries>xx</DocNumberSeries>
<DocDate>xxxx-xx-xx</DocDate>
<DocCause>
<Id>xx</Id>
<Code/>
<Name>xx</Name>
</DocCause>
<Anag>
<Name>NameCompany</Name>
<Address>xx</Address>
<ZipCode>xx</ZipCode>
<City>xx</City>
<Province>xx</Province>
<CountryCode>xx</CountryCode>
<PhoneNumber>xx</PhoneNumber>
<CellularNumber/>
<FaxNumber>xx</FaxNumber>
<EmailAddress>xx</EmailAddress>
<VatNumber>xx</VatNumber>
<PersonalID>xx</PersonalID>
</Anag>
<DestinationAddress>
<Name>xxL</Name>
<Address>xx</Address>
<ZipCode>xx</ZipCode>
<City>xx</City>
<Province>xx</Province>
<CountryCode>xx</CountryCode>
<PhoneNumber>xx</PhoneNumber>
<FaxNumber>xx</FaxNumber>
</DestinationAddress>
<Payment>
<Code/>
<Name>xx</Name>
</Payment>
<DocRows>
<DocRow>
<RowType>1</RowType>
<Product>
<Code>LSML1710</Code>
</Product>
<Description></Description>
<Quantity></Quantity>
<Price></Price>
<Tax>
<Tax>
<Id>xx</Id>
<Code/>
<Name>xx</Name>
<PercentAmount>xx</PercentAmount>
<IndPercentAmount>x</IndPercentAmount>
<FixedAmount>x</FixedAmount>
</Tax>
</Tax>
</DocRow>
</DocRows>
...
</Doc>
For example, one of the thing I want to do is to get the data inside tag Doc and then Anag.
At the moment I have these classes:
public class Anag
{
public string RagioneSociale { get; set; }
public string Indirizzo { get; set; }
public string CAP { get; set; }
public string Citta { get; set; }
public string Provincia { get; set; }
public string CodiceNazione { get; set; }
public string Telefono { get; set; }
public string Cellulare { get; set; }
public string Email { get; set; }
}
public class Doc
{
public string DocNumber { get; set; }
public Anag Anags { get; set; }
}
List<Doc> results = contentFile.Descendants("Doc").Select(doc => new Doc // var results = contentFile.Descendants("Doc").SelectMany(doc => doc.Descendants(doc.Name.Namespace + "Anag").Select(anag => new
{
DocNumber = (string)doc.Element(doc.Name.Namespace + "DocNumber"),
Anags = doc.Descendants(doc.Name.Namespace + "Anag").Select(anag => new Anag
{
RagioneSociale = (string)anag.Element(anag.Name.Namespace + "Name"),
Indirizzo = (string)anag.Element(anag.Name.Namespace + "Address"),
CAP = (string)anag.Element(anag.Name.Namespace + "ZipCode"),
Provincia = (string)anag.Element(anag.Name.Namespace + "Province"),
Telefono = (string)anag.Element(anag.Name.Namespace + "PhoneNumber"),
Cellulare = (string)anag.Element(anag.Name.Namespace + "CellularNumber"),
Email = (string)anag.Element(anag.Name.Namespace + "EmailAddress"),
Citta = (string)anag.Element(anag.Name.Namespace + "City")
}),
}).ToList();
This last code gives an error:
Cannot implicitly convert type system.collections.generic.IEnumerable<nameProject.Classes.Anag> in <NameProject.Classes.Anag>.
The final result I want is a List with the data and children of this XML:
List:
+ Doc=>
{DocNumber}
+ Anag => { Address .. , Name.. .. ) (not a list ! )
+ DestinationAddress => { Address.... Name..)
so that I can convert to a file .csv only .
Since you already went through the trouble of creating the classes, why not let the deserializer do the job for you?
Here's how:
XmlSerializer serializer = new XmlSerializer(typeof(Root));
using (var reader = new StreamReader(xmlFilePath))
{
List<Doc> docs = ((Root)serializer.Deserialize(reader)).Docs;
}
Now, you just have to add some tags into your class properties so that they match the xml names.
[Serializable, XmlRoot("Docs")]
public class Root
{
[XmlElement("Doc")]
public List<Doc> Docs { get; set; }
}
public class Doc
{
public string DocNumber { get; set; }
[XmlElement("Anag")]
public Anag Anags { get; set; }
}
public class Anag
{
[XmlElement("Name")]
public string RagioneSociale { get; set; }
[XmlElement("Address")]
public string Indirizzo { get; set; }
[XmlElement("ZipCode")]
public string CAP { get; set; }
[XmlElement("City")]
public string Citta { get; set; }
[XmlElement("Province")]
public string Provincia { get; set; }
[XmlElement("CountryCode")]
public string CodiceNazione { get; set; }
[XmlElement("PhoneNumber")]
public string Telefono { get; set; }
[XmlElement("CellularNumber")]
public string Cellulare { get; set; }
[XmlElement("EmailAddress")]
public string Email { get; set; }
}
Just try using the feature "Paste Special" to generate the classes for parsing. You will 100% not get an exception.
Crtl + A on the whole xml, copy to clipboard
Then create a new class
in your project Click "Edit" -> "Paste Special" -> "Paste XML as
classes"
Change the parsing to use type of the "RootNode" in the
pasted class
Build and try it out.
Given this XML, I need to deserialize it to a class. I'm not sure how to distinguish between the two 'MealPeriod' elements of different types, but of the same name.
<SalesSummary>
<MealPeriod1>Lunch</MealPeriod1>
<MealPeriod1Sales>6447.58</MealPeriod1Sales>
<MealPeriod2>Dinner</MealPeriod2>
<MealPeriod2Sales>12074.04</MealPeriod2Sales>
<MealPeriod3>Late-Night</MealPeriod3>
<MealPeriod3Sales>5156.90</MealPeriod3Sales>
<MealPeriod4></MealPeriod4>
<MealPeriod4Sales>0.00</MealPeriod4Sales>
<MealPeriod1>
<Interval>
<Name>10:00am-11:00am</Name>
<Checks>1</Checks>
<Guests>1</Guests>
<AvgCheck>$31.50</AvgCheck>
<AvgGuest>$31.50</AvgGuest>
<Sales>$31.50</Sales>
</Interval>
<Interval>
<Name>11:00am-12:00pm</Name>
<Checks>8</Checks>
<Guests>29</Guests>
<AvgCheck>$85.22</AvgCheck>
<AvgGuest>$23.51</AvgGuest>
<Sales>$681.75</Sales>
</Interval>
<Interval>
<Name>12:00pm-01:00pm</Name>
<Checks>27</Checks>
<Guests>53</Guests>
<AvgCheck>$48.76</AvgCheck>
<AvgGuest>$24.84</AvgGuest>
<Sales>$1,316.58</Sales>
</Interval>
<Interval>
<Name>01:00pm-02:00pm</Name>
<Checks>17</Checks>
<Guests>35</Guests>
<AvgCheck>$58.76</AvgCheck>
<AvgGuest>$28.54</AvgGuest>
<Sales>$999.00</Sales>
</Interval>
<Interval>
<Name>02:00pm-03:00pm</Name>
<Checks>22</Checks>
<Guests>31</Guests>
<AvgCheck>$38.99</AvgCheck>
<AvgGuest>$27.67</AvgGuest>
<Sales>$857.75</Sales>
</Interval>
<Interval>
<Name>03:00pm-04:00pm</Name>
<Checks>21</Checks>
<Guests>44</Guests>
<AvgCheck>$33.76</AvgCheck>
<AvgGuest>$16.11</AvgGuest>
<Sales>$709.00</Sales>
</Interval>
<Interval>
<Name>04:00pm-05:00pm</Name>
<Checks>32</Checks>
<Guests>55</Guests>
<AvgCheck>$57.88</AvgCheck>
<AvgGuest>$33.67</AvgGuest>
<Sales>$1,852.00</Sales>
</Interval>
<Totals>
<Checks>128</Checks>
<Guests>248</Guests>
<AvgCheck>$50.37</AvgCheck>
<AvgGuest>$26.00</AvgGuest>
<Sales>$6,447.58</Sales>
</Totals>
</MealPeriod1>
<MealPeriod2>
<Interval>
<Name>05:00pm-06:00pm</Name>
<Checks>36</Checks>
<Guests>71</Guests>
<AvgCheck>$47.85</AvgCheck>
<AvgGuest>$24.26</AvgGuest>
<Sales>$1,722.75</Sales>
</Interval>
<Interval>
<Name>06:00pm-07:00pm</Name>
<Checks>40</Checks>
<Guests>79</Guests>
<AvgCheck>$49.01</AvgCheck>
<AvgGuest>$24.81</AvgGuest>
<Sales>$1,960.25</Sales>
</Interval>
<Interval>
<Name>07:00pm-08:00pm</Name>
<Checks>46</Checks>
<Guests>82</Guests>
<AvgCheck>$51.03</AvgCheck>
<AvgGuest>$28.63</AvgGuest>
<Sales>$2,347.29</Sales>
</Interval>
<Interval>
<Name>08:00pm-09:00pm</Name>
<Checks>53</Checks>
<Guests>80</Guests>
<AvgCheck>$42.04</AvgCheck>
<AvgGuest>$27.85</AvgGuest>
<Sales>$2,228.25</Sales>
</Interval>
<Interval>
<Name>09:00pm-10:00pm</Name>
<Checks>39</Checks>
<Guests>68</Guests>
<AvgCheck>$46.94</AvgCheck>
<AvgGuest>$26.92</AvgGuest>
<Sales>$1,830.50</Sales>
</Interval>
<Interval>
<Name>10:00pm-11:00pm</Name>
<Checks>39</Checks>
<Guests>56</Guests>
<AvgCheck>$50.90</AvgCheck>
<AvgGuest>$35.45</AvgGuest>
<Sales>$1,985.00</Sales>
</Interval>
<Totals>
<Checks>253</Checks>
<Guests>436</Guests>
<AvgCheck>$47.72</AvgCheck>
<AvgGuest>$27.69</AvgGuest>
<Sales>$12,074.04</Sales>
</Totals>
</MealPeriod2>
<MealPeriod3>
<Interval>
<Name>11:00pm-12:00am</Name>
<Checks>35</Checks>
<Guests>54</Guests>
<AvgCheck>$35.80</AvgCheck>
<AvgGuest>$23.20</AvgGuest>
<Sales>$1,253.04</Sales>
</Interval>
<Interval>
<Name>12:00am-01:00am</Name>
<Checks>34</Checks>
<Guests>45</Guests>
<AvgCheck>$38.21</AvgCheck>
<AvgGuest>$28.87</AvgGuest>
<Sales>$1,299.16</Sales>
</Interval>
<Interval>
<Name>01:00am-02:00am</Name>
<Checks>23</Checks>
<Guests>45</Guests>
<AvgCheck>$40.30</AvgCheck>
<AvgGuest>$20.60</AvgGuest>
<Sales>$926.87</Sales>
</Interval>
<Interval>
<Name>02:00am-03:00am</Name>
<Checks>30</Checks>
<Guests>59</Guests>
<AvgCheck>$48.38</AvgCheck>
<AvgGuest>$24.60</AvgGuest>
<Sales>$1,451.33</Sales>
</Interval>
<Interval>
<Name>03:00am-04:00am</Name>
<Checks>1</Checks>
<Guests>6</Guests>
<AvgCheck>$226.50</AvgCheck>
<AvgGuest>$37.75</AvgGuest>
<Sales>$226.50</Sales>
</Interval>
<Totals>
<Checks>123</Checks>
<Guests>209</Guests>
<AvgCheck>$41.93</AvgCheck>
<AvgGuest>$24.67</AvgGuest>
<Sales>$5,156.90</Sales>
</Totals>
</MealPeriod3>
</SalesSummary>
Here is my class so far. Using multiple XmlElement attributes with the same name isn't going to work, but I'm not sure how to decorate the class properties in a way that will. Any pointers would be much appreciated. Thanks!
public partial class SalesSummary
{
[XmlElement("MealPeriod1")]
public List<SalesSummaryMealPeriod1> MealPeriod1List { get; set; }
[XmlElement("MealPeriod1")]
public string MealPeriod1 { get; set; }
[XmlElement("MealPeriod1Sales")]
public string MealPeriod1Sales { get; set; }
[XmlElement("MealPeriod2")]
public List<SalesSummaryMealPeriod2> MealPeriod2List { get; set; }
[XmlElement("MealPeriod2")]
public string MealPeriod2 { get; set; }
[XmlElement("MealPeriod2Sales")]
public string MealPeriod2Sales { get; set; }
[XmlElement("MealPeriod3")]
public List<SalesSummaryMealPeriod3> MealPeriod3List { get; set; }
[XmlElement("MealPeriod3")]
public string MealPeriod3 { get; set; }
[XmlElement("MealPeriod3Sales")]
public string MealPeriod3Sales { get; set; }
[XmlElement("MealPeriod4")]
public List<SalesSummaryMealPeriod4> MealPeriod4List { get; set; }
[XmlElement("MealPeriod4")]
public string MealPeriod4 { get; set; }
[XmlElement("MealPeriod4Sales")]
public string MealPeriod4Sales { get; set; }
}
Note that the MealPeriodx elements are all fixed.
Each of your <MealPeriodx> elements consists of mixed content, and may contain:
A string value;
A sequence of <Interval> elements;
A <Totals> element.
Building on the answer to Correct XML serialization and deserialization of "mixed" types in .NET by Stefan, you can bind such an element to a c# type as follows:
public class MealPeriod
{
// A polymorphic array of "mixed" types.
// See https://stackoverflow.com/questions/2567414/correct-xml-serialization-and-deserialization-of-mixed-types-in-net
// and the solution by Stefan, https://stackoverflow.com/users/307747/stefan
// for why this works.
[XmlElement("Interval", typeof(Interval))]
[XmlElement("Totals", typeof(Totals))]
[XmlText(typeof(string))]
public List<object> Items { get; set; }
}
public class Interval
{
public string Name { get; set; }
public int Checks { get; set; }
public int Guests { get; set; }
public string AvgCheck { get; set; }
public string AvgGuest { get; set; }
public string Sales { get; set; }
}
public class Totals
{
public int Checks { get; set; }
public int Guests { get; set; }
public string AvgCheck { get; set; }
public string AvgGuest { get; set; }
public string Sales { get; set; }
}
Then, you can define a root object SalesSummary as follows, where each MealPeriodx property must be a collection of MealPeriod objects, since each <MealPeriodx> appears multiple times in the source XML:
public class SalesSummary
{
[XmlElement("MealPeriod1")]
public List<MealPeriod> MealPeriod1 { get; set; }
[XmlElement("MealPeriod1Sales")]
public decimal MealPeriod1Sales { get; set; }
[XmlElement("MealPeriod2")]
public List<MealPeriod> MealPeriod2 { get; set; }
[XmlElement("MealPeriod2Sales")]
public decimal MealPeriod2Sales { get; set; }
[XmlElement("MealPeriod3")]
public List<MealPeriod> MealPeriod3 { get; set; }
[XmlElement("MealPeriod3Sales")]
public decimal MealPeriod3Sales { get; set; }
[XmlElement("MealPeriod4")]
public List<MealPeriod> MealPeriod4 { get; set; }
[XmlElement("MealPeriod4Sales")]
public decimal MealPeriod4Sales { get; set; }
}
public static class MealPeriodExtensions
{
public static string MealPeriodName(this IEnumerable<MealPeriod> mealPeriods)
{
if (mealPeriods == null)
return null;
return String.Concat(mealPeriods.Where(m => m.Items != null).SelectMany(m => m.Items).OfType<string>());
}
public static IEnumerable<T> MealPeriodItems<T>(this IEnumerable<MealPeriod> mealPeriods)
{
if (mealPeriods == null)
return Enumerable.Empty<T>();
return mealPeriods.Where(m => m.Items != null).SelectMany(m => m.Items).OfType<T>();
}
}
Then, given a deserialization helper method such as
public static class XmlSerializationHelper
{
public static T LoadFromXml<T>(this string xmlString, XmlSerializer serial = null)
{
serial = serial ?? new XmlSerializer(typeof(T));
using (var reader = new StringReader(xmlString))
{
return (T)serial.Deserialize(reader);
}
}
}
You can do:
var summary = xml.LoadFromXml<SalesSummary>();
// Prints:
// MealPeriod1 name: "Lunch"
Console.WriteLine("MealPeriod1 name: \"{0}\"", summary.MealPeriod1.MealPeriodName());
// Prints
// MealPeriod1 Total Sales: "$6,447.58"
Console.WriteLine("MealPeriod1 Total Sales: \"{0}\"", summary.MealPeriod1.MealPeriodItems<Totals>().Single().Sales);
Notes:
If you re-serialize SalesSummary to XML, then the two <MealPeriodx> elements will be shifted to become adjacent to each other. There is no easy way to avoid this using XmlSerializer, other than implementing IXmlSerializable or manually constructing an [XmlAnyElement] surrogate array as shown in this answer.
Luckily your question only asks for deserialization.
Interval and Totals have many common properties so you might want to have them share a common base class or interface.
Sample working .Net fiddle here.
I have following XML-Data:
<?xml version="1.0"?>
<e:propertyset xmlns:e="urn:schemas-upnp-org:event-1-0">
<e:property>
<LastChange>
<Event xmlns="urn:schemas-upnp-org:metadata-1-0/RCS/">
<InstanceID val="0">
<RoomVolumes val="uuid:29e07ad9-224f-4160-a2bc-61d17845182a=100"/>
<Volume channel="Master" val="100"/>
<Mute channel="Master" val="0"/>
<RoomMutes val="uuid:29e07ad9-224f-4160-a2bc-61d17845182a=0"/>
</InstanceID>
</Event>
</LastChange>
</e:property>
</e:propertyset>
And here are my classes:
[XmlRoot("propertyset", Namespace = "urn:schemas-upnp-org:event-1-0")]
public class EventPropertySet
{
[XmlElement("property")]
public List<EventProperty> Properties { get; set; }
}
public class EventProperty
{
[XmlElement("LastChange")]
public string LastChange { get; set; }
[XmlElement("SinkProtocolInfo")]
public string SinkProtocolInfo { get; set; }
[XmlElement("IndexerStatus")]
public string IndexerStatus { get; set; }
[XmlElement("SystemUpdateID")]
public string SystemUpdateID { get; set; }
}
Now when I try to deserialize the XML-Data 'LastChange' is always 'null'.
When I modify the class 'EventProperty' like so:
public class EventProperty
{
[XmlElement("LastChange", Namespae = "")]
public string LastChange { get; set; }
[XmlElement("SinkProtocolInfo", Namespae = "")]
public string SinkProtocolInfo { get; set; }
[XmlElement("IndexerStatus", Namespae = "")]
public string IndexerStatus { get; set; }
[XmlElement("SystemUpdateID", Namespae = "")]
public string SystemUpdateID { get; set; }
}
The deserialization throws an exception:
XmlException: ReadElementContentAs() methods cannot be called on an element that has child elements. Line 1, position 103.
Any ideas what I should do?
Sorry, I found the problem. The data behind "LastChange" is normaly a parsed XML-Structure (like this:)
<LastChange><Event xmlns="urn:schemas-upnp-org:metadata-1-0/RCS/"><InstanceID val="0"><RoomVolumes val="uuid:29e07ad9-224f-4160-a2bc-61d17845182a=100"/><Volume channel="Master" val="100"/><Mute channel="Master" val="0"/><RoomMutes val="uuid:29e07ad9-224f-4160-a2bc-61d17845182a=0"/></InstanceID></Event></LastChange>
Now when I don't do the "DeParse" with WebUtility.HtmlDecode, everythings works fine.
Hello to all (sorry for my bad english),
I have a strange problem: I'm using a WCF RIA Service in LightSitch 2012 (LS).
The Class Library with the WCF RIA compiles, and I'm able to use it a new Data Source in LS.
I'm able to import the tables and correctly see the relationship between tables (Navigation Properties, BUT when I compile the whole Solution I get this error:
Multiplicity is not valid in Role 'TappaEntity' in relationship 'AssocTappe'. Because the Dependent Role refers to the key properties, the upper bound of the multiplicity of the Dependent Role must be 1.
Right now the only solution is the comment the Association in the second class (TappaEntity), but I didn't try to use the Tables and I'm exprected errors..
Bellow I write my code..can someone help me please?
Thanks a lot!!!
public class GiroEntity
{
[Key(), Editable(false)]
public int IdGiro { get; set; }
[Required(ErrorMessage = "La descrizione del giro e' obbligatoria"), Editable(false), StringLength(50)]
public string DescrizioneGiro { get; set; }
[Include]
[Association("AssocTappe", "IdGiro", "IdTappa", IsForeignKey = false)]
public IQueryable<TappaEntity> Tappe { get; set; }
}
public class TappaEntity
{
[Key(), Editable(false)]
public int IdTappa { get; set; }
[Required(ErrorMessage = "La descrizione della tappa e' obbligatoria"), Editable(false), StringLength(50)]
public string DescrizioneTappa { get; set; }
[Association("AssocTappe", "IdTappa", "IdGiro", IsForeignKey = true)]
public GiroEntity Giro { get; set; }
}
O Found the answer...see the follow code
public class GiroEntity
{
[Key(), Editable(false)]
public int IdGiro { get; set; }
[Required(ErrorMessage = "La descrizione del giro e' obbligatoria"), Editable(false), StringLength(50)]
public string DescrizioneGiro { get; set; }
[Include]
[Association("AssocTappe", "IdGiro", "ParentId", IsForeignKey = false)]
//[Required(ErrorMessage = "Per il giro devono essere definite delle tappe")]
public List<TappaEntity> Tappe { get; set; }
}
public class TappaEntity
{
[Key(), Editable(false)]
public int IdTappa { get; set; }
[Required(ErrorMessage = "La descrizione della tappa e' obbligatoria"), Editable(false), StringLength(50)]
public string DescrizioneTappa { get; set; }
public int? ParentId { get; set; }
[Include]
[Association("AssocTappe", "ParentId", "IdGiro", IsForeignKey = true)]
public GiroEntity Giro { get; set; }
}