CopyBatchRequestProperties and DeleteRequestContainer replacement in AspNetCore - c#

I got a code which used the HttpRequest from Microsoft.AspNet.Http namespace and after updating the code, I am now using the Microsoft.AspNetCore.Http namespace along with Microsoft.AspNetCore.OData v8.0.12.
In the code I had invoked these methods for OData batch operation:
changeSetContext.Request.CopyBatchRequestProperties(request);
changeSetContext.Request.DeleteRequestContainer(false);
After the namespace change, these methods are no longer defined. Is there a replacement in the AspNetCore namespace version > 8 for what these methods were doing?

You could check the documents:
CopyBatchRequestProperties Method in Asp.Net
CopyBatchRequestProperties Method in Asp.Net Core
DeleteRequestContainer Method in Asp.Net
DeleteRequestContainer Method in Asp.Net Core
The request should be type of Microsoft.AspNetCore.Http.HttpRequest instead of System.Net.Http.HttpRequestMessage

So I have some bad news. There is no direct equivalent in v8.0.12. So you will need to find an alternate solution or write your own implementation. The following is how you might create your own implementation:
CopyBatchRequestProperites:
public static class HttpRequestExtensions
{
public static void CopyBatchRequestProperties(this HttpRequest targetRequest, HttpRequest sourceRequest)
{
foreach (var property in sourceRequest.GetType().GetProperties())
{
var value = property.GetValue(sourceRequest);
property.SetValue(targetRequest, value);
}
}
}
using:
var sourceRequest = changeSetContext.Request;
var targetRequest = new HttpRequest();
targetRequest.CopyBatchRequestProperties(sourceRequest);
for deleting a request container you can try setting its value to null.
DeleteRequestContainer:
You can make the example an extension method of the HttpRequest class:
public static class HttpRequestExtensions
{
public static void DeleteRequestContainer(this HttpRequest request, bool deleteRequest)
{
if(deleteRequest)
{
var request = changeSetContext.Request;
request.Headers.Remove("OData-Version");
request.Headers.Remove("Content-Type");
request.Content = null;
}
}
}
using
var request = changeSetContext.Request;
request.DeleteRequestContainer(false);
This might not work for your exact case but should help to give you an idea of what you could probably do. Might even be worth looking at the source code in version 7.5.8 to see if you can figure out how to recreate it for yourself.

Related

How to find the type of Httpcontext.Request.Body in .net core

