The StoreBitmapImage is synchronous function to store bitmap images to disk. But when I compile, I get System.AggregateException, what am I doing wrong?
public static void StoreBitmapImage(string uri,string fileName)
{
HttpClient httpClient = new HttpClient();
IRandomAccessStream randomAccessStream = new InMemoryRandomAccessStream();
Stream responseStream = httpClient.GetStreamAsync(new Uri(uri)).Result;//Get BMP from web
Byte[] buffer = new byte[500];
int read;
do
{
read = responseStream.ReadAsync(buffer, 0, buffer.Length).Result;
randomAccessStream.WriteAsync(buffer.AsBuffer()).GetResults();
} while (read != 0);//convert responseStream into bytes
randomAccessStream.FlushAsync().GetResults();
randomAccessStream.Seek(0);
StorageFolder folder = ApplicationData.Current.RoamingFolder;//prepare folder
StorageFile file = null;
if (folder != null && buffer != null)
file = folder.CreateFileAsync(fileName, CreationCollisionOption.ReplaceExisting).GetResults();
else
return;
using (IRandomAccessStream rStream = file.OpenAsync(FileAccessMode.ReadWrite).GetResults())
using (IOutputStream oStream = rStream.GetOutputStreamAt(0))
{
DataWriter writer = new DataWriter(oStream);
writer.UnicodeEncoding = Windows.Storage.Streams.UnicodeEncoding.Utf8;
writer.WriteBytes(buffer);
writer.StoreAsync().GetResults();
}//write to folder
}
System.AggregateException means there are multiple Exceptions in your code. Can you explain what do you mean by How to treat an asynchronous method as a synchronous method?
Trying to do such operation synchronosly is not a good practice. Saving a file from web can be done quite easy in WinRT, take a look at https://winrtxamltoolkit.codeplex.com/SourceControl/changeset/view/0657c67a93d5#WinRTXamlToolkit%2fNet%2fWebFile.cs.
You are trying to invoke GetResults on the IAsyncOperation object you get as the result of invoking an async method. That will fail in most cases since the async operation did not complete when the async method returns. The AggregateException you get is probably the result of trying to get the result to early. You could use following code to invoke an async method synchronously:
Task<StorageFile> task = folder.CreateFileAsync(fileName,
CreationCollisionOption.ReplaceExisting).AsTask();
// Read the result which will synchronously wait for the async operation
StorageFile file = task.Result;
Since invoking async code synchronously is not allowed by the Microsoft guidance for Windows Store apps and an application using such code would probably fail the certification a better solution would be to make your method async as well and invoke the async methods using await. That of course involves that your method itself is invoked using await. But if you design a new app this should not be a problem at all.
In your defence: Invoking async code synchronously is probably okay for "proof of concept" apps or Desktop apps that make use of WinRT types (referencing the appropriate .winmd files).
One of the primary guidelines of async code is "async all the way down"; in other words, don't block on async code.
Keeping with this guideline, you should make your method async:
public static async Task StoreBitmapImage(string uri, string fileName)
{
HttpClient httpClient = new HttpClient();
IRandomAccessStream randomAccessStream = new InMemoryRandomAccessStream();
Stream responseStream = await httpClient.GetStreamAsync(new Uri(uri)); //Get BMP from web
Byte[] buffer = new byte[500];
int read;
do
{
read = await responseStream.ReadAsync(buffer, 0, buffer.Length);
await randomAccessStream.WriteAsync(buffer.AsBuffer());
} while (read != 0);//convert responseStream into bytes
await randomAccessStream.FlushAsync();
randomAccessStream.Seek(0);
StorageFolder folder = ApplicationData.Current.RoamingFolder;//prepare folder
StorageFile file = null;
if (folder != null && buffer != null)
file = await folder.CreateFileAsync(fileName, CreationCollisionOption.ReplaceExisting);
else
return;
using (IRandomAccessStream rStream = await file.OpenAsync(FileAccessMode.ReadWrite))
using (IOutputStream oStream = rStream.GetOutputStreamAt(0))
{
DataWriter writer = new DataWriter(oStream);
writer.UnicodeEncoding = Windows.Storage.Streams.UnicodeEncoding.Utf8;
writer.WriteBytes(buffer);
await writer.StoreAsync();
}//write to folder
}
Related
i have a zip folder in my wwwroot/files
how will i let the user download this file?
though about making a controller with something like this, but this is clearly wrong and it fails. It just does nothing even though it is reading the bytes and returning correctly
[HttpGet("DownloadOutput")]
public async Task<IActionResult> Invoice(string JobId)
{
string file = Directory.EnumerateFiles($"***PATH***\\{JobId}", "*.*",
SearchOption.AllDirectories)
.Where(n => Path.GetExtension(n) == ".zip").FirstOrDefault();
//converting file into bytes array
var dataBytes = System.IO.File.ReadAllBytes(file);
//adding bytes to memory stream
var dataStream = new MemoryStream(dataBytes);
HttpResponseMessage httpResponseMessage = new();
httpResponseMessage.Content = new StreamContent(dataStream);
httpResponseMessage.Content.Headers.ContentDisposition = new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment");
httpResponseMessage.Content.Headers.ContentDisposition.FileName = file;
httpResponseMessage.Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/octet-stream");
return Ok(httpResponseMessage);
}
calling the file from my blazor code like this.
var result = await Http.GetAsync($"Inventer/DownloadOutput?jobId=JobId}");
I currently am trying to upload a pdf file of size 260kb with Swagger UI and it doesnt work. If I try to do the same thing with a small 50kb Word file it works.
My controller code is:
[HttpPost()]
public async Task<IActionResult> Upload(IFormFile file)
{
var name = SanitizeFilename(file.FileName);
if (String.IsNullOrWhiteSpace(name))
{
throw new ArgumentException();
}
using (Stream stream = file.OpenReadStream())
{
await storage.Save(stream, name);
}
return Accepted();
}
My AzureBlobStorage class's save method is:
public async Task<Task> Save(Stream fileStream, string name)
{
var blobContainer = await GetBlobContainerAsync();
CloudBlockBlob blockBlob = blobContainer.GetBlockBlobReference(name);
var task = blockBlob.UploadFromStreamAsync(fileStream);
var success = task.IsCompletedSuccessfully;
return task;
//return blockBlob.UploadFromStreamAsync(fileStream);
}
Here is some of the debug windows:
This is from the controller of the word document:
This is from the controller of the PDF document:
Notice the red/pink lettering which is different.
This is from the AzureBlobStorage save method - word document:
This is from the AzureBlobStorage save method - pdf document:
I have read the IFormFile might not do continuous streaming but how do I know if that is the issue? And if it is, what is the preferred approach?
I am not following your logic here:
public async Task<Task> Save(Stream fileStream, string name)
{
var blobContainer = await GetBlobContainerAsync();
CloudBlockBlob blockBlob = blobContainer.GetBlockBlobReference(name);
var task = blockBlob.UploadFromStreamAsync(fileStream);
var success = task.IsCompletedSuccessfully;
return task;
//return blockBlob.UploadFromStreamAsync(fileStream);
}
This is the way it should be written:
public async Task Save(Stream fileStream, string name)
{
var blobContainer = await GetBlobContainerAsync();
CloudBlockBlob blockBlob = blobContainer.GetBlockBlobReference(name);
await blockBlob.UploadFromStreamAsync(fileStream);
}
You want to await for the task to finish here before you return.
Returning Task<Task> is slightly unorthodox and doesn't make sense for what you want to do here.
Also, keep in mind, if your file is really large, Kestrel server could give up on the request. There is a timeout in the range of around 90 seconds to complete the request. So, if uploading the file takes longer than 90 seconds, the caller could receive an error (but the upload will still finish).
Typically you will dump the file to the disk, then return an Accepted to the caller. Then post the file to a background queue to upload the file. More information about that here.
Sample code below to write a file stream to Response.Body in an ASP.NET Core middleware doesn't work (emits empty response):
public Task Invoke(HttpContext context)
{
context.Response.ContentType = "text/plain";
using (var fs = new FileStream("/valid-path-to-file-on-server.txt", FileMode.Open)
using (var sr = new StreamReader(fs))
{
context.Response.Body = sr.BaseStream;
}
return Task.CompletedTask;
}
Any ideas what could be wrong with this approach of directly setting the context.Response.Body?
Note: any next middleware in the pipeline is skipped for no further processing.
Update (another example): a simple MemoryStream assignment doesn't work either (empty response):
context.Response.Body = new MemoryStream(Encoding.UTF8.GetBytes(DateTime.Now.ToString()));
No. You can never do that directly.
Note that context.Response.Body is a reference to an object (HttpResponseStream) that is initialized before it becomes available in HttpContext. It is assumed that all bytes are written into this original Stream. If you change the Body to reference (point to) a new stream object by context.Response.Body = a_new_Stream, the original Stream is not changed at all.
Also, if you look into the source code of ASP.NET Core, you'll find the Team always copy the wrapper stream to the original body stream at the end rather than with a simple replacement(unless they're unit-testing with a mocked stream). For example, the SPA Prerendering middleware source code:
finally
{
context.Response.Body = originalResponseStream;
...
And the ResponseCachingMiddleware source code:
public async Task Invoke(HttpContext httpContext)
{
...
finally
{
UnshimResponseStream(context);
}
...
}
internal static void UnshimResponseStream(ResponseCachingContext context)
{
// Unshim response stream
context.HttpContext.Response.Body = context.OriginalResponseStream;
// Remove IResponseCachingFeature
RemoveResponseCachingFeature(context.HttpContext);
}
As a walkaround, you can copy the bytes to the raw stream as below:
public async Task Invoke(HttpContext context)
{
context.Response.ContentType = "text/plain";
using (var fs = new FileStream("valid-path-to-file-on-server.txt", FileMode.Open))
{
await fs.CopyToAsync(context.Response.Body);
}
}
Or if you like to hijack the raw HttpResponseStream with your own stream wrapper:
var originalBody = HttpContext.Response.Body;
var ms = new MemoryStream();
HttpContext.Response.Body = ms;
try
{
await next();
HttpContext.Response.Body = originalBody;
ms.Seek(0, SeekOrigin.Begin);
await ms.CopyToAsync(HttpContext.Response.Body);
}
finally
{
response.Body = originalBody;
}
The using statements in the question causes your stream and stream reader to be rather ephemeral, so they will both be disposed. The extra reference to the steam in "body" wont prevent the dispose.
The framework disposes of the stream after sending the response. (The medium is the message).
In net 6 I found I was getting console errors when I tried to do this e.g.:
System.InvalidOperationException: Response Content-Length mismatch: too many bytes written (25247 of 8863).
The solution was to remove the relevant header:
context.Response.Headers.Remove("Content-Length");
await context.Response.SendFileAsync(filename);
My requirement: write a middleware that filters all "bad words" out of a response that comes from another subsequent middleware (e.g. Mvc).
The problem: streaming of the response. So when we come back to our FilterBadWordsMiddleware from a subsequent middleware, which already wrote to the response, we are too late to the party... because response started already sending, which yields to the wellknown error response has already started...
So since this is a requirement in many various situations -- how to deal with it?
Replace a response stream to MemoryStream to prevent its sending. Return the original stream after the response is modified:
public async Task Invoke(HttpContext context)
{
bool modifyResponse = true;
Stream originBody = null;
if (modifyResponse)
{
//uncomment this line only if you need to read context.Request.Body stream
//context.Request.EnableRewind();
originBody = ReplaceBody(context.Response);
}
await _next(context);
if (modifyResponse)
{
//as we replaced the Response.Body with a MemoryStream instance before,
//here we can read/write Response.Body
//containing the data written by middlewares down the pipeline
//finally, write modified data to originBody and set it back as Response.Body value
ReturnBody(context.Response, originBody);
}
}
private Stream ReplaceBody(HttpResponse response)
{
var originBody = response.Body;
response.Body = new MemoryStream();
return originBody;
}
private void ReturnBody(HttpResponse response, Stream originBody)
{
response.Body.Seek(0, SeekOrigin.Begin);
response.Body.CopyTo(originBody);
response.Body = originBody;
}
It's a workaround and it can cause performance problems. I hope to see a better solution here.
A simpler version based on the code I used:
/// <summary>
/// The middleware Invoke method.
/// </summary>
/// <param name="httpContext">The current <see cref="HttpContext"/>.</param>
/// <returns>A Task to support async calls.</returns>
public async Task Invoke(HttpContext httpContext)
{
var originBody = httpContext.Response.Body;
try
{
var memStream = new MemoryStream();
httpContext.Response.Body = memStream;
await _next(httpContext).ConfigureAwait(false);
memStream.Position = 0;
var responseBody = new StreamReader(memStream).ReadToEnd();
//Custom logic to modify response
responseBody = responseBody.Replace("hello", "hi", StringComparison.InvariantCultureIgnoreCase);
var memoryStreamModified = new MemoryStream();
var sw = new StreamWriter(memoryStreamModified);
sw.Write(responseBody);
sw.Flush();
memoryStreamModified.Position = 0;
await memoryStreamModified.CopyToAsync(originBody).ConfigureAwait(false);
}
finally
{
httpContext.Response.Body = originBody;
}
}
Unfortunately I'm not allowed to comment since my score is too low.
So just wanted to post my extension of the excellent top solution, and a modification for .NET Core 3.0+
First of all
context.Request.EnableRewind();
has been changed to
context.Request.EnableBuffering();
in .NET Core 3.0+
And here's how I read/write the body content:
First a filter, so we just modify the content types we're interested in
private static readonly IEnumerable<string> validContentTypes = new HashSet<string>() { "text/html", "application/json", "application/javascript" };
It's a solution for transforming nuggeted texts like [[[Translate me]]] into its translation. This way I can just mark up everything that needs to be translated, read the po-file we've gotten from the translator, and then do the translation replacement in the output stream - regardless if the nuggeted texts is in a razor view, javascript or ... whatever.
Kind of like the TurquoiseOwl i18n package does, but in .NET Core, which that excellent package unfortunately doesn't support.
...
if (modifyResponse)
{
//as we replaced the Response.Body with a MemoryStream instance before,
//here we can read/write Response.Body
//containing the data written by middlewares down the pipeline
var contentType = context.Response.ContentType?.ToLower();
contentType = contentType?.Split(';', StringSplitOptions.RemoveEmptyEntries).FirstOrDefault(); // Filter out text/html from "text/html; charset=utf-8"
if (validContentTypes.Contains(contentType))
{
using (var streamReader = new StreamReader(context.Response.Body))
{
// Read the body
context.Response.Body.Seek(0, SeekOrigin.Begin);
var responseBody = await streamReader.ReadToEndAsync();
// Replace [[[Bananas]]] with translated texts - or Bananas if a translation is missing
responseBody = NuggetReplacer.ReplaceNuggets(poCatalog, responseBody);
// Create a new stream with the modified body, and reset the content length to match the new stream
var requestContent = new StringContent(responseBody, Encoding.UTF8, contentType);
context.Response.Body = await requestContent.ReadAsStreamAsync();//modified stream
context.Response.ContentLength = context.Response.Body.Length;
}
}
//finally, write modified data to originBody and set it back as Response.Body value
await ReturnBody(context.Response, originBody);
}
...
private Task ReturnBody(HttpResponse response, Stream originBody)
{
response.Body.Seek(0, SeekOrigin.Begin);
await response.Body.CopyToAsync(originBody);
response.Body = originBody;
}
A "real" production scenario may be found here: tethys logging middeware
If you follow the logic presented in the link, do not forget to addhttpContext.Request.EnableRewind() prior calling _next(httpContext) (extension method of Microsoft.AspNetCore.Http.Internal namespace).
I have a working windows 8 caching solution using DataContractSerializer that raises a XmlException "Unexpected end of file" only when the UI is being used 'quickly'.
public static class CachingData<T>
{
public static async void Save(T data, string filename, StorageFolder folder = null)
{
folder = folder ?? ApplicationData.Current.LocalFolder;
try
{
StorageFile file = await ApplicationData.Current.LocalFolder.CreateFileAsync(filename, CreationCollisionOption.ReplaceExisting);
using (IRandomAccessStream raStream = await file.OpenAsync(FileAccessMode.ReadWrite))
{
using (IOutputStream outStream = raStream.GetOutputStreamAt(0))
{
DataContractSerializer serializer = new DataContractSerializer(typeof(T));
serializer.WriteObject(outStream.AsStreamForWrite(), data);
await outStream.FlushAsync();
}
}
}
catch (Exception exc)
{
throw exc;
}
}
public static async System.Threading.Tasks.Task<T> Load(string filename, StorageFolder folder = null)
{
folder = folder ?? ApplicationData.Current.LocalFolder;
T data = default(T);
StorageFile file = await folder.GetFileAsync(filename);
using (IInputStream inStream = await file.OpenSequentialReadAsync())
{
DataContractSerializer serializer = new DataContractSerializer(typeof(T));
data = (T)serializer.ReadObject(inStream.AsStreamForRead());
}
return data;
}
}
e.g. user clicks on item in list CachingData.Load is called async via await, checks for FileNotEoundException and either loads the data from disk or from the network, serialising on completion.
After first loaded user selects another item in the list and cycle repeats.
The problem occurs when "After first loaded" becomes "does not wait for load" and the item selected is not available cached.
Not quite sure how to proceed or even how to debug, hoping that just ignoring will allow the app to continue(just withough the nice speed increase of caching)