Can you serialize ByteArrayContent in a JSON that translates to C# object? - c#

I have written a C# Web API in VS Studio and have created numerous DTO's that get serialized into JSON entities which any client can consume. One of my API calls is to return a PDF file, so after some online research, I set up one of the DTO's is set up in the following format. I read somewhere you can do this, but not 100% sure:
public class MyCustomResult
{
public bool Success;
public DateTime? LastRunDate;
public string ErrorCode;
public string Message;
public ByteArrayContent ReportBody;
}
I do not get any errors when return the object as an IHttpActionResult:
return Ok(result);
I can see on the server that the ByteSize of the report byte[] is approx 700K. However, when I retrieve the object on the client, the JSON entity is approx 400B and no byte content in the ByteContentStream. When I run the query in Postman, I get an empty Header, so it appears that the ByteContentStream can't be serialized by Newtonsoft JSON.
Are there any options I should consider?

Here is a scenario where you'd use ByteArrayContent:
using(var req = new HttpRequestMessage(HttpMethod.Post, new Uri("https://example.com"))
{
req.Content = new ByteArrayContent(...);
using(var resp = await _client.SendAsync(req))
{
var data = await resp.Content.ReadAsAsync<object>();
}
}
What you'd want to do is this:
public class MyCustomResult
{
public bool Success;
public DateTime? LastRunDate;
public string ErrorCode;
public string Message;
public byte[] ReportBody; // <-- change this to byte[]
}
var dataToSend = new MyCustomResult(); // fill this in
using(var req = new HttpRequestMessage(HttpMethod.Post, new Uri("https://example.com"))
{
req.Content = new StringContent(
JsonConvert.SerializeObject(dataToSend, Encoding.UTF8, "application/json"));
using(var resp = await _client.SendAsync(req))
{
var data = await resp.Content.ReadAsAsync<object>();
}
}
(note: this code is not tested)
So what will happen is SerializeObject will convert that byte array into a Base64 string then send it.
The consumer would then have to decode that Base64 string. If it's another Newtonsoft.Json client and the model definitions match, then it will automatically decode it for you.
I understand you are doing an API endpoint. The above examples are to show the use of ByteArrayContent and why it exists in .NET. How you are returning data is correct: return Ok(response); as long as you fix your model.
So to sum it up:
ByteArrayContent is an implementation of HttpContent which is supposed to be used as a response body only. It's not to be used in conjunction with a JSON response.

Related

Parse HTTP request body to JSON string in .net core 3.0

I have implemented the following method format request body
private async Task<string> FormatRequest(HttpRequest request)
{
request.EnableBuffering();
//Create a new byte[] with the same length as the request stream
var buffer = new byte[Convert.ToInt32(request.ContentLength)];
//Copy the entire request stream into the new buffer
await request.Body.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false);
//Convert the byte[] into a string using UTF8 encoding
var bodyAsText = Encoding.UTF8.GetString(buffer);
request.Body.Position = 0;
return bodyAsText;
}
I got the following result
------WebKitFormBoundaryY8OPXY2MlrKMjBRe
Content-Disposition: form-data; name="RoleId"
2
------WebKitFormBoundaryY8OPXY2MlrKMjBRe
Content-Disposition: form-data; name="AuthenticationSettingsId"
3
.....
Expected result
"{\"fields\":[\"RoleId\",\"2\",\"AuthenticationSettingsId\",\"1\",\"recommendation\",\"reviewerId\"],\"size\":100,\"filter\":[{\"id\":\"ApplicationId\",\"operator\":\"and\",\"parent\":\"\",\"nested\":false,\"type\":\"integer\",\"value\":[360]}],\"aggregate\":[],\"sort\":[]}"
Note: Previously we used request.EnableRewind() it was returning the above result and later upgraded to .net core 3.0
Here is a high level of how I handle JSON queries. If you really want to get fancy you can implement all this into an abstract class and inherit direct to your data model.
There are plenty of different ways to get where you want to be, hopefully this helps you get there.
I've put comments in the code, but feel free to ask away if something doesn't make sense.
class SomeHttpJsonUtility
{
//If you want to parse your return data
//directly into a data model
class DataModel{
class ReturnData
{
[JsonPropertyName("fields")]
public Field[] Fields { get; set; }
}
class Field
{
[JsonPropertyName("RoleId")]
public int RoleId { get; set; }
//...you get the idea
}
}
//Some data if your sending a post request
private Dictionary<string, string> postParameters = new Dictionary<string, string>();
//Creates a HTTP Client With Specified Parameters
//You can do this any number of ways depending on the
//source you are querying
private HttpClient GetClient()
{
HttpClient _client = new HttpClient();
_client.DefaultRequestHeaders.Clear();
_client.DefaultRequestHeaders.Add(
"UserAgent",
new string[] { "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:103.0) Gecko/20100101 Firefox/103.0" });
_client.DefaultRequestHeaders.Add(
"AcceptLanguage",
new string[] { "en-US" });
_client.DefaultRequestHeaders.Add(
"AcceptEncoding",
new string[] { "gzip", "deflate", "br" });
_client.DefaultRequestHeaders.Add(
"Accept",
new string[] { "*/*" });
_client.DefaultRequestHeaders.Add(
"Connection",
new string[] { "keep-alive" });
return _client;
}
private void GetJson(Uri from_uri)
{
//Get the HttpClient With Proper Request Headers
HttpClient _client =
GetClient();
Task.Run(async () =>
{
//If your data comes from a get request
HttpResponseMessage _httpResponse =
await _client.GetAsync(
requestUri:from_uri);
//Or if your response comes from a post
_httpResponse =
await _client.PostAsync(
requestUri: from_uri,
content: new FormUrlEncodedContent(postParameters)
);
//since your initial post used a stream, we can
//keep going in that direction
//Initilize a memory stream to process the data
using(MemoryStream _ms = new MemoryStream())
{
//Send the http response content
////into the memory stream
await _httpResponse.Content.CopyToAsync(
stream: _ms);
//Goto the start of the memory stream
_ms.Seek(
offset: 0,
loc: SeekOrigin.Begin);
//Option 1:
//Send direct to data model
// This is utilizing the Microsoft Library:
// System.Text.Json.Serialization;
DataModel dataModel =
JsonSerializer.Deserialize<DataModel>(
utf8Json: _ms);
//Option 2:
//Send to a string
using(StreamReader _sr = new StreamReader(_ms))
{
string dataAsSting = _sr.ReadToEnd();
}
}
}).Wait();
}
}
If your query is only a Get request, then it's pretty easy get get the exact headers you need.
Using Firefox hit F12 and goto the web address.
Click the Network Tab, then Headers and view the request data.
You really only need a few of these:
Accept
Accept-Encoding
Accept-Language
Connection
User-Agent
Mozilla has some nice resources regarding the different header objects.
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept
Host should be taken care of by the HttpClient.
Cookies should be handled by the HttpClient (if you need them)
If you are actually getting the data back as Gzip you'll have to implement a reader, unless the HttpClient you are using will automatically decode it.
And at the end, victory :-)
I think you need to set the content-type on the request when you send it to application/json
Could you try to read this way?
var reader = new System.IO.StreamReader(request.Body);
var body = reader.ReadToEndAsync().Result;
Then you can use Newtonsoft or a similar library on body.
You need to to tokenize / encode your string with some JSON encoder.
Here you have two choices:
the internal (Microsoft) JsonConverter
the Newtonsoft.Json JsonConverter
Karthik, it appears you are sending a multipart request from a webkit browser.
If you would be able to just change it on the client side from multipart to application/json your problem would be fixed.
If this is not possible, you can just use:
private async Task<string> FormatRequest(HttpRequest request)
{
var form = request.Form.ToDictionary(x => x.Key, x => x.Value);
return JsonSerializer.Serialize(form);
}
This could parses your form into a dictionary and returns it as a Json.
(This code is written in dotnet 6, which has System.Text.Json. If you need to stick in .net 3.1, you would need to use a JsonSerializer like Newtonsoft.)

