ASP.NET Core How to output custom XML Model - c#

I have a Web API built in C# core and I need to customize the XML output that one of my endpoints gives.
To configure my app to return XML and JSON, I use Accept Header like so:
// Add framework services.
services
.AddMvc(options => {
options.RespectBrowserAcceptHeader = true;
})
//support application/xml
.AddXmlDataContractSerializerFormatters()
//support application/json
.AddJsonOptions(options => {
options.SerializerSettings.ContractResolver = new DefaultContractResolver();
});
And I have a model I return on a controller:
public class SageInvoice {
public long Id { get; set; }
public DateTime Created { get; set; }
public long? CustomerId { get; set; }
public long? JobId { get; set; }
public DateTime Date { get; set; }
public decimal Subtotal { get; set; }
public decimal VAT { get; set; }
public decimal Total { get; set; }
}
The XML comes out like so:
<ArrayOfSageInvoice xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07/AutomotiveDTO.DTOs.SageIntegration">
<SageInvoice>
<Created>2017-03-15T11:09:34.21</Created>
<CustomerId>1</CustomerId>
<Date>2017-03-15T00:00:00</Date>
<Id>2</Id>
<JobId>1</JobId>
<Subtotal>100.00</Subtotal>
<Total>120.00</Total>
<VAT>20.00</VAT>
</SageInvoice>
<SageInvoice>
<Created>2017-03-15T11:11:26.853</Created>
<CustomerId>2</CustomerId>
<Date>2017-03-15T00:00:00</Date>
<Id>3</Id>
<JobId i:nil="true"/>
<Subtotal>23532.00</Subtotal>
<Total>28238.40</Total>
<VAT>4706.40</VAT>
</SageInvoice>
</ArrayOfSageInvoice>
The consumer of my endpoint doesn't like it structured like this and wants me to make it look more:
<?xmlversion="1.0"encoding="utf-8"?>
<Order>
<OrderSummary>
<OrderID>1</OrderID>
<OrderReference>ORDER1</OrderReference>
<OrderDate>2017-03-15</OrderDate>
<AccountNumber>ACCOUNT1</AccountNumber>
<CompanyName>Company 1</CompanyName>
<ContactTitle>Mr</ContactTitle>
<ContactFirst>Dave</ContactFirst>
<ContactLast>Dave</ContactLast>
<Address1/>
<Address2/>
<Town/>
<County/>
<Postcode/>
<Country/>
<Telephone>01234567890</Telephone>
<NumberOfItems>2</NumberOfItems>
<OrderValue>200</OrderValue>
<Downloaded>false</Downloaded>
</OrderSummary>
<OrderSummary>
...
</OrderSummary>
</Order>
How can I change my XML output to match the above without wrecking the Accept headers JSON + XML input/output functionality?

This should do it (see MSDN). The docs are for .NET Framework, but should also apply to .NET Core.
[CollectionDataContract(Name = "Invoice", ItemName = "InvoiceSummary")]
public class SageInvoice
{
...
}

Related

C# How to ignore Xml Header in request to WebApi?

I'm creating a new Route to my WebApi, which should receive the following XML throught a HTTP POST
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<AcquireKeysRequest>
<version>2</version>
<content>
<id>MTV</id>
<type>VOD</type>
</content>
<protection>
<protection-system>
<type>DASH-CENC</type>
<system-id>urn:mpeg:dash:mp4protection:2011</system-id>
</protection-system>
</protection>
<key-timing>
<position>0</position>
</key-timing>
</AcquireKeysRequest>
I've mapped it through the framework, using the following Model:
public class AcquireKeysRequest
{
public int Version { get; set; }
public Content Content { get; set; }
public Protection Protection { get; set; }
public KeyTiming KeyTiming { get; set; }
}
public class Content
{
public string Id { get; set; }
public string Type { get; set; }
}
public class Protection
{
public ProtecionSystem ProtectionSystem{ get; set; }
}
public class ProtecionSystem
{
public string Type { get; set; }
public string SystemId { get; set; }
}
public class KeyTiming
{
public int Position { get; set; }
}
When I receive the request without the header
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
The mapping works just fine, but when I add the header, it breaks.
How can I ignore it?
[HttpPost]
[Route("{instanceId}")]
public object AcquireKeyRequest([FromUri]int instanceId, AcquireKeysRequest xml)
{
//SomeLogicHere
}
P.S: I know that the names in the Model and in the XML are diferent, I fixed in my code already.
Within your MVC Web API project please add the following package via NuGet:
Microsoft.AspNetCore.Mvc.Formatters.Xml
Then in your startup.cs. Adapt the following to generally enable XML serialization and deserialization.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(options =>
{
options.Filters.Add(new ProducesAttribute("application/xml"));
}).AddXmlSerializerFormatters();
}
Finally create a Get and Post Method withing a controller and try it out. For me both cases works. With or without the xml-tag.
[HttpGet]
public AcquireKeysRequest Get()
{
AcquireKeysRequest req = new AcquireKeysRequest();
req.KeyTiming = new KeyTiming() { Position = 2 };
req.Protection = new Protection()
{
ProtectionSystem = new ProtecionSystem() {
SystemId = "wkow", Type = "type"
}
};
req.Version = 2;
req.Content = new Content() { Id = "id", Type = "type" };
return req;
}
[HttpPost]
public void Post([FromBody]AcquireKeysRequest value)
{
}
I hope i could help out.
Cheers