How can I find the Type of the RequestBody object in Httpcontext in middleware and deserialize it to the original Type?
e.g. this is the request body {"username":"zxc", "password":"123"} and I have
public class LoginRequest
{
public string UserName { get; set; }
public string Password { get; set; }
}
Is that possible to convert the request body to an object of LoginRequest? (this is just an example and it can be different types so I don't know which type is coming to middleware)
It is not possible to do it inside IMiddleware, but it is going to be possible for example inside IActionFilter. This answer contains a good, short explanation of what is ASP.NET Core middleware and when it occurs: https://stackoverflow.com/a/61957091/2895299
Inside IMiddleware, you have access to HttpContext but model binding hasn't occured yet. If you want to know the type of request body you need to be inside MVC pipeline, after binding.
Following code snippet gets the type of request's argument:
public class SampleFilter : IActionFilter
{
public void OnActionExecuted(ActionExecutedContext context)
{
//
}
public void OnActionExecuting(ActionExecutingContext context)
{
var arg = context.ActionArguments.First().Value;
var argType = arg.GetType();
//do whatever you want with type information
}
}
Obviously it is not bullet proof, it is just a dummy example but you should get the idea. Just please not that using reflection to get type of every request may have a significant performance impact on your application.
If you are interested in how MVC Pipeline works you may also check https://livebook.manning.com/book/asp-net-core-in-action/chapter-13/
There are lots of ways to do this but I think you can use GetType() method and maybe you can achieve what you want.

The type or namespace name 'DefaultHttpRequest' could not be found

I have just upgraded to .net core 3.1 from 2.1.
I updated all the packages but i am getting the below errors which I am not able to resolve:
The type or namespace name 'DefaultHttpRequest' could not be found
The type or namespace name 'Internal' does not exist in the namespace 'Microsoft.AspNetCore.Http'
I have been using above as:
using Microsoft.AspNetCore.Http.**Internal**;
private static async Task<UserInfo>
Uplpoad(byte[] buffer, FormFileCollection formFileCollection,
**DefaultHttpRequest defaultHttpRequest,**
Dictionary<string, StringValues> dictionary)
{
//some code
defaultHttpRequest.Form = new FormCollection(dictionary, formFileCollection);
//some code
}
public async void Test1Up()
{
//some code
var defaultHttpContext = new DefaultHttpContext(featureCollection);
var defaultHttpRequest = new **DefaultHttpRequest**(defaultHttpContext);
//some code
}
See the error in the highlighted lines in **
I can see in the breaking changes for 3.1 there are changes to DefaultHttpContext but I didn't find anything related to DefaultHttpRequest which is giving me errors as above.
DefaultHttpRequest was changed from public to internal as part of the ASP.NET Core 3.0 release, which means it's no longer available. In the code you've shown, there doesn't appear to be any reason to create or depend on DefaultHttpRequest.
I recommend changing the code to something like this:
private static async Task<UserInfo> Upload(byte[] buffer, FormFileCollection formFileCollection,
HttpRequest httpRequest, Dictionary<string, StringValues> dictionary)
{
// ...
httpRequest.Form = new FormCollection(dictionary, formFileCollection);
// Update all other references to defaultHttpRequest
// ...
}
public async void Test1Up() // async void is generally bad, but that's a separate issue
{
// ...
var httpContext = new DefaultHttpContext(featureCollection);
var httpRequest = httpContext.Request;
// Update all other references to defaultHttpContext and defaultHttpRequest
// ...
}
Rather than passing around DefaultHttpRequest, use the abstract HttpRequest. The Form property being set in Upload is part of HttpRequest, so that should work as-is. There's also no need to create an instance of DefaultHttpRequest: the DefaultHttpContext constructor does this for you and provides it via its Request property.
Here's the official announcement and relevant linked issue.

Unable to get request header in asp net core web API

I am trying to create a custom filter in asp net core web api which is as below but unable to get header info.
internal class BasicAuthFilterAttribute : ActionFilterAttribute
{
private StringValues xyz;
public override void OnActionExecuting(ActionExecutingContext actionContext)
{
var authHeader = actionContext.HttpContext.Request.Headers.TryGetValue("Basic", out xyz);
}
}
TryGetValue always return false however I can see Headers contains the "Basic" header. As I am new in ASP.NET Core so can anyone guide me what I am possibly doing wrong?
Here how headers looks like.
Thank you all for your valuable input however below code worked as expected.
actionContext.HttpContext.Request.Headers.TryGetValue("Authorization", out authorizationToken);
public static class HttpRequestExtension
{
public static string GetHeader(this HttpRequest request, string key)
{
return request.Headers.FirstOrDefault(x => x.Key == key).Value.FirstOrDefault();
}
}
calling method:
var Authorization = Request.GetHeader("Authorization");
How about this one? We shoule embrace the new variables. :)
bool tryGetValue = actionContext.ActionArguments.TryGetValue("data", out data);
var xyz = context.HttpContext.Request?.Headers["Basic"];

How to read request body in an asp.net core webapi controller?

