Принять x-www-form-urlencoded в Asp .net core Web Api - PullRequest
0 голосов
/ 18 декабря 2018

У меня есть веб-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;
    }
}

Класс Person:

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

    public int Age { get; set; }
}

Согласно этой статье: Принятие необработанного содержимого тела запроса в контроллерах API ядра ASP.NET

Я создал собственный 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), были не в запросе. Но в запросе. Форма, и я не могу десериализовать их. Форма.

Любая помощь очень ценится.

Ответы [ 2 ]

0 голосов
/ 10 августа 2019

Я знаю, что ответ уже принят, но я нашел способ синтаксического анализа запроса. Формирование данных и перестройка содержимого в исходный запрос. Формат в формате:

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();
}
0 голосов
/ 18 декабря 2018
  1. Поскольку вам необходимо прочитать необработанный Request.Body, лучше включить функцию перемотки.
  2. InputFormatter для этого сценария излишне.InputFormatter заботится о содержании переговоров.Как правило, мы используем это следующим образом: если клиент отправляет полезную нагрузку application/json, мы должны сделать A;если клиент отправляет полезную нагрузку application/xml, мы должны сделать B.Но ваш клиент (устаревшая система) отправляет только x-www-form-urlencoded.Вместо создания InputFormatter вы можете создать простой простой ModelBinder для десериализации полезной нагрузки.
  3. Взлом: Ваша устаревшая система .Net framework(4.6.2) использует BinaryFormatter для сериализации класса Person и ваш *Веб-сайт 1019 * должен десериализовать его до объекта Person.Как правило, для этого требуется, чтобы ваше приложение .NET Core и система Legacy .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; }
    }
}

Обратите внимание, что пространство имен также должно оставаться прежним.

Как в деталях.

  1. Создать Filter, который включает Rewind для Request.Body

    public class EnableRewindResourceFilterAttribute : Attribute, IResourceFilter
    {
        public void OnResourceExecuted(ResourceExecutedContext context) { }
    
        public void OnResourceExecuting(ResourceExecutingContext context)
        {
            context.HttpContext.Request.EnableRewind();
        }
    }
    
  2. СейчасВы можете создать 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;
        }
    }
    
  3. Наконец, украсьте свой метод действия с помощью 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
        }
    }
    

демо:

enter image description 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());
});

А также, вы должны десериализоватьчеловек возражает так же, как мы делаем в Моdel Binder.

Но будьте осторожны с производительностью!

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...