Parse nested XML from URL

I'm currently working on a .NET 4.6 console application. I need to parse a nested XML from an URL and transform the XML into an object list.
The URL for the sample XML is the following:
https://www.w3schools.com/xml/cd_catalog.xml
The XML looks like this:
<?xml version="1.0" encoding="UTF-8"?>
<CATALOG>
<CD>
<TITLE>Empire Burlesque</TITLE>
<ARTIST>Bob Dylan</ARTIST>
<COUNTRY>USA</COUNTRY>
<COMPANY>Columbia</COMPANY>
<PRICE>10.90</PRICE>
<YEAR>1985</YEAR>
</CD>
<CD>
<TITLE>Hide your heart</TITLE>
<ARTIST>Bonnie Tyler</ARTIST>
<COUNTRY>UK</COUNTRY>
<COMPANY>CBS Records</COMPANY>
<PRICE>9.90</PRICE>
<YEAR>1988</YEAR>
</CD>
</CATALOG>
My corresponding C# classes look like this:
[XmlRoot(ElementName = "CATALOG")]
public class Catalog
{
[XmlElement("CD")]
List<Cd> Cds { get; set; }
}
[XmlRoot(ElementName = "CD")]
public class Cd
{
[XmlElement("TITLE")]
public string Title { get; set; }
[XmlElement("ARTIST")]
public string Artist { get; set; }
[XmlElement("COUNTRY")]
public string Country { get; set; }
[XmlElement("COMPANY")]
public string Company { get; set; }
[XmlElement("PRICE")]
public double Price { get; set; }
[XmlElement("YEAR")]
public int Year { get; set; }
}
My program class looks like this:
class Program
{
static void Main(string[] args)
{
Init();
}
public static void Init() {
var url = "https://www.w3schools.com/xml/cd_catalog.xml";
XmlDocument myXmlDocument = new XmlDocument();
myXmlDocument.Load(url);
var catalog = myXmlDocument.InnerXml.ToString();
var result = Deserialize<Catalog>(catalog);
// result is null :(
Console.ReadKey();
}
public static T Deserialize<T>(string xmlText)
{
try
{
var stringReader = new StringReader(xmlText);
var serializer = new XmlSerializer(typeof(T));
return (T)serializer.Deserialize(stringReader);
}
catch (Exception ex)
{
throw;
}
}
}
So far so good, my variable catalog consists out of an XML string, but somehow the XML doesn't get parsed correctly. I always get null as a return result. Perhaps it's because of my class definitions.
What do you think, do you have an idea on how to solve this issue? To retrieve an List<Cd> instead of null.
The error is very subtle. You have done everything right, but you missed to add the public access qualifier in your Catalog class, on the list of Cd like so:
[XmlRoot(ElementName = "CATALOG")]
public class Catalog
{
[XmlElement("CD")]
public List<Cd> Cds { get; set; }
}
Since the access qualifiers default to private, the deserializer is having a hard time finding the correct property to deserialize the XML into.
List<Cd> Cds { get; set; }
Change this line to
Public List<Cd> Cds { get; set; }

C# web service - getting list of equal element names

I was creating C# SOAP web service and web method from interface specification that should have specific xml structure in request:
<SomeItems>
<Value></Value>
<Value></Value>
...
</SomeItems>
It needs to have list of elements with equal names.
When I put in C# code class like:
public List<int> SomeItems { get; set; }
I get as output (checked in SOAP UI) (epv is xmlns:epv="..."):
<epv:SomeItems>
<!--Zero or more repetitions:-->
<epv:int></epv:int>
</epv:SomeItems>
If I add another class named "Value":
public List<Value> SomeItems { get; set; }
public class Value
{
public int Value1 { get; set; }
}
I get in SOAP UI:
<epv:SomeItems>
<!--Zero or more repetitions:-->
<epv:Value>
<epv:Value1></epv:Value1>
</epv:Value>
</epv:SomeItems>
Is there any way to get:
<epv:SomeItems>
<!--Zero or more repetitions:-->
<epv:Value></epv:Value>
</epv:SomeItems>
Where "Value" is int.
Moved solution from question to answer:
EDIT:
I have found solution:
public SomeItems SomeItems{ get; set; }
[XmlType(AnonymousType = true)]
public class SomeItems
{
[XmlElement(ElementName = "Value")]
public List<int> Value { get; set; }
}
Finally in SOAP UI:
<epv:SomeItems>
<!--Zero or more repetitions:-->
<epv:Value>?</epv:Value>
</epv:SomeItems>

