I was trying, in a v2 function, to return an XML response to the caller. Of course, I want my Content-Type header to be properly set to application/xml in this case.
Using the V2 (ASP.Net Core) types, I was unable to do so. Documented my experience here
Content-Type header properly set to application/jsonI am able to return XML content easily, such as return new OkObjectResult(xmlDoc); or even return new OkObjectResult(xmlDoc) { ContentTypes = new Microsoft.AspNetCore.Mvc.Formatters.MediaTypeCollection { @"application/xml" } };
Output is either heavily escaped, wrapped in a .Net type tag (eg: <string>), doesn't have the Content-Type header set, or throws an HTTP 406 NOT ACCEPTABLE error.
Use the System.Net.Http types from v1 (eg: HttpResponseMessage) to create the response payload instead of the .Net Core (v2) types.
http://netitude.bc3tech.net/2018/05/25/creating-an-xml-json-converter-in-azure-functions
cc @fabiocav
@brandonh-msft is this a typo?
Attempt to return XML payload from the response with the Content-Type header properly set to application/json
@brandonh-msft The easiest way (if possible) is to rely on content negotation - for example:
public static class Function1
{
[FunctionName("Function1")]
public static IActionResult Run([HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)]HttpRequest req, TraceWriter log)
{
return new OkObjectResult(new MyData { Property1 = "Foo", Property2 = 3 });
}
}
public class MyData
{
public string Property1 { get; set; }
public int Property2 { get; set; }
}
```
GET http://localhost:7071/api/Function1 HTTP/1.1
User-Agent: Fiddler
Host: localhost:7071
Accept: application/json
HTTP/1.1 200 OK
Date: Fri, 25 May 2018 21:25:50 GMT
Content-Type: application/json; charset=utf-8
Server: Kestrel
Transfer-Encoding: chunked
{"Property1":"Foo","Property2":3}
GET http://localhost:7071/api/Function1 HTTP/1.1
User-Agent: Fiddler
Host: localhost:7071
Accept: application/xml
HTTP/1.1 200 OK
Date: Fri, 25 May 2018 21:25:59 GMT
Content-Type: application/xml; charset=utf-8
Server: Kestrel
Content-Length: 181
But if you can't do that (perhaps because you ONLY have xml), then I think `ContentResult` is the appropriate type to use (hat tip to @fabiocav for pointing me in the right direction).
```csharp
[FunctionName("ContentResultWithXmlInString")]
public static IActionResult Run4([HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)]HttpRequest req, TraceWriter log)
{
string xmlContent = File.ReadAllText("xmlcontent.xml");
return new ContentResult { Content = xmlContent, ContentType = "application/xml" };
}
GET http://localhost:7071/api/ContentResultWithXmlInString HTTP/1.1
User-Agent: Fiddler
Host: localhost:7071
Accept: application/xml
HTTP/1.1 200 OK
Date: Fri, 25 May 2018 22:16:58 GMT
Content-Type: application/xml
Server: Kestrel
Content-Length: 232
<?xml version="1.0" encoding="utf-8" ?>
<MyData xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07/HttpReturnXml">
<Property1>Foo</Property1>
<Property2>3</Property2>
</MyData>
I think the behavior you get when you put xml into a string and then put that string into an OkObjectResult is expected because OkObjectResult will serialize whatever value you pass it (so you're serializing a string as a string). This is the type of behavior I imagine engineers and PMs arguing about in a room. Its technically correct and probably never what you want 🤷♂️
Gave Accept a shot but unfortunately, in my example of a converter, it didn't work:
~~~
POST /api/ConvertToXml HTTP/1.1
Host: localhost:7071
Content-Type: application/json
Accept: application/xml
Cache-Control: no-cache
Postman-Token: 6533fbab-15bc-49ee-881e-8249f23b364e
{
"root": {
"this": {
"@att": "x",
"underthis": "val"
},
"that": {
"withval": "x",
"bigval": {
"#cdata-section": "ntttsomething somethingnttt"
}
}
}
}
~
Got back
~
{
"root": {
"this": {
"@att": "x",
"underthis": "val"
},
"that": {
"withval": "x",
"bigval": {
"#cdata-section": "ntttsomething somethingnttt"
}
}
}
}
~~~
despite code:
if (req.ContentType.IndexOf(@"/json", 0, System.StringComparison.OrdinalIgnoreCase) == -1)
{
return new BadRequestObjectResult(@"Content-Type header must be a JSON content type");
}
var json = await req.ReadAsStringAsync();
XmlDocument doc = JsonConvert.DeserializeXmlNode(json);
return new OkObjectResult(doc);
ContentResult, however, did exactly what I needed. Blog post updated.
Incidentally I filed this Stack Overflow question as to why you still get JSON returned after putting an XmlDocument into an OkObjectResult and requesting XML via the Accept header, but no useful answers yet:
https://stackoverflow.com/questions/50537809/asp-net-core-okobjectresult-with-xmldocument-is-ignoring-accept-header
At this point I'm going to close the issue because AFAIK there is no functions issue here. ContentResult is the appropriate type to use and the rest of the questions are about quirks in how ASP.NET core works.
sounds good. thanks for the xpost to SO
Turns out in ASP.NET core, the key step is to add XmlSerializer formatters:
public void ConfigureServices(IServiceCollection services)
{
services
.AddMvc()
.AddXmlSerializerFormatters();
}
If functions did the same (or a functions user could do it themselves), then the code snippets above using XmlDocument would probably work.
yeah I had a feeling there might be some Startup()-like MVC stuff that handles it in .Net Core land, but alas we don't get to see that in Functions world.
Most helpful comment
@brandonh-msft The easiest way (if possible) is to rely on content negotation - for example:
```
GET http://localhost:7071/api/Function1 HTTP/1.1
User-Agent: Fiddler
Host: localhost:7071
Accept: application/json
HTTP/1.1 200 OK
Date: Fri, 25 May 2018 21:25:50 GMT
Content-Type: application/json; charset=utf-8
Server: Kestrel
Transfer-Encoding: chunked
{"Property1":"Foo","Property2":3}
GET http://localhost:7071/api/Function1 HTTP/1.1
User-Agent: Fiddler
Host: localhost:7071
Accept: application/xml
HTTP/1.1 200 OK
Date: Fri, 25 May 2018 21:25:59 GMT
Content-Type: application/xml; charset=utf-8
Server: Kestrel
Content-Length: 181
I think the behavior you get when you put xml into a string and then put that string into an OkObjectResult is expected because OkObjectResult will serialize whatever value you pass it (so you're serializing a string as a string). This is the type of behavior I imagine engineers and PMs arguing about in a room. Its technically correct and probably never what you want 🤷♂️