Replacing Request/Response Body in asp.net core middleware - c#

I want to replace the request/response body in my middleware. Suppose if client sends Hello, I want to change it to Hola and similarly with response. I found the code that works but not to my requirement. My question is why this works and the other do not. It is actually the same code.
Working Code
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
var request = context.Request;
var stream = request.Body;// currently holds the original stream
var originalReader = new StreamReader(stream);
var originalContent = await originalReader.ReadToEndAsync();
var notModified = true;
try
{
if (originalContent != null)
{
//Master is some model name
var modifiedData = new Master() { Id = "Changed in Middleware", Email = "Changed" };
var json = JsonConvert.SerializeObject(modifiedData);
//json variable is just a string here
//modified stream
var requestData = Encoding.UTF8.GetBytes(json);
stream = new MemoryStream(requestData);
notModified = false;
}
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
if (notModified)
{
//putting original data
var requestData = Encoding.UTF8.GetBytes(originalContent);
stream = new MemoryStream(requestData);
}
request.Body = stream;
await next(context);
}
Not working Code But My Requirement
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
var request = context.Request;
var stream = request.Body;// currently holds the original stream
var originalReader = new StreamReader(stream);
var originalContent = await originalReader.ReadToEndAsync();
var notModified = true;
try
{
if (originalContent != null)
{
var json = "This is just a string as deserializing returns string";
//modified stream
var requestData = Encoding.UTF8.GetBytes(json);
stream = new MemoryStream(requestData);
notModified = false;
}
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
if (notModified)
{
//putting original data
var requestData = Encoding.UTF8.GetBytes(originalContent);
stream = new MemoryStream(requestData);
}
request.Body = stream;
await next(context);
}
So the working code pass on the changes to the controller, and has value Id ="Changed in middlware" and Email = "changed" but the not working code doesnt pass anything ... in the controller, argument has null and not the "This is just a string as deserializing returns string" value. I know this is not some magic happening. Am i missing something?

You need to convert string to jsontype.Try to change your code like this:
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
var request = context.Request;
var stream = request.Body;// currently holds the original stream
var originalReader = new StreamReader(stream);
var originalContent = await originalReader.ReadToEndAsync();
var notModified = true;
try
{
if (originalContent != null)
{
var str = "This is just a string as deserializing returns string";
//convert string to jsontype
var json = JsonConvert.SerializeObject(str);
//modified stream
var requestData = Encoding.UTF8.GetBytes(json);
stream = new MemoryStream(requestData);
notModified = false;
}
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
if (notModified)
{
//putting original data
var requestData = Encoding.UTF8.GetBytes(originalContent);
stream = new MemoryStream(requestData);
}
request.Body = stream;
await next(context);
}
TestRequestBody Action:
public IActionResult TestRequestBody([FromBody] string s)
{
return Ok();
}
result:

Related

Upload a list of images of type UIImage (Xamarin)

