I need to format an xml into a soap1.1 response envelope.
First, my business objects. I am aware of the insane nesting. I am not allowed to change the structure:
[DataContract]
public class DIVSReturnObject
{
public CoverageResponseDocument coverageResponseDoc { get; set; }
public DIVSReturnObject()
{
coverageResponseDoc = new CoverageResponseDocument();
}
}
[DataContract]
public class CoverageResponseDocument
{
public Detail detail { get; set; }
public CoverageResponseDocument()
{
detail = new Detail();
}
}
[DataContract]
public class Detail
{
public PolicyInformation policyInformation { get; set; }
public VehileInformation vehicleInformation { get; set; }
public Detail()
{
policyInformation = new PolicyInformation();
}
}
[DataContract]
public class PolicyInformation
{
public OrganizationDetails organizationDetails { get; set; }
public PolicyDetails policyDetails { get; set; }
public CoverageStatus coverageStatus { get; set; }
public PolicyInformation()
{
coverageStatus = new CoverageStatus();
}
}
[DataContract]
public class CoverageStatus
{
public ResponseDetails responseDetails { get; set; }
public CoverageStatus()
{
responseDetails = new ResponseDetails();
}
}
[DataContract]
public class ResponseDetails
{
[DataMember]
[XmlAttribute(AttributeName = "ResponseCode")]
public string ResponseCode { get; set; }
[DataMember]
[XmlAttribute(AttributeName = "UnconfirmedReasonCode")]
public string UnconfirmedReasonCode { get; set; }
}
Next, my code to serialize the desired object to XML:
XmlDocument xmlDoc = new XmlDocument();
XmlSerializer xmlSerializer = new XmlSerializer(divsResponse.GetType());
using (MemoryStream xmlStream = new MemoryStream())
{
xmlSerializer.Serialize(xmlStream, divsResponse);
xmlStream.Position = 0;
xmlDoc.Load(xmlStream);
return xmlDoc.InnerXml;
}
Next, the resulting XML string. Note that ResponseCode and UnconfirmedReasonCode are attributes when they should be their own elements:
<?xml version="1.0"?>
<DIVSReturnObject xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<coverageResponseDoc>
<detail>
<policyInformation>
<coverageStatus>
<responseDetails ResponseCode="Unconfirmed" UnconfirmedReasonCode="VIN1" />
</coverageStatus>
</policyInformation>
</detail>
</coverageResponseDoc>
</DIVSReturnObject>
Finally, the desired envelope with the correct namespaces (how do I add those?):
<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/">
<SOAP-ENV:Body>
<CoverageResponseDocument PublicationVersion="00200809" PublicationDate="2008-11-05" xmlns="http://www.iicmva.com/CoverageVerification/">
<Detail>
<PolicyInformation>
<CoverageStatus>
<ResponseDetails>
<ResponseCode>Unconfirmed</ResponseCode>
<UnconfirmedReasonCode>VIN1</UnconfirmedReasonCode>
</ResponseDetails>
</CoverageStatus>
<OrganizationDetails>
<NAIC>12345</NAIC>
<!-- this can be echoed from the request or provide the actual NAIC that has evidence of coverage -->
</OrganizationDetails>
<PolicyDetails>
<!-- this section can be echoed from the request -->
<VerificationDate>2015-01-01T00:00:00.000</VerificationDate>
<PolicyKey>UNKNOWN</PolicyKey>
<PolicyState>CT</PolicyState>
</PolicyDetails>
</PolicyInformation>
</Detail>
</CoverageResponseDocument>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
Under the following conditions which I hope apply in your case, there is indeed a pretty quick way to solve your problems. And time is money, right?
Soap response is rather static (no collection etc. Just a hand full of fields to fill in).
You do not have full control over your code base (DOM) and as such look to decouple your solution somewhat from the DOM code. With DOM I mean the data model classes you showed in your question.
The drawbacks of the solution I show below are:
Not a "generic" solution which should be applied in all situations. (suits your current problem but maybe not your next ones).
As cool as text templating is, there is one source of possible errors, once things get more complicated (dynamic) - it is hard to proof that you will produce the correct output at all times. Maybe then, other solutions such as a reviewable xslt transform or even better a "contract first -> code generating of the DOM" appraoch is more reliable.
I suggest you warm yourself up a little before you add this to your main project. A small C# console application project will do.
Create your toy console app (File-> New Project -> C# -> ConsoleApp).
Right click your project in solution explore and Add New Item -> Runtime Text Template. It is important to use "Runtime Text Template" and not "Text Template" in your case. Give it a proper name (I used in my demo code "MySoapResponse.tt".
In the generated .tt file which is now part of your project items, copy and paste the desired soap output below what is already contained in the file.
Create yourself a dummy DOM just to have the same scenario as you have it in your application.
Add a new C# code file to your project and add a partical class in the same namespace as the code-behind .tt code uses, with the name of the generated template class (which is partial). Add a constructor where you pass as argument the instance of your DOM which you want to use for generating the message.
Create a few Query functions or simply a Document {get;} property.
In your console app main, create an instance of your DOM with some data in it.
Also create an instance of your generated generator object.
Call the TransformText() method and save the string to a file on your disk for inspection.
Rince and repeat: Modify your .tt template by adding more and more markdown statements at the spots where you need the data from your DOM.
As you can see - 10 steps and you are done.
This is how the transformation code looks like (even simpler than XmlSerializer calls):
namespace ConsoleApplication2
{
class Program
{
static void Main(string[] args)
{
MyDOM.Root document = MyDOM.Root.CreateExampleDocument();
MySoapResponse responseGenerator = new MySoapResponse(document);
System.IO.File.WriteAllText(#"E:\temp\blabla.txt", responseGenerator.TransformText());
System.Console.WriteLine("Done transforming!");
}
}
}
And this - just for completness sake is my miniDOM I invented for this answer:
namespace MyDOM
{
internal class Root
{
internal ChildA childA { get; set; }
internal ChildB childB { get; set; }
internal static Root CreateExampleDocument()
{
Root r = new Root();
r.childA = new ChildA();
r.childB = new ChildB();
r.childA.ResponseCode = "I have my reasons!";
r.childA.ReasonCode = "I have response code using T4!";
return r;
}
}
internal class ChildA
{
internal string ResponseCode { get; set; }
internal string ReasonCode { get; set; }
}
internal class ChildB
{
}
}
I only added 2 items - which is sufficient to showcase how it all works.
In the .tt file of course, I also added just enough to cover those 2 items.
<## template language="C#" #>
<## assembly name="System.Core" #>
<## import namespace="System.Linq" #>
<## import namespace="System.Text" #>
<## import namespace="System.Collections.Generic" #>
<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/">
<SOAP-ENV:Body>
<CoverageResponseDocument PublicationVersion="00200809" PublicationDate="2008-11-05" xmlns="http://www.iicmva.com/CoverageVerification/">
<Detail>
<PolicyInformation>
<CoverageStatus>
<ResponseDetails>
<ResponseCode><#=ResponseCode()#></ResponseCode>
<UnconfirmedReasonCode><#=ReasonCode()#></UnconfirmedReasonCode>
</ResponseDetails>
</CoverageStatus>
<OrganizationDetails>
<NAIC>12345</NAIC>
<!-- this can be echoed from the request or provide the actual NAIC that has evidence of coverage -->
</OrganizationDetails>
<PolicyDetails>
<!-- this section can be echoed from the request -->
<VerificationDate>2015-01-01T00:00:00.000</VerificationDate>
<PolicyKey>UNKNOWN</PolicyKey>
<PolicyState>CT</PolicyState>
</PolicyDetails>
</PolicyInformation>
</Detail>
</CoverageResponseDocument>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
The calls I invoke from within the .tt template (e.g. <#=ResponseCode()) are methods I added to my partial class part of the generator object.
namespace ConsoleApplication2
{
public partial class MySoapResponse
{
MyDOM.Root Document { get; }
internal MySoapResponse(MyDOM.Root document)
{
Document = document;
}
internal string ResponseCode()
{
return Document.childA.ResponseCode;
}
internal string ReasonCode()
{
return Document.childA.ReasonCode;
}
}
}
And that is about it. A bit of clicking and a few lines of code actually written (let it be 50 lines). For sure better than using a StringBuilder by hand.
Related
I have implemented Swagger/Swashbuckle in my AspNetCore 2.1 app and it's working great. However some of my API models are based on complex WCF XML services and use a few System.Xml.Serialization annotations for adding namespaces and changing the names of properties.
When these models are viewed on the Swagger page they are missing the namespaces and ignore any attribute name changes. Therefore the swagger default requests won't deserialize when posted to my controller.
On the other hand the JSON requests work fine.
Consider these two classes;
public class test
{
[System.Xml.Serialization.XmlElementAttribute(Namespace = "http://www.example.com/ns/v1")]
public test_c ct1 { get; set; }
public string t2 { get; set; }
}
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true, Namespace = "http://www.example.com/ns/v1")]
public class test_c
{
[System.Xml.Serialization.XmlElementAttribute("my-new-name")]
public string tc1 { get; set; }
public string tc2 { get; set; }
}
When serialized as XML we get something like;
<?xml version="1.0" encoding="UTF-8"?>
<test xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<ct1 xmlns="http://www.example.com/ns/v1">
<my-new-name>aaa</my-new-name>
<tc2>xxxxxx</tc2>
</ct1>
<t2>bbb</t2>
</test>
This is what is the xml that is expected as the request.
However, the swagger sample request shows as;
<?xml version="1.0" encoding="UTF-8"?>
<test>
<ct1>
<tc1>string</tc1>
<tc2>string</tc2>
</ct1>
<t2>string</t2>
</test>
Which will not deserialize when posted.
So now to the point. All I need/hope to do is modify the swagger request xml schema -- while not affecting the JSON request schema (I don't even know if they are -or can be- separate).
I thought this would be simple but I'm lost sea of swashbuckle options & setup.
I was hoping that I could simply assign the aspnet xml serializer to deserialize a provided request object. Or implement an ISchemaFilter or IOperationsFilter?
If someone could point me in the right direction I'd be forever grateful.
ok, well I'll answer this myself.
I did end up implementing ISchemaFilter. So to answer this question using the same models as in the question, I first created my own implementation of ISchemaFilter and just hardcoded in the checks for the required changes (in reality i'm going to have a big dictionary<> of class and property changes).
The "Schema" class allows us to add XML only meta to the model class or any of its properties.
public class MyRequestISchemaFilter : ISchemaFilter
{
public void Apply(Schema schema, SchemaFilterContext context)
{
if (schema.Type == "object"){
if (context.SystemType == typeof(test_c))
{
schema.Xml = new Xml()
{
Namespace = "http://www.example.com/ns/v1"
};
foreach (var prop in schema.Properties)
{
if (prop.Key == "tc1")
{
prop.Value.Xml = new Xml()
{
Name = "my-new-name"
};
}
}
}
}
}
}
Then we wire-up this filter in our AddSwaggerGen service configure call at startup.
services.AddSwaggerGen(c =>
{
c.SchemaFilter<MyRequestISchemaFilter>();
...
Here is the sample request XML that the filter will produce;
<?xml version="1.0" encoding="UTF-8"?>
<test>
<ct1 xmlns="http://www.example.com/ns/v1">
<my-new-name>string</my-new-name>
<tc2>string</tc2>
</ct1>
<t2>string</t2>
</test>
It's missing the root level XMLSchema namespaces but the Schema::Xml prop doesn't support multiple namespaces. In any case the XML deserializes fine as long as I don't use those namespaces.
I might be able to add them if I add properties with a namespace then set them as Attributes of the root element (which the Xml prop does handle). Haven't tried that though.
I've got an app with config.xml file added for user settings.
I am reading it simply by:
DataSet config = new DataSet();
config.ReadXml(configPath);
Parameters in config.xml are in columns grouped under some tables:
<?xml version="1.0" standalone="yes"?>
<UserSetup>
<TableA>
<revBegin>value1</revBegin>
<revEnd>value2</revEnd>
...
</TableA>
...
</UserSetup>
What I'm looking for is a clean way to read from DataSet config, without memorizing table or column names.
In my current implementation I achieved that by following class:
public static class MyTables
{
public static class TableA
{
public const String name = "TableA";
public const String revBegin = "revBegin";
public const String revEnd = "revEnd";
...
}
...
}
And I read values like this:
String revEnd = config.Tables[MyTables.TableA.name].Rows[0][MyTables.TableA.revEnd].ToString();
But I somehow fill that it is quite easy problem solved in quite complicated - not to say nasty - way.
Do you have any idea how can I make things simpler or cleaner?
P.S. At some point I tried reducing config.Tables[MyTables.TableA.name] part to config.Tables[MyTables.TableA] but - how I see it - it would require adding Index[object] to sealed class DataTableCollection and overriding ToString() method in my static class - both impossible. Am I right?
Unless you absolutely have to use a DataSet to read the XML, you can achieve the same results using XML serialization. Annotate your classes with the below.
[XmlRoot]
[Serializable]
public class UserSetUp
{
[XmlElement]
public TableA TableA { get; set; }
}
[Serializable]
public class TableA
{
[XmlElement]
public string revBegin { get; set; }
[XmlElement]
public string revEnd { get; set; }
}
Assuming your config in on C:\ for this example.
var configStream = File.OpenRead(#"C:\Config.xml");
var reader = new StreamReader(configStream);
var xmlString = reader.ReadToEnd();
var serializer = new XmlSerializer(typeof(UserSetUp));
var stringReader = new StringReader(xmlString);
var userSetup = (UserSetUp)serializer.Deserialize(stringReader);
I've tested it with the below XML and it works ok.
<?xml version="1.0" encoding="utf-16" ?>
<UserSetUp>
<TableA>
<revBegin>1</revBegin>
<revEnd>2</revEnd>
</TableA>
</UserSetUp>
Hope that helps you on your way.
I am retrieving XML documents from a web service I have no control over. The XML is formatted similarly to the following:
<?xml version="1.0"?>
<ns:obj xmlns:ns="somenamespace">
<address>1313 Mockingbird Lane</address>
<residents>5</residents>
</ns:obj>
where the root node is in the "ns" namespace, but none of its child elements are.
After some trial and error, I found that I could deserialize the document to a C# object by doing the following:
[XmlRoot(Namespace="somenamespace", ElementName="obj")]
public class xmlObject
{
[XmlElement(Namespace = "")]
public string address { get; set; }
[XmlElement(Namespace = "")]
public int residents { get; set; }
}
class Program
{
static void Main(string[] args)
{
string xml =
"<?xml version=\"1.0\"?>" +
"<ns:obj xmlns:ns=\"somenamespace\">" +
" <address>1313 Mockingbird Lane</address>" +
" <residents>5</residents>" +
"</ns:obj>";
var serializer = new XmlSerializer(typeof(xmlObject));
using (var reader = new StringReader(xml))
{
var result = serializer.Deserialize(reader) as xmlObject;
Console.WriteLine("{0} people live at {1}", result.residents, result.address);
// Output: "5 people live at 1313 Mockingbird lane"
}
}
}
If I omit the XmlElementAttribute on the individual members, I instead get an empty object. I.e. The output reads
0 people live at
(result.address is equal to null.)
I understand the rationale behind why the deserialization process works like this, but I'm wondering if there is a less verbose way to tell XmlSerializer that the child elements of the object are not in the same namespace as the root node.
The objects I'm working with in production have dozens of members and, for cleanliness sake, I'd like to avoid tagging all of them with [XmlElement(Namespace = "")] if it's easily avoidable.
You can combine XmlRootAttribute with XmlTypeAttribute to make it so the root element, and the root element's elements, have different namespaces:
[XmlRoot(Namespace="somenamespace", ElementName="obj")]
[XmlType(Namespace="")]
public class xmlObject
{
public string address { get; set; }
public int residents { get; set; }
}
Using the type above, if I deserialize and re-serialize your XML I get:
<q1:obj xmlns:q1="somenamespace">
<address>1313 Mockingbird Lane</address>
<residents>5</residents>
</q1:obj>
Sample fiddle.
If you know the contract with the web service, why not use a DataContractSerializer to deserialize the XML into the objects?
I have the following xml:
<?xml version="1.0" encoding="UTF-8"?>
<connection_state>conn_state</connection_state>
Following the msdn, I must describe it as a type for correct deserialization using XmlSerializer. So the class name points the first tag, and its fields subtags.
For example:
public class connection_state
{
public string state;
}
Will be transformed into the following xml:
<?xml version="1.0" encoding="UTF-8"?>
<connection_state>
<state>conn_state</state>
</connection_state>
But the xml I receive has only one tag. And we cannot create a field with the name of its class like:
public class connection_state
{
public string connection_state;
}
Or can?
Is there any solution for this issue?
Proper Xml has a root element with no content except other elements. If you are stuck with that tiny one-tag psuedo-XML, is there a reason you need to use XmlSerializer? Why not just create a class with a constructor that takes the literal "Xml" string:
using System.Xml.Linq;
public class connection_state {
public string state { get; set; }
public connection_state(string xml) {
this.state = XDocument.Parse(xml).Element("connection_state").Value;
}
}
Edit:
In response to OP's comment: You don't have to us an XmlSerializer; you can just read the ResponseStream directly and pass that to your connection_state constructor:
String xmlString = (new StreamReader(webResponse.GetResponseStream())).ReadToEnd();
connection_state c= new connection_state(xmlString);
Replace
public class connection_state
{
public string state;
}
to
public class connection_state
{
public string state {set; get;}
}
is it possible to use the XmlSerializer in .NET to load an XML file which includes other XML files? And how?
This, in order to share XML state easily in two "parent" XML files, e.g. AB and BC in below.
Example:
using System;
using System.IO;
using System.Xml.Serialization;
namespace XmlSerializerMultipleFilesTest
{
[Serializable]
public class A
{
public int Value
{ get; set; }
}
[Serializable]
public class B
{
public double Value
{ get; set; }
}
[Serializable]
public class C
{
public string Value
{ get; set; }
}
[Serializable]
public class AB
{
public A A
{ get; set; }
public B B
{ get; set; }
}
[Serializable]
public class BC
{
public B B
{ get; set; }
public C C
{ get; set; }
}
class Program
{
public static void Serialize<T>(T data, string filePath)
{
using (var writer = new StreamWriter(filePath))
{
var xmlSerializer = new XmlSerializer(typeof(T));
xmlSerializer.Serialize(writer, data);
}
}
public static T Deserialize<T>(string filePath)
{
using (var reader = new StreamReader(filePath))
{
var xmlSerializer = new XmlSerializer(typeof(T));
return (T)xmlSerializer.Deserialize(reader);
}
}
static void Main(string[] args)
{
const string fileNameA = #"A.xml";
const string fileNameB = #"B.xml";
const string fileNameC = #"C.xml";
const string fileNameAB = #"AB.xml";
const string fileNameBC = #"BC.xml";
var a = new A(){ Value = 42 };
var b = new B(){ Value = Math.PI };
var c = new C(){ Value = "Something rotten" };
Serialize(a, fileNameA);
Serialize(b, fileNameB);
Serialize(c, fileNameC);
// How can AB and BC be deserialized from single
// files which include two of the A, B or C files.
// Using ideally something like:
var ab = Deserialize<AB>(fileNameAB);
var bc = Deserialize<BC>(fileNameBC);
// That is, so that A, B, C xml file
// contents are shared across these two
}
}
}
Thus, the A, B, C files contain the following:
A.xml:
<?xml version="1.0" encoding="utf-8"?>
<A xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Value>42</Value>
</A>
B.xml:
<?xml version="1.0" encoding="utf-8"?>
<B xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Value>3.1415926535897931</Value>
</B>
C.xml:
<?xml version="1.0" encoding="utf-8"?>
<C xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Value>Something rotten</Value>
</C>
And then the "parent" XML files would contain a XML include file of some sort (I have not been able to find anything like this), such as:
AB.xml:
<?xml version="1.0" encoding="utf-8"?>
<AB xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<A include="A.xml"/>
<B include="B.xml"/>
</AB>
BC.xml:
<?xml version="1.0" encoding="utf-8"?>
<BC xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<B include="B.xml"/>
<C include="C.xml"/>
</BC>
Of course, I guess this can be solved by implementing IXmlSerializer for AB and BC, but I was hoping there was an easier solution or a generic solution with which classes themselves only need the [Serializable] attribute and nothing else. That is, the split into multiple files is XML only and handled by XmlSerializer itself or a custom generic serializer on top of this.
I know this should be somewhat possible with app.config (as in Use XML includes or config references in app.config to include other config files' settings), but I would prefer a solution based on XmlSerializer.
Thanks.
The short answer is yes, it's possible without implementing IXmlSerializer. But it's really ugly. Probably uglier than implementing IXmlSerializer. Check this out to get started. It involves running sgen.exe to generate the XmlSerializationWriter and XmlSerializationReader that is generated when you create an XmlSerializer. Then you modify the output from sgen.exe. Not fun. Not fun at all. But possible.