接受Asp .net核心Web Api中的x-www-form-urlencoded

时间:2018-12-18 03:16:54

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

我有一个.Net Core(2.1)Web API,该API必须适应现有的.Net framework(4.6.2)系统,并且现有系统发送Api接受的请求。

这是问题所在。在.Net Framework系统中,它像这样调用api:

var request = (HttpWebRequest)WebRequest.Create("http://xxx.xxx/CloudApi/RegionsList");
request.KeepAlive = true;
request.Method = "POST";
request.ContentType = "application/x-www-form-urlencoded";
request.Accept = "*/*";

var data = new Person()
{
    Name = "Alex",
    Age = 40
};
byte[] dataBuffer;

using (MemoryStream ms = new MemoryStream())
{
    IFormatter formatter = new BinaryFormatter(); formatter.Serialize(ms, data);
    dataBuffer = ms.GetBuffer();
}

request.ContentLength = dataBuffer.Length;
Stream requestStream = request.GetRequestStream();
requestStream.Write(dataBuffer, 0, dataBuffer.Length);
requestStream.Close();

try
{
     var response = (HttpWebResponse)request.GetResponse();
     Console.WriteLine("OK");
}
catch (Exception exp)
{
     Console.WriteLine(exp.Message);
}

这是api控制器代码:

[Route("cloudapi")]
public class LegacyController : ControllerBase
{
    [HttpPost]
    [Route("regionslist")]
    public dynamic RegionsList([FromBody]byte[] value)
    {
        return value.Length;
    }
}

人员班:

[Serializable]
public class Person
{
    public string Name { get; set; }

    public int Age { get; set; }
}

根据本文:Accepting Raw Request Body Content in ASP.NET Core API Controllers

我已经制作了一个自定义的InputFormatter来处理这种情况:

public class RawRequestBodyFormatter : IInputFormatter
{
    public RawRequestBodyFormatter()
    {

    }

    public bool CanRead(InputFormatterContext context)
    {
        if (context == null) throw new ArgumentNullException("argument is Null");
        var contentType = context.HttpContext.Request.ContentType;
        if (contentType == "application/x-www-form-urlencoded")
            return true;
        return false;
    }

    public async Task<InputFormatterResult> ReadAsync(InputFormatterContext context)
    {
        var request = context.HttpContext.Request;
        var contentType = context.HttpContext.Request.ContentType;
        if (contentType == "application/x-www-form-urlencoded")
        {
            using (StreamReader reader = new StreamReader(request.Body, Encoding.UTF8))
            {
                using (var ms = new MemoryStream(2048))
                {
                    await request.Body.CopyToAsync(ms);
                    var content = ms.ToArray();

                    return await InputFormatterResult.SuccessAsync(content);
                }
            }
        }
        return await InputFormatterResult.FailureAsync();
    }
}

但是我发现我发送的数据(Person类实例)不在request.Body中,而是在request.Form中,并且我无法对它进行反序列化。

任何帮助都将不胜感激。

2 个答案:

答案 0 :(得分:2)

我知道已经接受了一个响应,但是我想出了一种解析请求的方法。表单数据并将内容重建为原始请求。正文格式:

public override async Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context)
{
    var request = context.HttpContext.Request;
    var contentType = request.ContentType;
    if (contentType.StartsWith("application/x-www-form-urlencoded")) // in case it ends with ";charset=UTF-8"
    {
        var content = string.Empty;
        foreach (var key in request.Form.Keys)
        {
            if (request.Form.TryGetValue(key, out var value))
            {
                content += $"{key}={value}&";
            }
        }
        content = content.TrimEnd('&');
        return await InputFormatterResult.SuccessAsync(content);
    }
    return await InputFormatterResult.FailureAsync();
}

答案 1 :(得分:0)

  1. 由于您需要阅读原始的Request.Body,因此最好启用倒带功能。
  2. 在这种情况下,
  3. InputFormatter显得过分杀伤力。 InputFormatter关心内容协商。通常,我们以这种方式使用它:如果客户端发送application/json的有效负载,则应该执行A;如果客户端发送application/xml的有效负载,则应该执行B。但是您的客户端(旧版系统)仅发送x-www-form-urlencoded。除了创建InputFormatter之外,您还可以创建一个简单的ModelBinder来反序列化有效负载。
  4. 黑客:您的旧式.Net framework(4.6.2)系统使用BinaryFormatter来序列化Person类,并且您的.NET Core网站需要将其反序列化为Person的对象。通常,这需要您的.NET Core应用和旧版.NET Framework系统共享相同的Person程序集。 但是很明显,原始Person的目标是.NET Framewrok 4.6.2,换句话说,.NET Core不能引用该程序集。一种变通方法是创建一个与Person共享相同名称的类型,并创建一个SerializationBinder来绑定新类型。

假设您在旧版系统的Person类中是:

namespace App.Xyz{

    [Serializable]
    public class Person
    {
        public string Name { get; set; }

        public int Age { get; set; }
    }
}

您应该在.NET Core网站中创建一个相同的类:

namespace App.Xyz{

    [Serializable]
    public class Person
    {
        public string Name { get; set; }

        public int Age { get; set; }
    }
}

请注意,名称空间也应保持不变。

详细信息。

  1. 创建一个为Filter启用Rewind的{​​{1}}

    Request.Body
  2. 现在您可以创建一个public class EnableRewindResourceFilterAttribute : Attribute, IResourceFilter { public void OnResourceExecuted(ResourceExecutedContext context) { } public void OnResourceExecuting(ResourceExecutingContext context) { context.HttpContext.Request.EnableRewind(); } }

    ModelBinder
  3. 最后,用public class BinaryBytesModelBinder: IModelBinder { internal class LegacyAssemblySerializationBinder : SerializationBinder { public override Type BindToType(string assemblyName, string typeName) { var typeToDeserialize = Assembly.GetEntryAssembly() .GetType(typeName); // we use the same typename by convention return typeToDeserialize; } } public Task BindModelAsync(ModelBindingContext bindingContext) { if (bindingContext == null) { throw new ArgumentNullException(nameof(bindingContext)); } var modelName = bindingContext.BinderModelName?? "LegacyBinaryData"; var req = bindingContext.HttpContext.Request; var raw= req.Body; if(raw == null){ bindingContext.ModelState.AddModelError(modelName,"invalid request body stream"); return Task.CompletedTask; } var formatter= new BinaryFormatter(); formatter.AssemblyFormat = System.Runtime.Serialization.Formatters.FormatterAssemblyStyle.Simple; formatter.Binder = new LegacyAssemblySerializationBinder(); var o = formatter.Deserialize(raw); bindingContext.Result = ModelBindingResult.Success(o); return Task.CompletedTask; } } 装饰您的action方法,并使用模型绑定器检索实例:

    Filter

演示:

enter image description here


替代方法:使用[Route("cloudapi")] public class LegacyController : ControllerBase { [EnableRewindResourceFilter] [HttpPost] [Route("regionslist")] public dynamic RegionsList([ModelBinder(typeof(BinaryBytesModelBinder))] Person person ) { // now we gets the person here } } (不建议)

或者,如果您确实想使用InputFormatter,则还应该启用快退:

InputFormatter

并配置服务:

[Route("cloudapi")]
public class LegacyController : ControllerBase
{
    [HttpPost]
    [EnableRewindResourceFilter]
    [Route("regionslist")]
    public dynamic RegionsList([FromBody] byte[] bytes )
    {

        return new JsonResult(bytes);
    }
}

而且,您应该以与Model Binder中相同的方式反序列化person对象。

但是要小心表演!

相关问题