My app will retrieve a list of all images from a specific folder and attempt to upload them to a server via an API endpoint
Due to the above requirements, an image picker is not suited
Below is the method in the shared code that is passed a list of UIImages (I am trying to get it to work with just ios for now but the same scenario will eventually be applied to Android also)
The below does not work, as when I view the image on the server(AWS), it is in code format. It also says the content type is application/json on the server which I don't understand as I'm setting it to image/png
private async Task UploadImages(List<UIImage> images)
{
HttpClient client = new HttpClient();
var contentType = new MediaTypeWithQualityHeaderValue("image/png");
client.DefaultRequestHeaders.Accept.Add(contentType);
client.DefaultRequestHeaders.Add("Id-Token", Application.Current.Properties["id_token"].ToString());
foreach (var image in images)
{
try
{
string baseUrl = $"https://********/dev/ferret-test/media/team1/user1/device1/test1.png";
client.BaseAddress = new Uri(baseUrl);
//UploadModel uploadModel = new UploadModel
//{
// image_file = image.AsPNG()
//};
byte[] bArray = null;
Stream pst = image.AsPNG().AsStream();
using (MemoryStream ms = new MemoryStream())
{
ms.Position = 0;
pst.CopyTo(ms);
bArray = ms.ToArray();
}
//string stringData = JsonConvert.SerializeObject(bArray);
//var contentData = new StringContent(stringData,
//System.Text.Encoding.UTF8, "image/png");
//Byte[] myByteArray = new Byte[imageData.Length];
//System.Runtime.InteropServices.Marshal.Copy(imageData.Bytes, myByteArray, 0, Convert.ToInt32(imageData.Length));
var postRequest = new HttpRequestMessage(HttpMethod.Put, baseUrl)
{
Content = new ByteArrayContent(bArray)
};
var response = await client.SendAsync(postRequest);
response.EnsureSuccessStatusCode();
string stringJWT = response.Content.ReadAsStringAsync().Result;
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
}
}
I archived uploading multiple files to the server by using the following snippet, you can give it a try...
foreach (SQLiteAccess.Tables.Image image in images.OrderByDescending(x => x.Id)) //Here is the collection of all the file at once (Documents + Images)
{
int documentId = UploadImageToServerAndroid(image).Result;
// My other code implementation
.
.
.
}
private async Task<int> UploadImageToServerAndroid(SQLiteAccess.Tables.Image image)
{
int documentId = 0;
if (!Admins.ConnectedToNetwork()) return documentId;
MyFile = FileSystem.Current.GetFileFromPathAsync(image.Path).Result;
if (MyFile == null) return documentId;
Stream stream = MyFile.OpenAsync(FileAccess.Read).Result;
byte[] byteArray;
byteArray = new byte[stream.Length];
stream.Read(byteArray, 0, (int)stream.Length);
if( !image.IsDocument )
{
try
{
byteArray = DependencyService.Get<IImageUtilities>().CompressImage(byteArray); //Its custom code to compress the Image.
}
catch (Exception ex)
{
UoW.Logs.LogMessage(new LogDTO { Message = ex.Message, Ex = ex });
}
}
string url = "Your URL";
using (HttpClient client = new HttpClient(new RetryMessageHandler(new HttpClientHandler())))
{
try
{
client.DefaultRequestHeaders.Add(Properties.Resources.Authorization, Sessions.BearerToken);
client.DefaultRequestHeaders.Add("DocumentSummary", image.Comment);
client.DefaultRequestHeaders.Add("DocumentName", Path.GetFileName(image.Path));
MultipartFormDataContent multiPartContent = new MultipartFormDataContent();
ByteArrayContent byteContent = new ByteArrayContent(byteArray);
byteContent.Headers.ContentType = MediaTypeHeaderValue.Parse("image/jpeg");
multiPartContent.Add(byteContent, "image", Path.GetFileName(image.Path));
HttpResponseMessage response = await client.PostAsync(url, multiPartContent);
if (response.IsSuccessStatusCode && response.Content != null)
{
string jsonString = response.Content.ReadAsStringAsync().Result;
DocumentDTO result = JsonConvert.DeserializeObject<DocumentDTO>(jsonString);
documentId = result.DocumentId;
}
}
catch(Exception ex)
{
UoW.Logs.LogMessage( new LogDTO { Message = ex.Message, Ex = ex });
return documentId;
}
}
return documentId;
}
If documentid is 0(if something went wrong, for any of reason), it's marked as not uploaded & will try to upload it again when the internet is available.
If you need some more help, you can ask...:)
You could try to use MultipartFormDataContent and set content type to application/octet-stream.
You could refer the two links one and two.

System.Text.Json Cannot access a dispose jsonDocument

