ASP Core,XML 反序列化,忽略 DTD DOCTYPE 标签(WorldPay 通知)

Posted

技术标签:

【中文标题】ASP Core,XML 反序列化,忽略 DTD DOCTYPE 标签(WorldPay 通知)【英文标题】:ASP Core, XML deserialization, ignore DTD DOCTYPE tag (WorldPay Notification) 【发布时间】:2021-08-20 04:02:48 【问题描述】:

我正在尝试在 .NET Core 中编写一个 Web API 控制器来接收 Worldpay 通知。

我已经使用 XmlSerializerInputFormatter 完成了 AddMvc,它正在愉快地读取其有效负载的“调整”版本。

        services.AddMvc(config =>
        
            config.InputFormatters.Add(new XmlSerializerInputFormatter(config));
        );

我的测试控制器

    [HttpPost]
    [Route("SendDocument")]
    [Consumes("application/xml")]
    public ActionResult SendDocument([FromBody] paymentService payment)
    
        return Ok();
    

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE paymentService PUBLIC "-//Worldpay//DTD Worldpay PaymentService v1//EN"
  "http://dtd.worldpay.com/paymentService_v1.dtd">
<paymentService version="1.4" merchantCode="Your_merchant_code">
<notify>
<orderStatusEvent orderCode="ExampleOrder1"> 
      <payment>
       <paymentMethod>ECMC-SSL</paymentMethod>
          <amount value="2400" currencyCode="EUR" exponent="2" debitCreditIndicator="credit"/>
        <lastEvent>AUTHORISED</lastEvent>       
        <AuthorisationId id="622206"/>
        <balance accountType="IN_PROCESS_AUTHORISED">
          <amount value="2400" currencyCode="EUR" exponent="2" debitCreditIndicator="credit"/>
        </balance>
        <cardNumber>5255********2490</cardNumber>
        <riskScore value="0"/>
      </payment>
</orderStatusEvent>
</notify>
</paymentService>

但是,它们在开始时包含一个 DOCTYPE 标记,如果我将其添加回有效负载中,它会失败。没有 DOCTYPE,它可以完美运行

我已经进行了大量搜索并查看了有关忽略 DTD 等的信息,但我不知道如何使用 InputFormatter 来做到这一点。

如何让 InputFormatter 忽略文件中的 DTD DOCTYPE 标记?

【问题讨论】:

也许您可以尝试使用jdweng 在link 中提到的自定义XmlSerializerInputFormatter。 【参考方案1】:

经过一番折腾,我得到了一些可能对其他人有用的东西

所以我的 ConfigureServices 包含

        services.AddMvc(config =>
        
            config.InputFormatters.Insert(0, new RawXmlRequestBodyFormatter());
        );

我创建了一个原始格式化程序,它作为字符串通过 XML 并删除了任何以 /p> 开头的行

public class RawXmlRequestBodyFormatter : InputFormatter

    public RawXmlRequestBodyFormatter()
    
        SupportedMediaTypes.Add(new Microsoft.Net.Http.Headers.MediaTypeHeaderValue("application/xml"));
        SupportedMediaTypes.Add(new Microsoft.Net.Http.Headers.MediaTypeHeaderValue("text/xml"));
    


    /// <summary>
    /// Allow text/xml, application/xml only
    /// </summary>
    /// <param name="context"></param>
    /// <returns></returns>
    public override Boolean CanRead(InputFormatterContext context)
    
        if (context == null) throw new ArgumentNullException(nameof(context));

        var contentType = context.HttpContext.Request.ContentType;

        return (contentType == "text/xml" || contentType == "application/xml");
    

    /// <summary>
    /// Handle xml results
    /// </summary>
    /// <param name="context"></param>
    /// <returns></returns>
    public override async Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context) 
    
        var contentType = context.HttpContext.Request.ContentType;
        var httpContext = context.HttpContext;

        if (contentType == "application/xml" || contentType == "text/xml")
        
            using var reader = new StreamReader(httpContext.Request.Body, Encoding.UTF8);
            StringBuilder xml = new StringBuilder();

            try
            
                while(true)
                
                    var Line = await ReadLineAsync(reader, context);

                    if (Line != null)
                        xml.AppendLine(Line);
                    else
                        break;
                
            
            catch
            
                return await InputFormatterResult.FailureAsync();
            

            return await InputFormatterResult.SuccessAsync(xml.ToString());
        

        return await InputFormatterResult.FailureAsync();
    

    /// <summary>
    /// Read next line, Skip any lines starting with DOCTYPE
    /// </summary>
    /// <param name="reader"></param>
    /// <param name="context"></param>
    /// <returns></returns>
    private async Task<string> ReadLineAsync(StreamReader reader, InputFormatterContext context)
    
        string line = null;

        do
        
            line = await reader.ReadLineAsync();

         while (line != null && line.StartsWith("<!DOCTYPE"));

        return line;
    

