在到达操作方法之前,对Request.Body进行修整,以进行XML模型绑定

时间:2018-12-12 23:21:37

标签: c# asp.net-core asp.net-core-mvc

我有一个以太网到1-Wire接口,该接口定期发送带有传感器数据的HTTP帖子。数据主体使用XML,但它不是完全有效的XML。我无法更改HTTP正文,因为它位于嵌入式软件中。完整的请求正文如下所示:

 ------------------------------3cbec9ce8f05
 Content-Disposition: form-data; name="ServerData"; filename="details.xml"
 Content-Type: text/plain

 <?xml version="1.0" encoding="UTF-8"?>
 <Devices-Detail-Response xmlns="http://www.example.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
 <PollCount>2739</PollCount>
 <DevicesConnected>1</DevicesConnected>
 <LoopTime>1.022</LoopTime>
 <DevicesConnectedChannel1>0</DevicesConnectedChannel1>
 <DevicesConnectedChannel2>0</DevicesConnectedChannel2>
 <DevicesConnectedChannel3>1</DevicesConnectedChannel3>
 <DataErrorsChannel1>0</DataErrorsChannel1>
 <DataErrorsChannel2>0</DataErrorsChannel2>
 <DataErrorsChannel3>0</DataErrorsChannel3>
 <VoltageChannel1>4.91</VoltageChannel1>
 <VoltageChannel2>4.92</VoltageChannel2>
 <VoltageChannel3>4.92</VoltageChannel3>
 <VoltagePower>5.16</VoltagePower>
 <DeviceName>Unit 3 OW2</DeviceName>
 <HostName>EDSOWSERVER2</HostName>
 <MACAddress>00:00:00:00:00:00</MACAddress>
 <DateTime>2018-12-12 16:44:48</DateTime>
 <owd_DS18B20 Description="Programmable resolution thermometer">
 <Name>DS18B20</Name>
 <Family>28</Family>
 <ROMId>F70000024D85E528</ROMId>
 <Health>7</Health>
 <Channel>3</Channel>
 <RawData>C6004B467FFF0A102A00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000</RawData>
 <PrimaryValue>12.3750 Deg C</PrimaryValue>
 <Temperature Units="Centigrade">12.3750</Temperature>
 <UserByte1 Writable="True">75</UserByte1>
 <UserByte2 Writable="True">70</UserByte2>
 <Resolution>12</Resolution>
 <PowerSource>0</PowerSource>
 </owd_DS18B20>
 </Devices-Detail-Response>

 ------------------------------3cbec9ce8f05--

因此,我尝试先删除“ --------...”和Content-Type,最后删除“ ------- ..”,然后再执行操作方法。

这是我的控制器:

 using Microsoft.AspNetCore.Mvc;
 using Microsoft.AspNetCore.Mvc.Filters;
 using Microsoft.Extensions.Logging;
 using Monitor.Models;
 using System;
 using System.IO;

 namespace Monitor.Controllers
 {
     [ApiController]
     [Route("api/[controller]")]
     public class SensorController : Controller
     {
         private readonly ILogger _log;

         public SensorController(ILogger<SensorController> logger)
         {
             _log = logger;
         }

         [HttpPost]
         [OwServer]
         public IActionResult Post([FromBody] Ow_ServerModel model)
         {
             return Ok("Working");
         }
     }

     public class OwServer : Attribute, IResourceFilter
     {
         public void OnResourceExecuting(ResourceExecutingContext context)
         {
             context.HttpContext.Request.Headers["Content-Type"] = "application/xml";

             using (StreamReader stream = new StreamReader(context.HttpContext.Request.Body))
             {
                 string body = stream.ReadToEnd();

                 int start = body.IndexOf('<');
                 int last = body.LastIndexOf('>') + 1;

                 string parsedBody = body.Substring(start, (last - start));


                 // context.HttpContext.Request.Body = 
             }

         }

         public void OnResourceExecuted(ResourceExecutedContext context)
         {
         }
     }
 }


 using System;
 using System.Xml.Serialization;

 namespace Monitor.Models
 {
     [Serializable]
     [XmlRoot("Devices-Detail-Response", Namespace = "http://www.example.com")]
     public class Ow_ServerModel
     {
         public int PollCount { get; set; }
     }
 }