could someone help me how to fix this error?
I can't resolve this until now. I can't figure out where the problem is.
"Cannot access a disposed object. Object name: 'JsonDocument'"
I just started to use "Sytem.Text.Json" that's why I'm still learning and want to to know how to use it properly.
Thank you.
public static async Task<JsonElement> ParseJsonData(string api, CancellationToken ct)
{
clientHandler = new HttpClientHandler()
{
UseProxy = Proxy.IsUseProxy ? true : false,
Proxy = Proxy.IsUseProxy ? new WebProxy($"{Proxy.ProxyHost}:{Proxy.ProxyPort}") : null,
//ServerCertificateCustomValidationCallback = (sender, certificate, chain, errors) => { return true; },
// SslProtocols = SslProtocols.Tls12 | SslProtocols.Tls11 | SslProtocols.Tls
};
var uri = new Uri(api, UriKind.Absolute);
utils.SetConnection(uri);
client = new HttpClient(clientHandler);
using (var request = new HttpRequestMessage(HttpMethod.Get, uri))
{
AddRequestHeaders(request, uri);
return await ResponseMessage(request, ct);
}
}
private static async Task<JsonElement> ResponseMessage(HttpRequestMessage request, CancellationToken ct)
{
using (var response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, ct).ConfigureAwait(false))
{
ct.ThrowIfCancellationRequested();
using (var content = response.Content)
{
var stream = await content.ReadAsStreamAsync().ConfigureAwait(false);
var json = await ParseStream(stream, response);
return json.RootElement;
}
}
}
private static async Task<JsonDocument> ParseStream(Stream stream, HttpResponseMessage response)
{
if (stream == null || stream.CanRead == false)
{
return default;
}
HttpStatusCode status = response.StatusCode;
StatusCode.status = status.ToString();
StatusCode.value = (int)status;
using (var json = await JsonDocument.ParseAsync(stream).ConfigureAwait(false))
{
if (!response.IsSuccessStatusCode)
{
throw new ApiException()
{
Content = json.RootElement.ToString(),
StatusCode = status.ToString(),
value = (int)status,
};
}
return json;
}
}
UPDATE: (Here's what I've tried)
public static async Task<JsonDocument> ParseJsonData(string api, CancellationToken ct)
{
clientHandler = new HttpClientHandler()
{
UseProxy = Proxy.IsUseProxy ? true : false,
Proxy = Proxy.IsUseProxy ? new WebProxy($"{Proxy.ProxyHost}:{Proxy.ProxyPort}") : null,
ServerCertificateCustomValidationCallback = (sender, certificate, chain, errors) => { return true; }
// SslProtocols = SslProtocols.Tls12 | SslProtocols.Tls11 | SslProtocols.Tls
};
var uri = new Uri(api, UriKind.Absolute);
utils.SetConnection(uri);
client = new HttpClient(clientHandler);
using (var request = new HttpRequestMessage(HttpMethod.Get, uri))
{
AddRequestHeaders(request, uri);
return await ResponseMessage(request, ct);
}
}
private static async Task<JsonDocument> ResponseMessage(HttpRequestMessage request, CancellationToken ct)
{
using (var response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, ct).ConfigureAwait(false))
{
ct.ThrowIfCancellationRequested();
HttpStatusCode status = response.StatusCode;
using (var content = response.Content)
{
var stream = await content.ReadAsStreamAsync().ConfigureAwait(false);
if (stream == null || stream.CanRead == false) { return default; }
var options = new JsonDocumentOptions { AllowTrailingCommas = true };
var json = await JsonDocument.ParseAsync(stream, options).ConfigureAwait(false);
if (!response.IsSuccessStatusCode)
{
throw new ApiException()
{
Content = json.RootElement.ToString(),
StatusCode = status.ToString(),
value = (int)status,
};
}
return json;
}
}
}
public static async Task<test> GetData(string id, CancellationToken ct)
{
string API = $"https://www.test.com/api/videos/{id}";
using (var root = await MyClientHelper.ParseJsonData(API, ct))
{
var json = root.RootElement;
//here i can access the root and dispose after
return new test()
{
/////
}
}
}
It's the way using works. When you leave a using clause, the object is disposed. That's on purpose.
So consider your code:
using (var json = await JsonDocument.ParseAsync(stream).ConfigureAwait(false))
{
if (!response.IsSuccessStatusCode)
{
throw new ApiException()
{
Content = json.RootElement.ToString(),
StatusCode = status.ToString(),
value = (int)status,
};
}
return json; <------ the moment you return it you also dispose it
}
So when you try to access it outside, you are getting the error:
var json = await ParseStream(stream, response);
// here your object is already disposed
return json.RootElement;
Solution: before existing the parse function, return your json. The JsonDocument object should not be used outside the using clause.
You should NOT omit to dispose of the object as a workaround: https://learn.microsoft.com/en-us/dotnet/api/system.text.json.jsondocument?view=netcore-3.1
Failure to properly dispose this object will result in the memory not being returned to the pool, which will increase GC impact across various parts of the framework.
Luckily, there's the Clone() method. So instead of:
using JsonDocument doc = JsonDocument.Parse(jsonString);
return doc; // or return doc.RootElement;`
You can do this:
using JsonDocument doc = JsonDocument.Parse(jsonString);
var root = doc.RootElement.Clone();
return root;
"Gets a JsonElement that can be safely stored beyond the lifetime of the original JsonDocument."
https://learn.microsoft.com/en-us/dotnet/api/system.text.json.jsonelement.clone?view=net-5.0

How to receive a byte array and json in a Web API Controller