I'm trying to read the request body in the OnActionExecuting method, but I always get null for the body.
var request = context.HttpContext.Request;
var stream = new StreamReader(request.Body);
var body = stream.ReadToEnd();
I have tried to explicitly set the stream position to 0, but that also didn't work. Since this is ASP.NET Core, things are a little different I think. I can see all the samples here referring to old web API versions.
Is there any other way of doing this?
In ASP.Net Core it seems complicated to read several times the body request, however, if your first attempt does it the right way, you should be fine for the next attempts.
I read several turnarounds for example by substituting the body stream, but I think the following is the cleanest:
The most important points being
to let the request know that you will read its body twice or more times,
to not close the body stream, and
to rewind it to its initial position so the internal process does not get lost.
[EDIT]
As pointed out by Murad, you may also take advantage of the .Net Core 2.1 extension: EnableBuffering It stores large requests onto the disk instead of keeping it in memory, avoiding large-streams issues stored in memory (files, images, ...).
You can change the temporary folder by setting the ASPNETCORE_TEMP environment variable, and files are deleted once the request is over.
In an AuthorizationFilter, you can do the following:
// 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 at the body for the request
req.Body.Position = 0;
// Do whatever works with bodyStr here
}
}
public class SomeController: Controller
{
[HttpPost("MyRoute")]
[EnableBodyRewind]
public IActionResult SomeAction([FromBody]MyPostModel model )
{
// play the body string again
}
}
Then you can use the body again in the request handler.
In your case, if you get a null result, it probably means that the body has already been read at an earlier stage. In that case, you may need to use a middleware (see below).
However be careful if you handle large streams, that behavior implies that everything is loaded into memory, this should not be triggered in case of a file upload.
You may want to use this as a Middleware
Mine looks like this (again, if you download/upload large files, this should be disabled to avoid memory issues):
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>();
}
}
A clearer solution, works in ASP.Net Core 2.1 / 3.1
Filter class
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();
}
}
In an Controller
[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&param2=someothervalue"
}
}
A quick way to add response buffering in .NET Core 3.1 is
app.Use((context, next) =>
{
context.Request.EnableBuffering();
return next();
});
in Startup.cs. I found this also guarantees that buffering will be enabled before the stream has been read, which was a problem for .Net Core 3.1 with some of the other middleware/authorization filter answers I've seen.
Then you can read your request body via HttpContext.Request.Body in your handler as several others have suggested.
Also worth considering is that EnableBuffering has overloads that allow you to limit how much it will buffer in memory before it uses a temporary file, and also an overall limit to you buffer. NB if a request exceeds this limit an exception will be thrown and the request will never reach your handler.
Recently I came across a very elegant solution that take in random JSON that you have no idea the structure:
[HttpPost]
public JsonResult Test([FromBody] JsonElement json)
{
return Json(json);
}
Just that easy.
To be able to rewind the request body, #Jean's answer helped me come up with a solution that seems to work well. I currently use this for Global Exception Handler Middleware but the principle is the same.
I created a middleware that basically enables the rewind on the request body (instead of a decorator).
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>();
}
}
This can then be used in your Startup.cs like so:
[...]
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
[...]
app.UseEnableRequestRewind();
[...]
}
Using this approach, I have been able to rewind the request body stream successfully.
This is a bit of an old thread, but since I got here, I figured I'd post my findings so that they might help others.
First, I had the same issue, where I wanted to get the Request.Body and do something with that (logging/auditing). But otherwise I wanted the endpoint to look the same.
So, it seemed like the EnableBuffering() call might do the trick. Then you can do a Seek(0,xxx) on the body and re-read the contents, etc.
However, this led to my next issue. I'd get "Synchronous operations are disallowed" exceptions when accessing the endpoint. So, the workaround there is to set the property AllowSynchronousIO = true, in the options. There are a number of ways to do accomplish this (but not important to detail here..)
THEN, the next issue is that when I go to read the Request.Body it has already been disposed. Ugh. So, what gives?
I am using the Newtonsoft.JSON as my [FromBody] parser in the endpoint call. That is what is responsible for the synchronous reads and it also closes the stream when it's done. Solution? Read the stream before it get's to the JSON parsing? Sure, that works and I ended up with this:
/// <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 because 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);
}
}
}
}
So now, I can access the body using the HttpContext.Items["request_body"] in the endpoints that have the [ReadRequestBodyIntoItems] attribute.
But man, this seems like way too many hoops to jump through. So here's where I ended, and I'm really happy with it.
My endpoint started as something like:
[HttpPost("")]
[ReadRequestBodyIntoItems]
[Consumes("application/json")]
public async Task<IActionResult> ReceiveSomeData([FromBody] MyJsonObjectType value)
{
var bodyString = HttpContext.Items["request_body"];
// use the body, process the stuff...
}
But it is much more straightforward to just change the signature, like so:
[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...
}
}
I really liked this because it only reads the body stream once, and I have have control of the deserialization. Sure, it's nice if ASP.NET core does this magic for me, but here I don't waste time reading the stream twice (perhaps buffering each time), and the code is quite clear and clean.
If you need this functionality on lots of endpoints, perhaps the middleware approaches might be cleaner, or you can at least encapsulate the body extraction into an extension function to make the code more concise.
Anyways, I did not find any source that touched on all 3 aspects of this issue, hence this post. Hopefully this helps someone!
BTW: This was using ASP .NET Core 3.1.
for read of Body , you can to read asynchronously.
use the async method like follow:
public async Task<IActionResult> GetBody()
{
string body="";
using (StreamReader stream = new StreamReader(Request.Body))
{
body = await stream.ReadToEndAsync();
}
return Json(body);
}
Test with postman:
It's working well and tested in Asp.net core version 2.0 , 2.1 , 2.2, 3.0.
I hope is useful.
Writing an extension method is the most efficient way in my opinion
public static string PeekBody(this HttpRequest request)
{
try
{
request.EnableBuffering();
var buffer = new byte[Convert.ToInt32(request.ContentLength)];
request.Body.Read(buffer, 0, buffer.Length);
return Encoding.UTF8.GetString(buffer);
}
finally
{
request.Body.Position = 0;
}
}
You can use Request.Body.Peeker Nuget Package as well (source code)
//Return string
var request = HttpContext.Request.PeekBody();
//Return in expected type
LoginRequest request = HttpContext.Request.PeekBody<LoginRequest>();
//Return in expected type asynchronously
LoginRequest request = await HttpContext.Request.PeekBodyAsync<LoginRequest>();
I had a similar issue when using ASP.NET Core 2.1:
I need a custom middleware to read the POSTed data and perform some security checks against it
using an authorization filter is not practical, due to large number of actions that are affected
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.EnableBuffering(); // this used to be 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);
}
I was able to read request body in an asp.net core 3.1 application like this (together with a simple middleware that enables buffering -enable rewinding seems to be working for earlier .Net Core versions-) :
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;
The IHttpContextAccessor method does work if you wish to go this route.
TLDR;
Inject the IHttpContextAccessor
Rewind -- HttpContextAccessor.HttpContext.Request.Body.Seek(0, System.IO.SeekOrigin.Begin);
Read --
System.IO.StreamReader sr = new System.IO.StreamReader(HttpContextAccessor.HttpContext.Request.Body);
JObject asObj = JObject.Parse(sr.ReadToEnd());
More -- An attempt at a concise, non-compiling, example of the items you'll need to ensure are in place in order to get at a useable IHttpContextAccessor.
Answers have pointed out correctly that you'll need to seek back to the start when you try to read the request body. The CanSeek, Position properties on the request body stream helpful for verifying this.
.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");
}
}
}
I Know this my be late but in my case its Just I had a problem in routing as bellow
At startup.cs file I was beginning the routing with /api
app.MapWhen(context => context.Request.Path.StartsWithSegments(new PathString("/api")),
a =>
{
//if (environment.IsDevelopment())
//{
// a.UseDeveloperExceptionPage();
//}
a.Use(async (context, next) =>
{
// API Call
context.Request.EnableBuffering();
await next();
});
//and I was putting in controller
[HttpPost]
[Route("/Register", Name = "Register")]
//Just Changed the rout to start with /api like my startup.cs file
[HttpPost]
[Route("/api/Register", Name = "Register")]
/and now the params are not null and I can ready the body request multiple
I also wanted to read the Request.Body without automatically map it to some action parameter model. Tested a lot of different ways before solved this. And I didnĀ“t find any working solution described here. This solution is currently based on the .NET Core 3.0 framework.
reader.readToEnd() seamed like a simple way, even though it compiled, it throwed an runtime exception required me to use async call. So instead I used ReadToEndAsync(), however it worked sometimes, and sometimes not. Giving me errors like, cannot read after stream is closed. The problem is that we cannot guarantee that it will return the result in the same thread (even if we use the await). So we need some kind of callback. This solution worked for me.
[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!");
}
}
The simplest possible way to do this is the following:
In the Controller method you need to extract the body from, add this parameter:
[FromBody] SomeClass value
Declare the "SomeClass" as:
class SomeClass {
public string SomeParameter { get; set; }
}
When the raw body is sent as json, .net core knows how to read it very easily.
To those who simply want to get the content (request body) from the request:
Use the [FromBody] attribute in your controller method parameter.
[Route("api/mytest")]
[ApiController]
public class MyTestController : Controller
{
[HttpPost]
[Route("content")]
public async Task<string> ReceiveContent([FromBody] string content)
{
// Do work with content
}
}
As doc says: this attribute specifies that a parameter or property should be bound using the request body.
Here's a solution for POSTed JSON body that doesn't require any middleware or extensions, all you need is to override OnActionExecuting to have access to all of the data set in the body or even the arguments in the URL:
using System.Text.Json;
....
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
base.OnActionExecuting(filterContext);
// You can simply use filterContext.ActionArguments to get whatever param that you have set in the action
// For instance you can get the "json" param like this: filterContext.ActionArguments["json"]
// Or better yet just loop through the arguments and find the type
foreach(var elem in filterContext.ActionArguments)
{
if(elem.Value is JsonElement)
{
// Convert json obj to string
var json = ((JsonElement)elem.Value).GetRawText();
break;
}
}
}
[HttpPost]
public IActionResult Add([FromBody] JsonElement json, string id = 1)
{
return Ok("v1");
}
I run into the same problem under .NET5.0, none of the solutions above worked.
It turned out that the issue was the return value of the Post method. It must be Task and not void.
Bad code:
[HttpPost]
public async void Post() {...}
Good code:
[HttpPost]
public async Task Post() {...}

