在 Azure Functions 中创建 XML JSON 转换器
创建一个 Azure 函数以将 XML 转换为 JSON 以及反之亦然的演练,包括从 Azure Functions 返回 XML 的陷阱和注意事项。
您的第一个想法可能是“但是为什么?”,这很合理。所以让我解释一下。
我是 Microsoft Flow 和 Azure Logic Apps 的重度用户,这两个产品都内置了非常好的 JSON 支持,但对 XML 的支持却不然。事实上,您甚至无法真正将 XML 解析为可以在这两个产品中引用的对象。因此,我希望将 XML 转换为 JSON,以便我可以将其传递到这些产品的 解析 JSON 步骤并在以后使用它。我想要查询的一些端点只返回 XML。所以我就在这里了。
也就是说,使用 Azure Functions 执行此操作并不像我希望的那样简单,所以我在这里与您分享,亲爱的读者。让我们开始吧。
在 Visual Studio 中创建一个 HTTP 触发器 Azure 函数项目
我建议坚持使用 Function
访问权限(而不是匿名),因为如果有人发现 URL,它很容易被滥用。
一旦你有了它,这里的内容可以用来创建两个函数,一个用于将 JSON 转换为 XML,另一个用于将 XML 转换为 JSON
[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"),
};
}
剖析 ConvertToJson
作为稍后的设置,让我们看看获取 XML -> JSON 有多简单。
让我们回顾一下一些技巧以及我是如何得出上面显示的 XML -> JSON 代码的
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);
这是我在 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>
这是你得到的回复
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"
}
}
}
}
因为 Functions 会自动获取任何赋予 OkObjectResult
的 object
并在 JSON 反序列化中运行它,所以只需将从 LoadXml
生成的 XmlDocument
赋予它,就可以得到我们想要的!
但这带来了一些负担…
剖析 ConvertToXml
这一个甚至更奇怪。
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"),
};
将 JSON 转换为 XML 的一般方法是获取它并使用 Newtonsoft.Json 的构造将其序列化为 XmlNode
。没什么大不了的,我做到了。
首先,这是我们将要发送到 ConvertToXml
的 Postman 请求
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"
}
}
}
}
所以现在让我们调查一下为什么我们不能只获取生成的 XmlDocument
对象并将其写入 OkObjectResult
。
我们必须在此处更改的第一个实验是 ConvertToXml
的返回值。您会注意到它设置为 HttpResponseMessage
,这通常是在 v1 Azure 函数中使用的类型,而不是 v2。稍后会详细介绍,但将其更改回 IActionResult
,以便签名现在看起来像
public static async Task<IActionResult> RunToXmlAsync([HttpTrigger
(AuthorizationLevel.Function, "post", Route = null)]HttpRequest req, TraceWriter log)
现在,将主体更改为以下内容
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);
然后试一试,结果却看到 JSON 出来了
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"
}
}
}
}
此时,我尝试在我的函数的签名中添加 [Produces(@"application/xml")]
,但它没有任何效果。不幸的是,Functions 一定不尊重那些 ASP.NET Core 属性(尚未?)。
让我们尝试再次使用 MediaTypeFormatters
集合强制输出为 XML
return new OkObjectResult(doc)
{ ContentTypes = new Microsoft.AspNetCore.Mvc.Formatters.MediaTypeCollection { @"application/xml" } };
没有。这一次,我们得到了 HTTP 406 NOT ACCEPTABLE
作为我们函数的输出。
好的。让我们获取 XML 文档,将其写入一个 string
,然后将其输出。
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());
得到
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>
快了!但我真的希望 Content-Type
标头是准确的,该死!
让我们将我们的 MediaTypeFormatter
添加回来
return new OkObjectResult(sb.ToString())
{ ContentTypes = new Microsoft.AspNetCore.Mvc.Formatters.MediaTypeCollection { @"application/xml" } };
给我们……
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/"><root>
<this att="x"><underthis>val</underthis></this><that>
<withval>x</withval><bigval><![CDATA[
something something
]]></bigval></that></root></string>
DAG NABBIT! (甚至与我此时发出的实际单词都不接近)
显然,ASP.NET Core 和/或 Functions 正在进行一些自动操作,但我无法控制。我知道 ASP.NET MVC 很好地处理了这类事情;也许 .NET Core 只是将我们所有人推向一个只有 JSON 的世界? ¯_(ツ)_/¯
作为最后的努力,我将此函数转换为使用 ASP.NET MVC 构造来响应消息。从签名开始
public static async Task<HttpResponseMessage> RunToXmlAsync
([HttpTrigger(AuthorizationLevel.Function, "post", Route = null)]HttpRequest req, TraceWriter log)
然后是我发回的每个响应
return new HttpResponseMessage(System.Net.HttpStatusCode.BadRequest)
{
Content = new StringContent(@"Content-Type header must be a JSON content type")
};
return new HttpResponseMessage(System.Net.HttpStatusCode.OK)
{
Content = new StringContent(output.ToString(), Encoding.Default, @"application/xml"),
};
你不会知道的……
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! 我们有一个 XML 格式的主体,并且我们已将标头设置为指示它。完美!
拥有一个使用 ASP.NET Core 构造 (ConvertToJson
) 的函数和一个使用 ASP.NET MVC 构造的函数似乎根本没有任何影响。
尽情享受吧!嘿,如果你是 ASP.NET Core 专家,并且看到我在尝试为我的函数获取所需的标头和输出时可能做错了什么,请在评论中告诉我!