Подтвердить что ты не робот

Как читать тело запроса в главном контроллере webapi asp.net?

Я пытаюсь прочитать тело запроса в методе OnActionExecuting, но я всегда получаю null для тела.

var request = context.HttpContext.Request;
var stream = new StreamReader(request.Body);
var body = stream.ReadToEnd();

Я попытался явно установить позицию потока в 0, но это также не сработало. Так как это ASP.NET CORE, все выглядит немного по-другому. Я вижу все образцы здесь, ссылаясь на старые версии webapi.
Есть ли другой способ сделать это?

4b9b3361

Ответ 1

По-видимому, мы можем использовать IHttpContextAccessor для доступа к http-контексту в контроллерах. Просто нужно ввести в класс запуска и получить его в своих контроллерах.

  services.AddScoped<IHttpContextAccessor, HttpContextAccessor>();

с помощью этого вы можете получить доступ к контексту даже в конструкторе.

Ответ 2

В ASP.Net Core кажется сложным несколько раз прочитать запрос тела, однако, если ваша первая попытка делает это правильно, вам будет хорошо при следующих попытках.

Я прочитал несколько оборотов, например, заменив поток тела, но я думаю, что следующее является самым чистым:

Наиболее важными моментами являются

  1. сообщить запросу, что вы будете читать его тело дважды или более,
  2. не закрывать поток тела, и
  3. перемотать его в исходное положение, чтобы внутренний процесс не потерялся.

[EDIT]

Как отметил Мурад, вы также можете воспользоваться преимуществами расширения .Net Core 2.1: EnableBuffering Он хранит большие запросы на диске вместо того, чтобы хранить его в памяти, избегая проблем с большими потоками, хранящихся в памяти. (файлы, изображения,...). Вы можете изменить временную папку, установив переменную среды ASPNETCORE_TEMP, и файлы будут удалены после завершения запроса.

В AuthorizationFilter вы можете сделать следующее:

// Helper to enable request stream rewinds
using Microsoft.AspNetCore.Http.Internal;
[...]
public class EnableBodyRewind : Attribute, IAuthorizationFilter
{
    public void OnAuthorization(AuthorizationFilterContext context)
    {
        var bodyStr = "";
        var req = context.HttpContext.Request;

        // Allows using several time the stream in ASP.Net Core
        req.EnableRewind(); 

        // Arguments: Stream, Encoding, detect encoding, buffer size 
        // AND, the most important: keep stream opened
        using (StreamReader reader 
                  = new StreamReader(req.Body, Encoding.UTF8, true, 1024, true))
        {
            bodyStr = reader.ReadToEnd();
        }

        // Rewind, so the core is not lost when it looks the body for the request
        req.Body.Position = 0;

        // Do whatever work with bodyStr here

    }
}



public class SomeController : Controller
{
    [HttpPost("MyRoute")]
    [EnableBodyRewind]
    public IActionResult SomeAction([FromBody]MyPostModel model )
    {
        // play the body string again
    }
}

Затем вы можете снова использовать тело в обработчике запросов.

В вашем случае, если вы получите нулевой результат, это, вероятно, означает, что тело уже было прочитано на более ранней стадии. В этом случае вам может понадобиться промежуточное программное обеспечение (см. ниже).

Однако будьте осторожны, если вы обрабатываете большие потоки, такое поведение подразумевает, что все загружено в память, это не должно запускаться в случае загрузки файла.

Вы можете использовать это в качестве промежуточного программного обеспечения

Мой выглядит так (опять же, если вы загружаете/загружаете большие файлы, это следует отключить, чтобы избежать проблем с памятью):

public sealed class BodyRewindMiddleware
{
    private readonly RequestDelegate _next;

    public BodyRewindMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task Invoke(HttpContext context)
    {
        try { context.Request.EnableRewind(); } catch { }
        await _next(context);
        // context.Request.Body.Dipose() might be added to release memory, not tested
    }
}
public static class BodyRewindExtensions
{
    public static IApplicationBuilder EnableRequestBodyRewind(this IApplicationBuilder app)
    {
        if (app == null)
        {
            throw new ArgumentNullException(nameof(app));
        }

        return app.UseMiddleware<BodyRewindMiddleware>();
    }

}

Ответ 3

Чтобы иметь возможность перемотать тело запроса, ответ @Jean помог мне придумать решение, которое, кажется, работает хорошо. В настоящее время я использую это для промежуточного программного обеспечения Global Exception Handler, но принцип тот же.

Я создал промежуточное программное обеспечение, которое в основном позволяет перемотку на тело запроса (вместо декоратора).

using Microsoft.AspNetCore.Http.Internal;
[...]
public class EnableRequestRewindMiddleware
{
    private readonly RequestDelegate _next;

    public EnableRequestRewindMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task Invoke(HttpContext context)
    {
        context.Request.EnableRewind();
        await _next(context);
    }
}

public static class EnableRequestRewindExtension
{
    public static IApplicationBuilder UseEnableRequestRewind(this IApplicationBuilder builder)
    {
        return builder.UseMiddleware<EnableRequestRewindMiddleware>();
    }
}

Это можно затем использовать в Startup.cs следующим образом:

[...]
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
    [...]
    app.UseEnableRequestRewind();
    [...]
}