ASP.NET Core v2 (2015) MVC : How to get raw JSON bound to a string without a type?

Similar to this old question about prior ASP.NET versions, I want to get the request body of an HTTP POST to be bound to a string. It seems that the method binds, but that value is null, when ASP.NET invokes my controller method:
namespace Demo.Controllers
{
[Route("[controller]")]
public class WebApiDemoController : Controller
{
...
// POST api/values
[HttpPost]
public System.Net.Http.HttpResponseMessage Post([FromBody]string value)
{
// expected: value = json string, actual: json = null.
}
Do I still have to go grab the body from a stream? Or should this just work? When testing the above method, I used the following http headers:
Accept: Application/json
Content-Type: Application/json;charset=UTF-8
I'm passing in the following in the body: { "a": 1 }
I do NOT want to bind to a string variable named a. I want to bind any JSON I get, and then I want to use the JSON content, any arbitrary content at all, from within my method.
If I understood the documentation, the [FromBody] attribute should have done what I wanted, but I'm guessing that the ASP.NET core MVC binding mechanism won't bind a json to a "string value", but perhaps I could do something else that gets me an equivalent level of flexibility.
A similar question here gives me the idea maybe I should have written [FromBody] dynamic data instead of using [FromBody] string value.
Update: There are answers here for .net core 6 and other modern .net core versions.
The cleanest option I've found is adding your own simple InputFormatter:
public class RawJsonBodyInputFormatter : InputFormatter
{
public RawJsonBodyInputFormatter()
{
this.SupportedMediaTypes.Add("application/json");
}
public override async Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context)
{
var request = context.HttpContext.Request;
using (var reader = new StreamReader(request.Body))
{
var content = await reader.ReadToEndAsync();
return await InputFormatterResult.SuccessAsync(content);
}
}
protected override bool CanReadType(Type type)
{
return type == typeof(string);
}
}
And in your Startup.cs inside ConfigureServices:
services
.AddMvc(options =>
{
options.InputFormatters.Insert(0, new RawJsonBodyInputFormatter());
});
That will let you get at the raw JSON payload in your controllers:
[HttpPost]
public IActionResult Post([FromBody]string value)
{
// value will be the request json payload
}
The following works in .net core 1.x, but not in .net core 2.x.
As I commented, the solution is to use [FromBody]dynamic data as my parameter list, using dynamic instead of string, and I will receive a JObject.
Caution: If your architecture calls for a single WebApi server to be equally fluent in producing XML and JSON, depending on content-type header entries, this kind of direct-JSON-consumption strategy can backfire on you. (Supporting both XML and JSON on the same service is possible with sufficient work, but then you're taking stuff that was further UP the MVC asset pipeline and moving it down into your controller methods, which turns out to be against the spirit of MVC, where models come to you as POCOs already parsed.)
Once you convert to a string inside the method, converting the incoming JObject (Newtonsoft.JSON in memory data type for JSON) to a string.
Found at other answer here.
Sample code, thanks to Jeson Martajaya:
With dynamic:
[HttpPost]
public System.Net.Http.HttpResponseMessage Post([FromBody]dynamic value)
{
//...
}
Sample code with JObject:
[HttpPost]
public System.Net.Http.HttpResponseMessage Post([FromBody]Newtonsoft.Json.Linq.JObject value)
{
//...
}
Found a solution for ASP.NET Core 3.1 Web API.
Looks like following:
public async Task<IActionResult> PutAsync([FromBody] System.Text.Json.JsonElement entity)
{
// your code here
}
The following two methods works in ASP.NET core 2 to read the raw json string.
1) This one has better performance.
[HttpPost]
public async Task<ActionResult<int>> Process()
{
string jsonString;
using (StreamReader reader = new StreamReader(Request.Body, Encoding.UTF8))
{
jsonString = await reader.ReadToEndAsync();
}
2)
[HttpPost]
public async Task<ActionResult<int>> Process([FromBody]JToken jsonbody)
{
var jsonString = jsonBody.ToString();
Alternatively, you could also just accept a JObject and you would be able to use Linq to Json ot even directly ToString() if you really need the string.
Based on Saeb Amini's excellent answer above, this extends his solution to be for plain-text as well. The only changes here are adding the "text/plain" mime-type, and adding a namespace and required usings.
using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Formatters;
namespace AspExtensions // or whatever
{
// see: https://stackoverflow.com/a/47807117/264031
public class RawStringBodyInputFormatter : InputFormatter
{
public RawStringBodyInputFormatter()
{
this.SupportedMediaTypes.Add("text/plain");
this.SupportedMediaTypes.Add("application/json");
}
public override async Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context)
{
var request = context.HttpContext.Request;
using(var reader = new StreamReader(request.Body)) {
string content = await reader.ReadToEndAsync();
return await InputFormatterResult.SuccessAsync(content);
}
}
protected override bool CanReadType(Type type)
{
return type == typeof(string);
}
}
}
If you don't mine forgoing the automagic binding, this can be placed directly in an Http handler on a Controller:
using StreamReader reader = new StreamReader(Request.Body, Encoding.UTF8);
var value = reader.ReadToEndAsync().GetAwaiter().GetResult();
I see that Sam has already been down voted for saying pretty much the same thing, but in testing using Postman I find that if I set the request body to just a simple double quoted string ASP binds it fine with the default '[FromBody]string value' argument.
"just send your string like this without any curly braces"
Not sure whether application/json is supposed to accept data in this format. Hopefully by posting this someone knowledgeable will pipe up and state whether this is valid or not.
You need a type to bind the data. Example:
public class Person
{
public string Name {get; set;}
}
for data { "Name" : "James"}
If you want to receive a string you need to pass it as a string. Your JSON should be enclosed in quotes:
'{ "a": 1 }'

Categories