I need to receive a json object together with a byte array in a c# Web API application.
This is how I am sending the data:
public bool SendMedia(string method, Media media)
{
string filePath = Path.GetFullPath(Path.Combine(filesDirectory, media.FileName));
if (!File.Exists(filePath))
{
return false;
}
using (var client = new HttpClient())
using (var content = new MultipartContent() )
{
content.Add(new StringContent(JsonConvert.SerializeObject(media), Encoding.UTF8, "application/json"));
byte[] b = File.ReadAllBytes(filePath);
content.Add(new ByteArrayContent(b, 0, b.Length));
var response = client.PostAsync(new Uri(baseUri, method).ToString(), content).Result;
if (response.IsSuccessStatusCode)
return true;
return false;
}
}
And this is how I am trying to receive it:
// POST: api/Media
[ResponseType(typeof(Media))]
public HttpResponseMessage PostMedia(Media media, byte[] data)
{
int i = data.Length;
HttpResponseMessage response = new HttpResponseMessage();
if (!ModelState.IsValid)
{
response.StatusCode = HttpStatusCode.ExpectationFailed;
return response;
}
if (MediaExists(media.MediaId))
WebApplication1Context.db.Media.Remove(WebApplication1Context.db.Media.Where(p => p.MediaId == media.MediaId).ToArray()[0]);
WebApplication1Context.db.Media.Add(media);
try
{
WebApplication1Context.db.SaveChanges();
}
catch (DbUpdateException)
{
response.StatusCode = HttpStatusCode.InternalServerError;
return response;
throw;
}
response.StatusCode = HttpStatusCode.OK;
return response;
}
I don't know much about developing for web at the moment. Is sending a MultipartContent the right approach?
The framework can only bind one item from the body so what you are trying to attempt would not work.
Instead, read the request content just as you sent it and extract the parts.
[ResponseType(typeof(Media))]
public async Task<IHttpActionResult> PostMedia() {
if (!Request.Content.IsMimeMultipartContent()) {
return StatusCode(HttpStatusCode.UnsupportedMediaType); }
var filesReadToProvider = await Request.Content.ReadAsMultipartAsync();
var media = await filesReadToProvider.Contents[0].ReadAsAsync<Media>();
var data = await filesReadToProvider.Contents[1].ReadAsByteArrayAsync();
int i = data.Length;
if (!ModelState.IsValid) {
return StatusCode(HttpStatusCode.ExpectationFailed);
}
if (MediaExists(media.MediaId))
WebApplication1Context.db.Media.Remove(WebApplication1Context.db.Media.Where(p => p.MediaId == media.MediaId).ToArray()[0]);
WebApplication1Context.db.Media.Add(media);
try {
WebApplication1Context.db.SaveChanges();
} catch (DbUpdateException) {
return StatusCode(HttpStatusCode.InternalServerError);
}
return Ok(media);
}
Note also that in your original code you state the the action has a [ResponseType(typeof(Media))] but an object of that type was never returned. The above answer includes the model in the Ok(media) response.
The is a very simplified example. add any validation as necessary.

How to parse MultipartFormDataContent