Automapper from SOAP Web Service to View Model

I want to use Automapper for mapping SOAP Web Service Response to a Model which will be used to return the result through a Web API.
Mostly of the attributes returned in the object by the web service are codes, we want to show in the response of our api the descriptions related to those codes.
For example:
The web service response with a list of:
<charge>
<type>ABC</type>
<qualifier>3</qualifier>
<periodCode>004</periodCode>
<code>STE</code>
</charge>
<charge> ... </charge>
Which will be encapsulated in a class like this:
class Charge {
string type { get; set; }
string qualifier { get; set; }
string periodCode { get; set; }
string code { get; set; }
decimal rate { get; set; }
}
Our model is:
public class RCharge {
public string Description { get; set; }
public bool? IncludedInRate { get; set; }
public decimal? AmountValue { get; set; }
public string Period { get; set; }
}
I have stored in a database the information related to the codes and descriptions, as all codes have their own description.
The problem is how to map from the code returned in the web service, to the description. I have this code, and I could make a call to the database in search of the code and get the description, but is it ok? I guess the ConstructUsing is executed for every item in the response, so make a query here would result in a bunch of requests to DB.
AutoMapper.Mapper.Initialize(config => {
config
.CreateMap<Charge, RCharge>()
.ConstructUsing(s => RChargeConstructor.Construct(s));
});
public class RChargeConstructor {
public static RCharge Construct(ResolutionContext context) {
if (context == null || context.IsSourceValueNull)
return null;
var src = (Charge)context.SourceValue;
return new RCharge() {
Description = src.type, // want description from DB
IncludedInRate = src.qualifier == "3",
AmountValue = src.rate,
Period = src.periodCode // want description from DB
};
}
}
Is there a good approach for doing this kind of mapping?

Building XML request with RestSharp

I am attempting to work with a REST API using RestSharp and C#.
The documentation for the API that I am using gives a sample XML request:
<?xml version='1.0' encoding='UTF-8'?>
<messages>
<accountreference>EX0000000</accountreference>
<from>07700900654</from>
<message>
<to>07700900123</to>
<type>SMS</type>
<body>Hello Mr Sands.</body>
</message>
<message>
<to>07700900124</to>
<type>SMS</type>
<body>Hello Mr Mayo.</body>
</message>
</messages>
I am struggling to understand how to build the request in the format that they want (multiple elements called "message")
I have created these classes for RestSharp to serialize:
public class messages
{
public string accountreference { get; set; }
public string from { get; set; }
public message message { get; set; }
}
public class message
{
public string to { get; set; }
public string body { get; set; }
}
And here is my RestSharp code:
var client = new RestClient("http://api.url.com/v1.0")
{
Authenticator =
new HttpBasicAuthenticator(
UserName,
Password)
};
var request = new RestRequest("theresource", Method.POST) { RequestFormat = DataFormat.Xml };
request.AddBody(
new messages
{
accountreference = Configuration.AccountReference,
from = Configuration.From,
message =
new message { to = Configuration.Message.To, body = Configuration.Message.Body }
});
var response = client.Execute(request);
This works great when I have only 1 message element, but I don't know how to create multiple message elements without having them nested in an array, which doesn't work with the API.
By default RestSharp is using its own serializer but it also packs the DotNetSerializer so you achieve your goal by changing the serializer like this:
var request = new RestRequest("theresource", Method.POST)
{
RequestFormat = DataFormat.Xml,
XmlSerializer = new RestSharp.Serializers.DotNetXmlSerializer()
};
Then you can use a list of message objects and decorate it with XmlElement attribute:
public class messages
{
public string accountreference { get; set; }
public string from { get; set; }
[XmlElement("message")]
public List<message> messageList { get; set; }
}
public class message
{
public string to { get; set; }
public string body { get; set; }
}
Then you can change the last bit to add multiple messages:
request.AddBody(
new messages
{
accountreference = "ref",
from = "from",
messageList = new List<message>() {
new message { to = "to1", body = "body1" },
new message { to = "to2", body = "body2" }
}
});
which would produce (I got the XML by checking request.Parameters[0].Value):
<?xml version="1.0" encoding="utf-8"?>
<messages>
<accountreference>ref</accountreference>
<from>from</from>
<message>
<to>to1</to>
<body>body1</body>
</message>
<message>
<to>to2</to>
<body>body2</body>
</message>
</messages>
I guess this is the XML format you've been looking for.
Having message as list will work -
public class messages
{
public string accountreference { get; set; }
public string from { get; set; }
public List<message> message { get; set; }
}
public class message
{
public string to { get; set; }
public string body { get; set; }
}
Check the very last answer here -
How to post an array of complex objects with JSON, jQuery to ASP.NET MVC Controller?
If you face issues with list, try this -
Can RestSharp send a List<string> in a POST request?

Categories