Используя этот подход, я смог успешно перемотать поток тела запроса.

Ответ 4

Более четкое решение работает в ASP.Net Core 2.1.

Класс фильтра

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http.Internal;
using Microsoft.AspNetCore.Mvc.Filters;

public class ReadableBodyStreamAttribute : AuthorizeAttribute, IAuthorizationFilter
{
    public void OnAuthorization(AuthorizationFilterContext context)
    {
        context.HttpContext.Request.EnableRewind();
    }
}

В контроллере

[HttpPost]
[ReadableBodyStream]
public string SomePostMethod()
{
    using (StreamReader stream = new StreamReader(HttpContext.Request.Body))
    {
        string body = stream.ReadToEnd();
        // body = "param=somevalue&param2=someothervalue"
    }
}

Ответ 5

Метод IHttpContextAccessor работает, если вы хотите пройти этот маршрут.

TL;DR;

  • Внедрить IHttpContextAccessor

  • Перемотка назад - HttpContextAccessor.HttpContext.Request.Body.Seek(0, System.IO.SeekOrigin.Begin);

  • Чтение - System.IO.StreamReader sr = new System.IO.StreamReader(HttpContextAccessor.HttpContext.Request.Body); JObject asObj = JObject.Parse(sr.ReadToEnd()); System.IO.StreamReader sr = new System.IO.StreamReader(HttpContextAccessor.HttpContext.Request.Body); JObject asObj = JObject.Parse(sr.ReadToEnd());

Подробнее - попытка получить сжатый, не компилируемый пример элементов, которые вам понадобятся для обеспечения, чтобы получить IHttpContextAccessor. Ответы правильно указывали, что вам нужно будет вернуться к началу, когда вы попытаетесь прочитать тело запроса. CanSeek, Position в потоке тела запроса полезны для проверки этого.

.NET Core DI Docs

// First -- Make the accessor DI available
//
// Add an IHttpContextAccessor to your ConfigureServices method, found by default
// in your Startup.cs file:
// Extraneous junk removed for some brevity:
public void ConfigureServices(IServiceCollection services)
{
    // Typical items found in ConfigureServices:
    services.AddMvc(config => { config.Filters.Add(typeof(ExceptionFilterAttribute)); });
    // ...

    // Add or ensure that an IHttpContextAccessor is available within your Dependency Injection container
    services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
}

// Second -- Inject the accessor
//
// Elsewhere in the constructor of a class in which you want
// to access the incoming Http request, typically 
// in a controller class of yours:
public class MyResourceController : Controller
{
    public ILogger<PricesController> Logger { get; }
    public IHttpContextAccessor HttpContextAccessor { get; }

    public CommandController(
        ILogger<CommandController> logger,
        IHttpContextAccessor httpContextAccessor)
    {
        Logger = logger;
        HttpContextAccessor = httpContextAccessor;
    }

    // ...

    // Lastly -- a typical use 
    [Route("command/resource-a/{id}")]
    [HttpPut]
    public ObjectResult PutUpdate([FromRoute] string id, [FromBody] ModelObject requestModel)
    {
        if (HttpContextAccessor.HttpContext.Request.Body.CanSeek)
        {
            HttpContextAccessor.HttpContext.Request.Body.Seek(0, System.IO.SeekOrigin.Begin);
            System.IO.StreamReader sr = new System.IO.StreamReader(HttpContextAccessor.HttpContext.Request.Body);
            JObject asObj = JObject.Parse(sr.ReadToEnd());

            var keyVal = asObj.ContainsKey("key-a");
        }
    }
}    

Ответ 6

У меня была похожая проблема при использовании ASP.NET Core 2.1:

  • Мне нужно специальное промежуточное ПО для чтения данных POST и выполнения некоторых проверок безопасности на них
  • использование фильтра авторизации нецелесообразно из-за большого количества действий, на которые влияют
  • Я должен разрешить привязку объектов в действиях ([FromBody] someObject). Спасибо SaoBiz за указание на это решение.

Итак, очевидное решение состоит в том, чтобы разрешить перематывание запроса, но убедитесь, что после прочтения тела привязка все еще работает.

EnableRequestRewindMiddleware

public class EnableRequestRewindMiddleware
{
    private readonly RequestDelegate _next;

    ///<inheritdoc/>
    public EnableRequestRewindMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    /// <summary>
    /// 
    /// </summary>
    /// <param name="context"></param>
    /// <returns></returns>
    public async Task Invoke(HttpContext context)
    {
        context.Request.EnableRewind();
        await _next(context);
    }
}

Startup.cs

(поместите это в начале метода Configure)

app.UseMiddleware<EnableRequestRewindMiddleware>();

Некоторое другое промежуточное ПО

Это часть промежуточного программного обеспечения, которое требует распаковки POST-информации для проверки.

using (var stream = new MemoryStream())
{
    // make sure that body is read from the beginning
    context.Request.Body.Seek(0, SeekOrigin.Begin);
    context.Request.Body.CopyTo(stream);
    string requestBody = Encoding.UTF8.GetString(stream.ToArray());

    // this is required, otherwise model binding will return null
    context.Request.Body.Seek(0, SeekOrigin.Begin);
}