Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Microsoft-Flow

Creating an XML JSON Converter in Azure Functions

0.00/5 (No votes)
29 May 2018CPOL3 min read 12.8K  
A walkthrough of creating an Azure Function to convert XML to JSON and vice-versa, including pitfalls and gotchas of returning XML from Azure Functions..

Your first thought is probably “but why?” and that’s fair. So let me explain.

I’m a heavy user of Microsoft Flow and Azure Logic Apps, and both of those offerings have really good build in support for JSON, but not for XML. In fact, you can’t even really parse XML into objects you can then reference in either of these. So, enter my desire to want to convert XML to JSON so I can pass it to the Parse JSON step of these and use it later on. Some endpoints I want to query only give me back XML. So here I am.

That said, doing this with Azure Functions wasn’t as straightforward as I had hoped so here I am sharing with you, dear reader. Let’s get started.

Create an HTTP Trigger Azure Function project in Visual Studio:

Image 1

Image 2

I recommend sticking with Function access rights (vs Anonymous) for this one because it’d be an easy Function to abuse should anybody discover the URL.

Once you’ve got that, here’s the content to use to create two functions, one to convert JSON to XML and another to convert XML to JSON:

C#
[FunctionName("ConvertToJson")]
public static IActionResult RunToJson([HttpTrigger
(AuthorizationLevel.Function, "post", Route = null)]HttpRequest req, TraceWriter log)
{
    if (req.ContentType.IndexOf(@"/xml", 0, System.StringComparison.OrdinalIgnoreCase) == -1)
    {
        return new BadRequestObjectResult(@"Content-Type header must be an XML content type");
    }

    XmlDocument doc = new XmlDocument();
    doc.Load(req.Body);

    return new OkObjectResult(doc);
}

[FunctionName("ConvertToXml")]
public static async Task<HttpResponseMessage> RunToXmlAsync([HttpTrigger
(AuthorizationLevel.Function, "post", Route = null)]HttpRequest req, TraceWriter log)
{
    if (req.ContentType.IndexOf(@"/json", 0, System.StringComparison.OrdinalIgnoreCase) == -1)
    {
        return new HttpResponseMessage(System.Net.HttpStatusCode.BadRequest)
        {
            Content = new StringContent(@"Content-Type header must be a JSON content type")
        };
    }

    var json = await req.ReadAsStringAsync();
    XmlDocument doc = JsonConvert.DeserializeXmlNode(json);

    StringBuilder output = new StringBuilder();
    using (var sw = new StringWriter(output))
        doc.WriteTo(new XmlTextWriter(sw));

    return new HttpResponseMessage(System.Net.HttpStatusCode.OK)
    {
        Content = new StringContent(output.ToString(), Encoding.Default, @"application/xml"),
    };
}

Dissecting ConvertToJson

As a set up for later, let’s check out how simple it was to get XML -> JSON.
Let’s review some of the gymnastics and how I arrived at the XML -> JSON code shown above:

C#
if (req.ContentType.IndexOf(@"/xml", 0, System.StringComparison.OrdinalIgnoreCase) == -1)
{
    return new BadRequestObjectResult(@"Content-Type header must be an XML content type");
}

XmlDocument doc = new XmlDocument();
doc.Load(req.Body);

return new OkObjectResult(doc);

Here was my test request in Postman:

POST /api/ConvertToJson HTTP/1.1
Host: localhost:7071
Content-Type: application/xml
Cache-Control: no-cache
Postman-Token: a5dc4ca4-b6dd-4193-b590-d15982219da7

<root>
    <this att="x">
        <underthis>val</underthis>
    </this>
    <that>
        <withval>x</withval>
        <bigval>
            <![CDATA[
            something something
            ]]>
        </bigval>
    </that>
</root>

And here’s what you get back:

C#
Content-Type →application/json; charset=utf-8
Date →Fri, 25 May 2018 18:01:16 GMT
Server →Kestrel
Transfer-Encoding →chunked

{
    "root": {
        "this": {
            "@att": "x",
            "underthis": "val"
        },
        "that": {
            "withval": "x",
            "bigval": {
                "#cdata-section": "\n\t\t\tsomething something\n\t\t\t"
            }
        }
    }
}

Because Functions automatically take any object given to OkObjectResult and runs it through JSON deserialization, simply giving it the XmlDocument resulting from LoadXml gives us exactly what we want!

But this comes with some baggage…

Dissecting ConvertToXml

This one was even more bizarre.

C#
if (req.ContentType.IndexOf(@"/json", 0, System.StringComparison.OrdinalIgnoreCase) == -1)
{
    return new HttpResponseMessage(System.Net.HttpStatusCode.BadRequest)
    {
        Content = new StringContent(@"Content-Type header must be a JSON content type")
    };
}

var json = await req.ReadAsStringAsync();
XmlDocument doc = JsonConvert.DeserializeXmlNode(json);

StringBuilder output = new StringBuilder();
using (var sw = new StringWriter(output))
    doc.WriteTo(new XmlTextWriter(sw));

return new HttpResponseMessage(System.Net.HttpStatusCode.OK)
{
    Content = new StringContent(output.ToString(), Encoding.Default, @"application/xml"),
};

The general approach to getting JSON into XML is to take it and just serialize it to an XmlNode using Newtonsoft.Json’s constructs. No biggie there, I did that.

For starters, here’s the Postman request we’re going to be sending to ConvertToXml:

POST /api/ConvertToXml HTTP/1.1
Host: localhost:7071
Content-Type: application/json
Cache-Control: no-cache
Postman-Token: 7e55a73f-1d94-46b2-b93f-e7d1297c0c30