HttpClient POST Protobuf ByteArray to ASP.NET Core Web API

I am struggling to send some protobuf binary data to get back some other binary data.
The code is the following:
Client:
HttpClient client = new HttpClient
{
BaseAddress = new Uri("https://localhost:44302/")
};
// "Credentials" is a proto message
Credentials cred = new Credentials()
{
Email = "my#email.com",
Password = "mypassword"
};
var content = new ByteArrayContent(cred.ToByteArray());
var response = await client.PostAsync("api/database", content);
Server:
[HttpPost]
public async Task<IActionResult> Post(ByteArrayContent data)
{
Credentials c = Credentials.Parser.ParseFrom(await data.ReadAsByteArrayAsync());
// Get user from database, etc, etc...
// "user" being another proto defined message
return File(user.ToByteArray(), "application/octet-stream");
}
The thing is it doesn't even get to the server Post method. It fails directly to the client.PostAsync one. I get the Unsupported Media Type error:
I even tried the:
content.Headers.ContentType = MediaTypeHeaderValue.Parse("application/octet-stream");
Doesn't work...
All the answers I find to this issue are either old (This is the method I'm failing to apply) or have some weird Base64 string Json encoded serialization I absolutely want to avoid...
There also are some protobuf-net related answer but I want to avoid any thirdparty package.
Ok, finally got to find the solution. Issue was in the controller. Way to do is:
[HttpPost]
public async Task<IActionResult> LogIn()
{
Credentials cred = Credentials.Parser.ParseFrom(Utils.ReadRequestBody(Request));
// Use cred...
}
with method:
public static byte[] ReadStream(in Stream input)
{
using (MemoryStream ms = new MemoryStream())
{
input.CopyTo(ms);
return ms.ToArray();
}
}
public static byte[] ReadRequestBody(in HttpRequest request)
{
using (Stream stream = request.BodyReader.AsStream()) // Package "System.IO.Pipelines" needed here
{
return ReadStream(stream);
}
}

Disable ModelBinder in .NetCore

HeyGuys
I'm working on a WebApi project that receives requests from clients and redirects these requests to other services that are not open for direct access.
By default, .Net serializes and deserializes the Json request parameters automatically, so I need to re-serialize them before calling the appropriate service. The same problem occurs when receiving the service response. I need to deserialize it before sending the response to the user; otherwise .Net framework will serialize it one more time, resulting in a "Json of Json" response.
I found this answer but it does not seem to work with .NetCore; so I tried to create my own ModelBinder that just reads the Json object and returns it.
class JsonUnformatterBinderProvider : Microsoft.AspNetCore.Mvc.ModelBinding.IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
return new JsonUnformatterBinder(new SimpleTypeModelBinder(context.Metadata.ModelType));
}
}
and
class JsonUnformatterBinder : IModelBinder
{
private readonly IModelBinder _fallbackBinder;
public JsonUnformatterBinder(IModelBinder fallbackBinder)
{
_fallbackBinder = fallbackBinder;
}
public Task BindModelAsync(ModelBindingContext bindingContext)
{
string currMethod = bindingContext.ActionContext.HttpContext.Request.Method;
if ("POST".Equals(currMethod) || "PUT".Equals(currMethod))
{
string strData = new StreamReader(bindingContext.ActionContext.HttpContext.Request.Body).ReadToEnd();
bindingContext.Result = ModelBindingResult.Success(strData);
return Task.CompletedTask;
}
return _fallbackBinder.BindModelAsync(bindingContext);
}
}
This code is very simple, it was my first attempt and it worked well for my purposes. However, I still get the "Json of Json" problem when I take the second service answer and returns back to the user.
I basically have no idea what I can do to overcome this, so any workaround is welcome here.
If you need just redirect a request without modification, you could read it from input stream directly and send it to inner service. You could also use such approach to read responce from inner service.
//1. Set empty parameter list in action then neither serializator nor model binder are not invoked.
public async Task<ContentResult> ProxyAction(/*empty parameter list*/)
{
var newUrl = #"https://stackoverflow.com";
var data = this.Request.Body;
using (var client = new HttpClient())
{
//2. Read request body from input stream.
var reader = new StreamReader(data);
var json = reader.ReadToEnd();
using (var content = new StringContent(json))
{
//3. Set correct content type
content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue(this.Request.ContentType);
//4. Post request to inner service
var response = await client.PostAsync(newUrl, content);
//5. Read response without deserialization
var innerResponse = await response.Content.ReadAsStringAsync();
var contentType = response.Content.Headers.ContentType.ToString();
var statusCode = response.StatusCode;
//6. Return inner response without serialization
var outerResponse = this.Content(innerResponse, contentType);
outerResponse.StatusCode = (int)statusCode;
return outerResponse;
}
}
}

