IT Share you

asp.net 코어 webapi 컨트롤러에서 요청 본문을 읽는 방법은 무엇입니까?

shareyou 2020. 11. 21. 08:43
반응형

asp.net 코어 webapi 컨트롤러에서 요청 본문을 읽는 방법은 무엇입니까?


OnActionExecuting메서드 에서 요청 본문을 읽으려고 하지만 항상 null본문을 습니다.

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

스트림 위치를 명시 적으로 0으로 설정하려고했지만 작동하지 않았습니다. 이것이 ASP.NET CORE이기 때문에 제 생각에는 조금 다릅니다. 이전 webapi 버전을 참조하는 모든 샘플을 여기에서 볼 수 있습니다.
이 작업을 수행하는 다른 방법이 있습니까?


ASP.Net Core에서는 본문 요청을 여러 번 읽는 것이 복잡해 보이지만 첫 번째 시도가 올바른 방식으로 수행되면 다음 시도에도 문제가 없습니다.

예를 들어 본문 스트림을 대체하여 여러 차례를 읽었지만 다음이 가장 깨끗하다고 ​​생각합니다.

가장 중요한 점은

  1. 요청에 본문을 두 번 이상 읽을 것임을 알리기 위해
  2. 신체 흐름을 닫지 않기 위해
  3. 내부 프로세스가 손실되지 않도록 초기 위치로 되감습니다.

[편집하다]

Murad가 지적한대로 .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
    }
}

그런 다음 요청 핸들러에서 본문을 다시 사용할 수 있습니다.

귀하의 경우 null 결과가 표시되면 본문이 이미 이전 단계에서 읽 혔음을 의미합니다. 이 경우 미들웨어를 사용해야 할 수도 있습니다 (아래 참조).

그러나 큰 스트림을 처리하는 경우에는주의해야합니다. 그 동작은 모든 것이 메모리에로드된다는 것을 의미합니다. 파일 업로드시 트리거되지 않아야합니다.

이것을 미들웨어로 사용할 수 있습니다.

내 모습은 다음과 같습니다 (다시 말하지만, 대용량 파일을 다운로드 / 업로드하는 경우 메모리 문제를 방지하려면이 기능을 비활성화해야합니다).

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

}

요청 본문을 되감기 위해 @Jean의 답변은 잘 작동하는 것처럼 보이는 솔루션을 찾는 데 도움이되었습니다. 나는 현재 Global Exception Handler Middleware에 이것을 사용하지만 원칙은 동일합니다.

기본적으로 요청 본문 (데코레이터 대신)에서 되감기를 활성화하는 미들웨어를 만들었습니다.

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();
    [...]
}

이 방법을 사용하여 요청 본문 스트림을 성공적으로 되 감을 수있었습니다.


보다 명확한 솔루션은 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"
    }
}

IHttpContextAccessor이 경로를 이동하고자하는 경우 방법은 작업을 수행합니다.

TLDR;

  • 주입 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());

- 간결한에서 시도가 아닌 컴파일, 당신은 확인해야합니다 항목의 예는 가능한에서 얻기 위해 자리에 있습니다 IHttpContextAccessor. 요청 본문을 읽으려고 할 때 처음으로 돌아 가야한다는 답변이 올바르게 지적되었습니다. 요청 본문 스트림 CanSeek, Position속성은이를 확인하는 데 도움이됩니다.

.NET Core DI 문서

// 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");
        }
    }
}    

ASP.NET Core 2.1을 사용할 때 비슷한 문제가 발생했습니다.

  • POST 된 데이터를 읽고 이에 대한 보안 검사를 수행하려면 사용자 지정 미들웨어가 필요합니다.
  • 영향을받는 많은 작업으로 인해 권한 부여 필터를 사용하는 것은 실용적이지 않습니다.
  • I have to allow objects binding in the actions ([FromBody] someObject). Thanks to SaoBiz for pointing out this solution.

So, the obvious solution is to allow the request to be rewindable, but make sure that after reading the body, the binding still works.

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

(place this at the beginning of Configure method)

app.UseMiddleware<EnableRequestRewindMiddleware>();

Some other middleware

This is part of the middleware that requires unpacking of the POSTed information for checking stuff.

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);
}

Apparently we can use IHttpContextAccessor to access http context in controllers. Just need to inject in the start up class and get it in your controllers.

  services.AddScoped<IHttpContextAccessor, HttpContextAccessor>();

using this you can access the context even in the constructor.

참고URL : https://stackoverflow.com/questions/40494913/how-to-read-request-body-in-a-asp-net-core-webapi-controller

반응형