{
    "root": {
        "this": {
            "@att": "x",
            "underthis": "val"
        },
        "that": {
            "withval": "x",
            "bigval": {
                "#cdata-section": "\n\t\t\tsomething something\n\t\t\t"
            }
        }
    }
}

So now let’s investigate why we can’t just take the resulting XmlDocument object and write it out to the OkObjectResult.

The first thing we have to change to experiment here is the return value of ConvertToXml. You’ll notice it’s set to HttpResponseMessage which is the type used, typically, in a v1 Azure Function, not a v2. More on that later, but change it back to IActionResult so the signature now looks like:

C#
public static async Task<IActionResult> RunToXmlAsync([HttpTrigger
(AuthorizationLevel.Function, "post", Route = null)]HttpRequest req, TraceWriter log)

Now, change the body to this:

C#
if (req.ContentType.IndexOf(@"/json", 0, System.StringComparison.OrdinalIgnoreCase) == -1)
{
    return new BadRequestObjectResult(@"Content-Type header must be an JSON content type");
}

var json = await req.ReadAsStringAsync();
XmlDocument doc = JsonConvert.DeserializeXmlNode(json);

return new OkObjectResult(doc);

and give ‘er a go, only to see JSON come out:

C#
Content-Type →application/json; charset=utf-8
Date →Fri, 25 May 2018 17:44:59 GMT
Server →Kestrel
Transfer-Encoding →chunked

{
    "root": {
        "this": {
            "@att": "x",
            "underthis": "val"
        },
        "that": {
            "withval": "x",
            "bigval": {
                "#cdata-section": "\n\t\t\tsomething something\n\t\t\t"
            }
        }
    }
}

At this point, I tried adding [Produces(@"application/xml")] to the signature of my Function but it had no effect whatsoever. Functions must not respect those ASP.NET Core attributes, unfortunately (yet?).

Let’s try forcing the output to be XML using the MediaTypeFormatters collection again:

C#
return new OkObjectResult(doc) 
{ ContentTypes = new Microsoft.AspNetCore.Mvc.Formatters.MediaTypeCollection { @"application/xml" } };

Nada. This time, we get HTTP 406 NOT ACCEPTABLE as the output of our Function.

OK. Let’s take the XML document, write it to a string, and spit that out.

C#
XmlDocument doc = JsonConvert.DeserializeXmlNode(json);

StringBuilder sb = new StringBuilder();
using (var sw = new StringWriter(sb))
    doc.WriteTo(new XmlTextWriter(sw));

return new OkObjectResult(sb.ToString());

Gives us:

Content-Type →text/plain; charset=utf-8
Date →Fri, 25 May 2018 17:51:16 GMT
Server →Kestrel
Transfer-Encoding →chunked

<root><this att="x"><underthis>val</underthis></this><that><withval>x</withval><bigval><![CDATA[
            something something
            ]]></bigval></that></root>

Close! But I really want that Content-Type header to be accurate, darnit!

Let’s add our MediaTypeFormatter back in:

C#
return new OkObjectResult(sb.ToString()) 
{ ContentTypes = new Microsoft.AspNetCore.Mvc.Formatters.MediaTypeCollection { @"application/xml" } };

Giving us…..

Content-Type →application/xml; charset=utf-8
Date →Fri, 25 May 2018 17:52:58 GMT
Server →Kestrel
Transfer-Encoding →chunked

<string xmlns="http://schemas.microsoft.com/2003/10/Serialization/">&lt;root&gt;
        &lt;this att="x"&gt;&lt;underthis&gt;val&lt;/underthis&gt;&lt;/this&gt;&lt;that&gt;
        &lt;withval&gt;x&lt;/withval&gt;&lt;bigval&gt;&lt;![CDATA[
            something something
            ]]&gt;&lt;/bigval&gt;&lt;/that&gt;&lt;/root&gt;</string>

DAG NABBIT! (not even close to the actual words I was uttering at this point)

Clearly ASP.NET Core and/or Functions is doing something automatic under the covers that I’m just not able to control. I knew ASP.NET MVC handles this kind of stuff beautifully; maybe .NET Core is just pushing us all to a JSON-only world? ¯_(ツ)_/¯

As a last-ditch effort, I converted this Function to use the ASP.NET MVC constructs for response messages. Starting with the signature:

C#
public static async Task<HttpResponseMessage> RunToXmlAsync
([HttpTrigger(AuthorizationLevel.Function, "post", Route = null)]HttpRequest req, TraceWriter log)

Then each response I was sending back:

C#
return new HttpResponseMessage(System.Net.HttpStatusCode.BadRequest)
{
    Content = new StringContent(@"Content-Type header must be a JSON content type")
};
C#
return new HttpResponseMessage(System.Net.HttpStatusCode.OK)
{
    Content = new StringContent(output.ToString(), Encoding.Default, @"application/xml"),
};

and wouldn’t you know…

Content-Length →149
Content-Type →application/xml; charset=utf-8
Date →Fri, 25 May 2018 17:57:08 GMT
Server →Kestrel

<root>
    <this att="x">
        <underthis>val</underthis>
    </this>
    <that>
        <withval>x</withval>
        <bigval>
            <![CDATA[
            something something
            ]]>
        </bigval>
    </that>
</root>

TADA! We’ve got a body in XML, and we’ve got the header set to indicate it. Perfect!

Having one function using the ASP.NET Core constructs (ConvertToJson) and one using ASP.NET MVC constructs doesn’t seem to hurt matters at all, either.

Enjoy! And hey, if you’re an ASP.NET Core expert and see what I might be doing wrong in trying to get the desired headers & output for my Functions, please let me know in the comments!

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)