2 个答案:

答案 0 :(得分:1)

您正尝试使用ActionFilter做到这一点,我建议使用自定义的活页夹,并使用正则表达式从请求中提取de xml。

在WebApiConfig.cs中注册新的自定义xml绑定器

public static void Register(HttpConfiguration config)
{
    config.Services.Insert(typeof(ModelBinderProvider), 0,
    new SimpleModelBinderProvider(typeof(XDocument), new XmlCustomBinder()));
}

创建一个将获取内容主体并仅提取xml的自定义活页夹

public class XmlCustomBinder : IModelBinder
{
    public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
    {
        try
        {
            var parsedXml = actionContext.Request.Content.ReadAsStringAsync().Result;
            Regex regex = new Regex(@"<\?xml.*>", RegexOptions.Singleline);
            Match match = regex.Match(parsedXml);
            if (!match.Success) return false;
            parsedXml = match.Groups[0].Value;
            TextReader textReader = new StringReader(parsedXml);
            XDocument xDocument = XDocument.Load(textReader);
            bindingContext.Model = xDocument;
            return true;
        }
        catch(Exception ex)
        {
            bindingContext.ModelState.AddModelError("XmlCustomBinder", ex);
            return false;
        }
    }
}

控制器代码,获取XDocument(XML)值

[HttpPost]
[OwServer]
public IActionResult Post([ModelBinder(typeof(XmlCustomBinder))] XDocument xDocument)
{
     return Ok("Working");
}

https://docs.microsoft.com/en-us/dotnet/api/system.xml.linq.xdocument?view=netframework-4.7.2

答案 1 :(得分:1)

请求正文指示嵌入式软件正在发布多部分数据。而以下content-disposition表示它正在发送details.xml的文件:

------------------------------3cbec9ce8f05
Content-Disposition: form-data; name="ServerData"; filename="details.xml"
Content-Type: text/plain

因此,您无需手动删除'------------------------------ 3cbec9ce8f05'和{ {1}}。 只需使用Content-Type=...

此外,根据@ivan-valadares的建议,您可以使用Model活页夹抬起沉重的东西。但是似乎他将所有请求正文都视为字符串,然后构造了Request.Form.Files。一种更优雅的方法是使用XDocument创建一个强类型的对象。另外,IModelBinder Interface没有方法XmlSerializer。我们应该改用public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)

因此,如下所示创建模型联编程序:

BindModelAsync(ModelBindingContext bindingContext)

现在您可以在Action方法中使用它:

public class EmbededServerDataBinder<T> : IModelBinder
{
    public Task BindModelAsync(ModelBindingContext bindingContext)
    {
        if (bindingContext == null) { throw new ArgumentNullException(nameof(bindingContext)); }
        var modelName = bindingContext.BinderModelName ?? "ServerData";

        XmlSerializer serializer = new XmlSerializer(typeof(T));
        var data= bindingContext.HttpContext.Request.Form.Files[modelName];
        if(data == null){ 
            bindingContext.ModelState.AddModelError(modelName,"invalid error");
            return Task.CompletedTask;
        }
        using(var stream = data.OpenReadStream()){
            var o = serializer.Deserialize(stream);
            bindingContext.Result = ModelBindingResult.Success(o);
        }
        return Task.CompletedTask;
    }
}

请注意 [HttpPost] public IActionResult Post([ModelBinder(typeof(EmbededServerDataBinder<Ow_ServerModel>))] Ow_ServerModel ServerData) { return Ok("Working"); } 的名称很重要。模型绑定器将在content-disposition中寻找该名称。

我用您的有效负载对其进行了测试,并且它对我来说符合预期:

enter image description here