Saya mencoba membaca isi permintaan dalam OnActionExecuting
metode, tetapi saya selalu mendapatkan null
isi.
var request = context.HttpContext.Request;
var stream = new StreamReader(request.Body);
var body = stream.ReadToEnd();
Saya telah mencoba untuk secara eksplisit mengatur posisi aliran ke 0, tetapi itu juga tidak berhasil. Karena ini adalah ASP.NET Core, menurut saya sedikit berbeda. Saya dapat melihat semua contoh di sini mengacu pada versi API web lama.
Apakah ada cara lain untuk melakukan ini?
c#
asp.net-web-api
asp.net-core
Kasun Koswattha
sumber
sumber
Jawaban:
Di ASP.Net Core tampaknya rumit untuk membaca beberapa kali permintaan tubuh, namun jika upaya pertama Anda melakukannya dengan cara yang benar, Anda akan baik-baik saja untuk upaya berikutnya.
Saya membaca beberapa perubahan haluan misalnya dengan mengganti aliran tubuh, tetapi saya pikir berikut ini yang paling bersih:
Poin terpenting adalah
[EDIT]
Seperti yang ditunjukkan oleh Murad, Anda juga dapat memanfaatkan ekstensi .Net Core 2.1:
EnableBuffering
Ini menyimpan permintaan besar ke disk alih-alih menyimpannya di memori, menghindari masalah aliran besar yang disimpan dalam memori (file, gambar, ...) . Anda dapat mengubah folder sementara dengan mengaturASPNETCORE_TEMP
variabel lingkungan, dan file akan dihapus setelah permintaan selesai.Di AuthorizationFilter , Anda bisa melakukan hal berikut:
// 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 } }
Kemudian Anda dapat menggunakan isi kembali di penangan permintaan.
Dalam kasus Anda, jika Anda mendapatkan hasil nol, itu mungkin berarti bahwa isi telah dibaca pada tahap sebelumnya. Dalam hal ini Anda mungkin perlu menggunakan middleware (lihat di bawah).
Namun berhati-hatilah jika Anda menangani aliran besar, perilaku tersebut menyiratkan bahwa semuanya dimuat ke dalam memori, ini tidak boleh dipicu jika ada file yang diunggah.
Anda mungkin ingin menggunakan ini sebagai Middleware
Milik saya terlihat seperti ini (sekali lagi, jika Anda mengunduh / mengunggah file besar, ini harus dinonaktifkan untuk menghindari masalah memori):
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>(); } }
sumber
req.EnableRewind();
? Saya menggunakan kode di atas dan berfungsi dengan baik.request.EnableBuffering()
(pembungkus di atasEnableRewind()
) Ini tersedia di ASP.NET Core 2.1 docs.microsoft.com/en-us/dotnet/api/…Solusi yang lebih jelas, bekerja di ASP.Net Core 2.1 / 3.1
Kelas filter
using Microsoft.AspNetCore.Authorization; // For ASP.NET 2.1 using Microsoft.AspNetCore.Http.Internal; // For ASP.NET 3.1 using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Filters; public class ReadableBodyStreamAttribute : AuthorizeAttribute, IAuthorizationFilter { public void OnAuthorization(AuthorizationFilterContext context) { // For ASP.NET 2.1 // context.HttpContext.Request.EnableRewind(); // For ASP.NET 3.1 // context.HttpContext.Request.EnableBuffering(); } }
Dalam sebuah Kontroler
[HttpPost] [ReadableBodyStream] public string SomePostMethod() { //Note: if you're late and body has already been read, you may need this next line //Note2: if "Note" is true and Body was read using StreamReader too, then it may be necessary to set "leaveOpen: true" for that stream. HttpContext.Request.Body.Seek(0, SeekOrigin.Begin); using (StreamReader stream = new StreamReader(HttpContext.Request.Body)) { string body = stream.ReadToEnd(); // body = "param=somevalue¶m2=someothervalue" } }
sumber
.EnableRewind()
AuthorizeAttribute
keAttribute
(di ASP.Net Core 3.1).Untuk dapat memundurkan tubuh permintaan, jawaban @Jean membantu saya menemukan solusi yang tampaknya berfungsi dengan baik. Saat ini saya menggunakan ini untuk Global Exception Handler Middleware tetapi prinsipnya sama.
Saya membuat middleware yang pada dasarnya memungkinkan pemunduran pada tubuh permintaan (bukan dekorator).
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>(); } }
Ini kemudian dapat digunakan di
Startup.cs
like Anda :[...] public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { [...] app.UseEnableRequestRewind(); [...] }
Dengan menggunakan pendekatan ini, saya telah berhasil memundurkan aliran tubuh permintaan.
sumber
UseEnableRequestRewind(this IApplicationBuilder builder)
.Ini adalah utas lama, tetapi sejak saya tiba di sini, saya pikir saya akan memposting temuan saya sehingga mereka dapat membantu orang lain.
Pertama, saya memiliki masalah yang sama, di mana saya ingin mendapatkan Request.Body dan melakukan sesuatu dengan itu (logging / audit). Tetapi sebaliknya saya ingin titik akhir terlihat sama.
Jadi, sepertinya panggilan EnableBuffering () mungkin berhasil. Kemudian Anda dapat melakukan Seek (0, xxx) pada tubuh dan membaca kembali isinya, dll.
Namun, hal ini menyebabkan masalah saya berikutnya. Saya akan mendapatkan pengecualian "Operasi sinkronis tidak diizinkan" saat mengakses titik akhir. Jadi, solusinya adalah mengatur properti AllowSynchronousIO = true, dalam opsi. Ada beberapa cara untuk melakukannya (tetapi tidak penting untuk detailnya di sini ..)
LALU, masalah berikutnya adalah ketika saya pergi untuk membaca Request. Tubuh itu sudah dibuang. Ugh. Jadi, apa yang menyebabkannya?
Saya menggunakan Newtonsoft.JSON sebagai parser [FromBody] saya di panggilan endpiont. Itulah yang bertanggung jawab atas pembacaan sinkron dan juga menutup aliran saat selesai. Larutan? Baca aliran sebelum masuk ke penguraian JSON? Tentu, itu berhasil dan saya berakhir dengan ini:
/// <summary> /// quick and dirty middleware that enables buffering the request body /// </summary> /// <remarks> /// this allows us to re-read the request body's inputstream so that we can capture the original request as is /// </remarks> public class ReadRequestBodyIntoItemsAttribute : AuthorizeAttribute, IAuthorizationFilter { public void OnAuthorization(AuthorizationFilterContext context) { if (context == null) return; // NEW! enable sync IO beacuse the JSON reader apparently doesn't use async and it throws an exception otherwise var syncIOFeature = context.HttpContext.Features.Get<IHttpBodyControlFeature>(); if (syncIOFeature != null) { syncIOFeature.AllowSynchronousIO = true; var req = context.HttpContext.Request; req.EnableBuffering(); // read the body here as a workarond for the JSON parser disposing the stream if (req.Body.CanSeek) { req.Body.Seek(0, SeekOrigin.Begin); // if body (stream) can seek, we can read the body to a string for logging purposes using (var reader = new StreamReader( req.Body, encoding: Encoding.UTF8, detectEncodingFromByteOrderMarks: false, bufferSize: 8192, leaveOpen: true)) { var jsonString = reader.ReadToEnd(); // store into the HTTP context Items["request_body"] context.HttpContext.Items.Add("request_body", jsonString); } // go back to beginning so json reader get's the whole thing req.Body.Seek(0, SeekOrigin.Begin); } } } }
Jadi sekarang, saya dapat mengakses tubuh menggunakan HttpContext.Items ["request_body"] di titik akhir yang memiliki atribut [ReadRequestBodyIntoItems].
Tapi kawan, ini sepertinya terlalu banyak rintangan untuk dilewati. Jadi di sinilah saya mengakhiri, dan saya sangat senang dengannya.
Titik akhir saya dimulai sebagai sesuatu seperti:
[HttpPost("")] [ReadRequestBodyIntoItems] [Consumes("application/json")] public async Task<IActionResult> ReceiveSomeData([FromBody] MyJsonObjectType value) { val bodyString = HttpContext.Items["request_body"]; // use the body, process the stuff... }
Tetapi jauh lebih mudah untuk hanya mengubah tanda tangan, seperti:
[HttpPost("")] [Consumes("application/json")] public async Task<IActionResult> ReceiveSomeData() { using (var reader = new StreamReader( Request.Body, encoding: Encoding.UTF8, detectEncodingFromByteOrderMarks: false )) { var bodyString = await reader.ReadToEndAsync(); var value = JsonConvert.DeserializeObject<MyJsonObjectType>(bodyString); // use the body, process the stuff... } }
Saya sangat menyukai ini karena hanya membaca aliran tubuh sekali, dan saya memiliki kendali atas deserialisasi. Tentu, bagus jika inti ASP.NET melakukan keajaiban ini untuk saya, tetapi di sini saya tidak membuang waktu membaca aliran dua kali (mungkin buffering setiap kali), dan kodenya cukup jelas dan bersih.
Jika Anda memerlukan fungsionalitas ini pada banyak titik akhir, mungkin pendekatan middleware mungkin lebih bersih, atau setidaknya Anda dapat merangkum ekstraksi tubuh ke dalam fungsi ekstensi untuk membuat kode lebih ringkas.
Bagaimanapun, saya tidak menemukan sumber apa pun yang menyentuh ketiga aspek masalah ini, karenanya posting ini. Semoga ini membantu seseorang!
BTW: Ini menggunakan ASP .NET Core 3.1.
sumber
Baru-baru ini saya menemukan solusi yang sangat elegan yang mengambil JSON acak sehingga Anda tidak tahu strukturnya:
[HttpPost] public JsonResult Test([FromBody] JsonElement json) { return Json(json); }
Semudah itu.
sumber
GetRawText()
metode JsonElement dan menerima teks JSON saya.Cara cepat untuk menambahkan buffering respons di .NET Core 3.1 adalah
app.Use((context, next) => { context.Request.EnableBuffering(); return next(); });
di Startup.cs. Saya menemukan ini juga menjamin bahwa buffering akan diaktifkan sebelum streaming dibaca, yang merupakan masalah untuk .Net Core 3.1 dengan beberapa jawaban filter middleware / otorisasi lain yang pernah saya lihat.
Kemudian Anda dapat membaca isi permintaan Anda melalui
HttpContext.Request.Body
di handler Anda seperti yang disarankan beberapa orang lain.Juga patut dipertimbangkan adalah yang
EnableBuffering
memiliki kelebihan yang memungkinkan Anda untuk membatasi berapa banyak buffer dalam memori sebelum menggunakan file sementara, dan juga batas keseluruhan untuk Anda buffer. NB jika permintaan melebihi batas ini pengecualian akan dilempar dan permintaan tidak akan pernah mencapai penangan Anda.sumber
Saya mengalami masalah serupa saat menggunakan ASP.NET Core 2.1:
SaoBiz
untuk menunjukkan solusi ini.Jadi, solusi yang jelas adalah mengizinkan permintaan untuk dapat diputar ulang, tetapi pastikan bahwa setelah membaca isi, pengikatan masih berfungsi.
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
(tempatkan ini di awal metode Konfigurasi)
Beberapa middleware lainnya
Ini adalah bagian dari middleware yang membutuhkan pembongkaran informasi POST untuk memeriksa sesuatu.
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); }
sumber
untuk membaca
Body
, Anda dapat membaca secara asinkron.gunakan
async
metode seperti berikut:public async Task<IActionResult> GetBody() { string body=""; using (StreamReader stream = new StreamReader(Request.Body)) { body = await stream.ReadToEndAsync(); } return Json(body); }
Uji dengan tukang pos:
Ini bekerja dengan baik dan diuji dalam
Asp.net core
versi2.0 , 2.1 , 2.2, 3.0
.Semoga bermanfaat
sumber
The
IHttpContextAccessor
metode tidak bekerja jika Anda ingin pergi rute ini.TLDR;
Injeksi
IHttpContextAccessor
Mundur -
HttpContextAccessor.HttpContext.Request.Body.Seek(0, System.IO.SeekOrigin.Begin);
Baca --
System.IO.StreamReader sr = new System.IO.StreamReader(HttpContextAccessor.HttpContext.Request.Body); JObject asObj = JObject.Parse(sr.ReadToEnd());
More - Upaya pada contoh item yang ringkas dan tidak dapat dikompilasi, yang perlu Anda pastikan ada untuk mendapatkan yang bisa digunakan
IHttpContextAccessor
. Jawaban telah menunjukkan dengan benar bahwa Anda harus mencari kembali ke awal ketika Anda mencoba membaca isi permintaan. ItuCanSeek
,Position
properti pada aliran tubuh permintaan membantu untuk memverifikasi ini..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"); } } }
sumber
Saya dapat membaca tubuh permintaan dalam aplikasi asp.net core 3.1 seperti ini (bersama dengan middleware sederhana yang memungkinkan buffering -mengaktifkan rewinding tampaknya berfungsi untuk versi .Net Core sebelumnya-):
var reader = await Request.BodyReader.ReadAsync(); Request.Body.Position = 0; var buffer = reader.Buffer; var body = Encoding.UTF8.GetString(buffer.FirstSpan); Request.Body.Position = 0;
sumber
Saya juga ingin membaca Request.Body tanpa secara otomatis memetakannya ke beberapa model parameter tindakan. Menguji banyak cara berbeda sebelum memecahkan ini. Dan saya tidak menemukan solusi yang berfungsi seperti yang dijelaskan di sini. Solusi ini saat ini didasarkan pada kerangka kerja .NET Core 3.0.
reader.readToEnd () seamed seperti cara sederhana, meskipun dikompilasi, itu melontarkan pengecualian waktu proses yang mengharuskan saya untuk menggunakan panggilan async. Jadi, saya menggunakan ReadToEndAsync (), namun terkadang berhasil, dan terkadang tidak. Memberi saya kesalahan seperti, tidak dapat membaca setelah streaming ditutup. Masalahnya adalah kami tidak dapat menjamin bahwa itu akan mengembalikan hasil di utas yang sama (bahkan jika kami menggunakan await). Jadi kami membutuhkan semacam panggilan balik. Solusi ini berhasil untuk saya.
[Route("[controller]/[action]")] public class MyController : ControllerBase { // ... [HttpPost] public async void TheAction() { try { HttpContext.Request.EnableBuffering(); Request.Body.Position = 0; using (StreamReader stream = new StreamReader(HttpContext.Request.Body)) { var task = stream .ReadToEndAsync() .ContinueWith(t => { var res = t.Result; // TODO: Handle the post result! }); // await processing of the result task.Wait(); } } catch (Exception ex) { _logger.LogError(ex, "Failed to handle post!"); } }
sumber
Cara yang paling sederhana untuk melakukannya adalah sebagai berikut:
Dalam metode Controller Anda perlu mengekstrak tubuh dari, tambahkan parameter ini: Nilai SomeClass [FromBody]
Deklarasikan "SomeClass" sebagai: class SomeClass {public string SomeParameter {get; set; }}
Ketika tubuh mentah dikirim sebagai json, inti .net tahu cara membacanya dengan sangat mudah.
sumber
Untuk mereka yang hanya ingin mendapatkan konten (request body) dari request:
Gunakan
[FromBody]
atribut dalam parameter metode pengontrol Anda.[Route("api/mytest")] [ApiController] public class MyTestController : Controller { [HttpPost] [Route("content")] public async Task<string> ReceiveContent([FromBody] string content) { // Do work with content } }
Seperti yang dikatakan doc: atribut ini menetapkan bahwa parameter atau properti harus diikat menggunakan isi permintaan.
sumber