在我的控制器中,我像这样使用它。我获取 RAW XML 字符串,将其反序列化为我编写的 paymentService 类,然后以 JSON 格式返回它,以便您可以看到它在 POSTMAN 中工作

        [HttpPost]
    [Route("SendDocument")]
    [Consumes("application/xml")]
    public ActionResult SendDocument([FromBody] string raw)
    
        try
        
            XmlSerializer serializer = new XmlSerializer(typeof(paymentService), new XmlRootAttribute("paymentService"));
            StringReader stringReader = new StringReader(raw);

            var o = (paymentService)serializer.Deserialize(stringReader);

            return Ok(o);
        
        catch (Exception ex)
        
        

        return Ok();
    

我的 paymentService 类是这些,为了完整性而提供的

public class amount

    public amount()
    
    

    [XmlAttribute]
    public string value  get; set; 
    [XmlAttribute]
    public string currencyCode  get; set; 
    [XmlAttribute]
    public string exponent  get; set; 
    [XmlAttribute]
    public string debitCreditIndicator  get; set; 


public class balance

    public balance()
    
    

    [XmlAttribute]
    public string accountType  get; set; 

    [XmlElement]
    public amount amount  get; set; 


public class riskScore

    public riskScore()
    
    

    [XmlAttribute]
    public string value  get; set; 


public class authorisationId

    public authorisationId()
    
    

    [XmlAttribute]
    public string id  get; set; 


public class payment

    public payment()
    
    


    [XmlElement]
    public string paymentMethod  get; set; 

    [XmlElement]
    public amount amount  get; set; 

    [XmlElement]
    public string lastEvent  get; set; 

    [XmlElement]
    public authorisationId AuthorisationId  get; set; 

    [XmlElement]
    public balance balance  get; set; 

    [XmlElement]
    public string cardNumber  get; set; 

    [XmlElement]
    public riskScore riskScore  get; set; 


public class orderStatusEvent

    public orderStatusEvent()
    
    

    [XmlAttribute]
    public string orderCode  get; set; 

    [XmlElement]
    public payment payment  get; set; 


public class notify

    public notify()
    
    

    [XmlElement]
    public orderStatusEvent orderStatusEvent  get; set; 


[Serializable]
public class paymentService


    public paymentService()
    
    

    [XmlAttribute]
    public string version  get; set; 
    [XmlAttribute]
    public string merchantCode  get; set; 

    [XmlElement]
    public notify notify  get; set; 


我希望这可以节省一些时间。 WorldPay 在提供信息方面毫无用处,而且要达到这一点需要数周时间。

【讨论】:

以上是关于ASP Core,XML 反序列化,忽略 DTD DOCTYPE 标签(WorldPay 通知)的主要内容,如果未能解决你的问题,请参考以下文章

ASP.NET Core中如何使用缓存

ASP.Net Core 中的 JSON 序列化/反序列化

在 xml 序列化期间忽略属性,但在反序列化期间不忽略

XML 反序列化忽略不按字母顺序排列的属性

反序列化 xml 时忽略未知类型

C# 在忽略命名空间的同时反序列化 xml