У меня есть веб-API .Net Core (2.1), который должен адаптироваться к существующей системе .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; }
}
Согласно этой статье: Принятие необработанного содержимого тела запроса в контроллерах API ASP.NET Core
Я создал собственный 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, и я не могу десериализовать их Form.
Любая помощь очень ценится.
@itminus Необходимо преобразовать в объект, который передан HTTP-запросом, который выполняется завершенным клиентом (старая система .Net framework), который я не могу изменить.





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 класс Legacy системы:
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 для 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;
}
}
Наконец, украсьте свой метод действия 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);
}
}
и настраиваем сервисы:
services.AddMvc(o => {
o.InputFormatters.Insert(0, new RawRequestBodyFormatter());
});
Кроме того, вы должны десериализовать объект person таким же образом, как мы это делаем в Model Binder.
Но будьте осторожны с исполнением!
Я знаю, что есть уже принятый ответ, но я придумал способ разобрать запрос. Сформировать данные и перестроить содержимое в исходный запрос. Формат тела:
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();
}
Интересно, а зачем читать необработанный
byte[]?