How do I get the raw request body from the Request.Content object using .net 4 api endpoint

I'm trying to capture the raw request data for accountability and want to pull the request body content out of the Request object.
I've seen suggestions doing a Request.InputStream, but this method is not available on the Request object.
Any idea of how to get a string representation of the Request.Content body?
In your comment on #Kenneth's answer you're saying that ReadAsStringAsync() is returning empty string.
That's because you (or something - like model binder) already read the content, so position of internal stream in Request.Content is on the end.
What you can do is this:
public static string GetRequestBody()
{
var bodyStream = new StreamReader(HttpContext.Current.Request.InputStream);
bodyStream.BaseStream.Seek(0, SeekOrigin.Begin);
var bodyText = bodyStream.ReadToEnd();
return bodyText;
}
You can get the raw data by calling ReadAsStringAsAsync on the Request.Content property.
string result = await Request.Content.ReadAsStringAsync();
There are various overloads if you want it in a byte or in a stream. Since these are async-methods you need to make sure your controller is async:
public async Task<IHttpActionResult> GetSomething()
{
var rawMessage = await Request.Content.ReadAsStringAsync();
// ...
return Ok();
}
EDIT: if you're receiving an empty string from this method, it means something else has already read it. When it does that, it leaves the pointer at the end. An alternative method of doing this is as follows:
public IHttpActionResult GetSomething()
{
var reader = new StreamReader(Request.Body);
reader.BaseStream.Seek(0, SeekOrigin.Begin);
var rawMessage = reader.ReadToEnd();
return Ok();
}
In this case, your endpoint doesn't need to be async (unless you have other async-methods)
For other future users who do not want to make their controllers asynchronous, or cannot access the HttpContext, or are using dotnet core (this answer is the first I found on Google trying to do this), the following worked for me:
[HttpPut("{pathId}/{subPathId}"),
public IActionResult Put(int pathId, int subPathId, [FromBody] myViewModel viewModel)
{
var body = new StreamReader(Request.Body);
//The modelbinder has already read the stream and need to reset the stream index
body.BaseStream.Seek(0, SeekOrigin.Begin);
var requestBody = body.ReadToEnd();
//etc, we use this for an audit trail
}
If you need to both get the raw content from the request, but also need to use a bound model version of it in the controller, you will likely get this exception.
NotSupportedException: Specified method is not supported.
For example, your controller might look like this, leaving you wondering why the solution above doesn't work for you:
public async Task<IActionResult> Index(WebhookRequest request)
{
using var reader = new StreamReader(HttpContext.Request.Body);
// this won't fix your string empty problems
// because exception will be thrown
reader.BaseStream.Seek(0, SeekOrigin.Begin);
var body = await reader.ReadToEndAsync();
// Do stuff
}
You'll need to take your model binding out of the method parameters, and manually bind yourself:
public async Task<IActionResult> Index()
{
using var reader = new StreamReader(HttpContext.Request.Body);
// You shouldn't need this line anymore.
// reader.BaseStream.Seek(0, SeekOrigin.Begin);
// You now have the body string raw
var body = await reader.ReadToEndAsync();
// As well as a bound model
var request = JsonConvert.DeserializeObject<WebhookRequest>(body);
}
It's easy to forget this, and I've solved this issue before in the past, but just now had to relearn the solution. Hopefully my answer here will be a good reminder for myself...
Here's this answer as an extension method:
using System.IO;
using System.Text;
namespace System.Web.Http
{
public static class ApiControllerExtensions
{
public static string GetRequestBody(this ApiController controller)
{
using (var stream = new MemoryStream())
{
var context = (HttpContextBase)controller.Request.Properties["MS_HttpContext"];
context.Request.InputStream.Seek(0, SeekOrigin.Begin);
context.Request.InputStream.CopyTo(stream);
var requestBody = Encoding.UTF8.GetString(stream.ToArray());
return requestBody;
}
}
}
}

Getting raw POST data from Web API method

I have the following Web API method in an ApiController class:
public HttpResponseMessage Post([FromBody]byte[] incomingData)
{
...
}
I want incomingData to be the raw content of the POST. But it seems that the Web API stack attempts to parse the incoming data with the JSON formatter, and this causes the following code on the client side to fail:
new WebClient().UploadData("http://localhost:15134/api/Foo", new byte[] { 1, 2, 3 });
Is there a simple workaround for this?
For anyone else running into this problem, the solution is to define the POST method with no parameters, and access the raw data via Request.Content:
public HttpResponseMessage Post()
{
Request.Content.ReadAsByteArrayAsync()...
...
If you need the raw input in addition to the model parameter for easier access, you can use the following:
using (var contentStream = await this.Request.Content.ReadAsStreamAsync())
{
contentStream.Seek(0, SeekOrigin.Begin);
using (var sr = new StreamReader(contentStream))
{
string rawContent = sr.ReadToEnd();
// use raw content here
}
}
The secret is using stream.Seek(0, SeekOrigin.Begin) to reset the stream before trying to read the data.
The other answers suggest removing the input parameter, but that will break all of your existing code. To answer the question properly, an easier solution is to create a function that looks like this (Thanks to Christoph below for this code):
private async Task<String> getRawPostData()
{
using (var contentStream = await this.Request.Content.ReadAsStreamAsync())
{
contentStream.Seek(0, SeekOrigin.Begin);
using (var sr = new StreamReader(contentStream))
{
return sr.ReadToEnd();
}
}
}
and then get the raw posted data inside your web api call like so:
public HttpResponseMessage Post ([FromBody]byte[] incomingData)
{
string rawData = getRawPostData().Result;
// log it or whatever
return Request.CreateResponse(HttpStatusCode.OK);
}
In MVC 6 Request doesn't seem to have a 'Content' property. Here's what I ended up doing:
[HttpPost]
public async Task<string> Post()
{
string content = await new StreamReader(Request.Body).ReadToEndAsync();
return "SUCCESS";
}
I took LachlanB's answer and put it in a utility class with a single static method that I can use in all my controllers.
public class RawContentReader
{
public static async Task<string> Read(HttpRequestMessage req)
{
using (var contentStream = await req.Content.ReadAsStreamAsync())
{
contentStream.Seek(0, SeekOrigin.Begin);
using (var sr = new StreamReader(contentStream))
{
return sr.ReadToEnd();
}
}
}
}
Then I can call it from any of my ApiController's methods this way:
string raw = await RawContentReader.Read(this.Request);
Or simply
string rawContent = await Request.Content.ReadAsStringAsync();
make sure you run the above line on THE SAME THREAD before the original request is DISPOSED
Note: This is for ASP.NET MVC 5
in the controller below codeBlock gets the request body raw payload
string requestBody = string.Empty;
using (StreamReader reader = new StreamReader(Request.Body, Encoding.UTF8))
{
requestBody = await reader.ReadToEndAsync();
}
In .NET 6 i've done something like this:
JsonObject? requestBodyJSON = await context.Request.ReadFromJsonAsync<JsonObject>();
string rawRequestBody = requestBodyJSON.ToJsonString();
So basically read body as JSON and deserialize it in JsonObject, and after that just call .ToJsonString() and you get raw body data. Good thing is that from this JsonObject you can parse it to all basic types like Dictionary, List, Array, etc...

Categories