I am writing a Web API service where I want to accept a file (image) and a serialized object (JSON) that contains key information about the image. Not having issues with the image part but when I add string content containing the deserialized object I am having issues in trying to determine which is which and act accordingly.
The client code looks like:
HttpClient client = new HttpClient();
MultipartFormDataContent content = new MultipartFormDataContent();
content.Add(new StreamContent(File.Open("c:\\MyImages\\Image00.jpg", FileMode.Open)), "image_file", "Image00.jpg");
ImageKeys ik = new ImageKeys { ImageId = "12345", Timestamp = DateTime.Now.ToString() };
JavaScriptSerializer js = new JavaScriptSerializer();
if (ik != null)
{
content.Add(new StringContent(js.Serialize(ik), Encoding.UTF8, "application/json"), "image_keys");
}
string uri = "http://localhost/MyAPI/api/MyQuery/TransferFile";
var request = new HttpRequestMessage()
{
RequestUri = new Uri(uri),
Method = HttpMethod.Post
};
request.Content = content;
string responseStr = "";
try
{
HttpResponseMessage result = client.SendAsync(request).Result;
string resultContent = string.Format("{0}:{1}", result.StatusCode, result.ReasonPhrase);
//
// Handle the response
//
responseStr = resultContent;
}
catch (Exception ex)
{
responseStr = ex.Message;
}
listBox1.Items.Add(responseStr);
So I include the image file first followed by a serialized object as StringContent. On the server side I am using the following code to parse the message.
HttpRequestMessage request = this.Request;
HttpResponseMessage ret = new HttpResponseMessage();
//
// Verify that this is an HTML Form file upload request
//
if (!request.Content.IsMimeMultipartContent())
{
throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
}
string root = "c:\\tmp\\uploads";
if (!Directory.Exists(root))
{
Directory.CreateDirectory(root);
}
//
// Create a stream provider for setting up output streams that saves the output under c:\tmp\uploads
// If you want full control over how the stream is saved then derive from MultipartFormDataStreamProvider
// and override what you need.
//
MultipartFormDataStreamProvider streamProvider = new MultipartFormDataStreamProvider(root);
try
{
await request.Content.ReadAsMultipartAsync(streamProvider);
foreach (var file in streamProvider.Contents)
{
if (file.Headers.ContentDisposition.Name == "image_file")
{
FileInfo finfo = new FileInfo(streamProvider.FileData.First().LocalFileName);
string destFile = Path.Combine(root, streamProvider.FileData.First().Headers.ContentDisposition.FileName.Replace("\"", ""));
//
// File.Move cannot deal with duplicate files
// Ensure that the target does not exist.
//
if (File.Exists(destFile))
{
File.Delete(destFile);
}
File.Move(finfo.FullName, destFile);
}
else if (file.Headers.ContentDisposition.Name == "image_keys")
{
// deserialize key class
string str = file.ReadAsStringAsync().Result;
JavaScriptSerializer js = new JavaScriptSerializer();
ImageKeys ik = js.Deserialize<ImageKeys>(str);
}
}
ret.StatusCode = HttpStatusCode.OK;
ret.Content = new StringContent("File uploaded.");
}
catch (Exception ex)
{
ret.StatusCode = HttpStatusCode.UnsupportedMediaType;
ret.Content = new StringContent("File upload failed.");
}
return ret;
The foreach loop tries to process each item in the multipart content as a file but I want to treat the various content types separately but it is not clear to me how they are delineated.
Thanks
You can cast Content to MultipartFormDataContent and iterate thru it. Based on content type you can read it as a file or string. Example for string content type:
var dataContents = request.Content as MultipartFormDataContent;
foreach (var dataContent in dataContents)
{
var name = dataContent.Headers.ContentDisposition.Name;
var value = dataContent.ReadAsStringAsync().Result;
...
}

How to return a Task<T> in an async method

I have this method in my Windows Phone 8 app where I get some data from a url
public async static Task<byte[]> getData(string url)
{
HttpClient client = null;
HttpResponseMessage response = null;
Stream stream = null;
byte[] dataBytes = null;
bool error = false;
try
{
Uri uri = new Uri(url);
client = new HttpClient();
response = await client.GetAsync(uri);
response.EnsureSuccessStatusCode();
stream = await response.Content.ReadAsStreamAsync();
dataBytes = getDataBytes(stream);
if (dataBytes == null)
{
error = true;
}
else if (dataBytes.Length == 0)
{
error = true;
}
}
catch (HttpRequestException )
{
}
if (error)
{
return getData(url); // this is where the issue is
}
return dataBytes;
}
But since the method is an async one, the return type cannot be a Task, like I have done on the line return getData(url); since getData(string) returns Task. Any ideas on how I can rewrite this to make it work?
Awaiting the result of getData may do the trick. Still, I strongly recommand you to rewrite your method with a loop, rather than recursively call the method again. It makes it hard to read, and may lead to unforeseen issues.
public async static Task<byte[]> getData(string url)
{
bool success = false;
byte[] dataBytes = null;
while (!success)
{
try
{
Uri uri = new Uri(url);
var client = new HttpClient();
var response = await client.GetAsync(uri);
response.EnsureSuccessStatusCode();
var stream = await response.Content.ReadAsStreamAsync();
dataBytes = getDataBytes(stream);
success = dataBytes != null && dataBytes.Length > 0;
}
catch (HttpRequestException)
{
}
}
return dataBytes;
}
you can get around the compile error by adding changing the return to the following :
if (error)
{
return await getData(url); // this is where the issue is
}
I hope you do realize that this code will keep on looping as long as no data is returned? having many clients like this could easily overload your server.

Categories