我有一个.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中,并且我无法对它进行反序列化。
任何帮助都将不胜感激。
答案 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)
Request.Body
,因此最好启用倒带功能。InputFormatter
显得过分杀伤力。 InputFormatter
关心内容协商。通常,我们以这种方式使用它:如果客户端发送application/json
的有效负载,则应该执行A;如果客户端发送application/xml
的有效负载,则应该执行B。但是您的客户端(旧版系统)仅发送x-www-form-urlencoded
。除了创建InputFormatter
之外,您还可以创建一个简单的ModelBinder
来反序列化有效负载。.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; }
}
}
请注意,名称空间也应保持不变。
详细信息。
创建一个为Filter
启用Rewind
的{{1}}
Request.Body
现在您可以创建一个public class EnableRewindResourceFilterAttribute : Attribute, IResourceFilter
{
public void OnResourceExecuted(ResourceExecutedContext context) { }
public void OnResourceExecuting(ResourceExecutingContext context)
{
context.HttpContext.Request.EnableRewind();
}
}
:
ModelBinder
最后,用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
演示:
[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对象。
但是要小心表演!