How to properly stream response in ASP.NET Core?
There is a controller like this (UPDATED CODE):
[HttpGet("test")]
public async Task GetTest()
{
HttpContext.Response.ContentType = "text/plain";
using (var writer = new StreamWriter(HttpContext.Response.Body))
await writer.WriteLineAsync("Hello World");
}
Firefox/Edge browsers show
Hello World
, while Chrome/Postman report an error:
The localhost page isn’t working
localhost unexpectedly closed the connection.
ERR_INCOMPLETE_CHUNKED_ENCODING
P.S. I am about to stream a lot of content, so I cannot specify Content-Length header in advance.
To stream a response that should appear to the browser like a downloaded file, you should use FileStreamResult:
[HttpGet]
public FileStreamResult GetTest()
{
var stream = new MemoryStream(Encoding.ASCII.GetBytes("Hello World"));
return new FileStreamResult(stream, new MediaTypeHeaderValue("text/plain"))
{
FileDownloadName = "test.txt"
};
}
#Developer4993 was correct that to have data sent to the client before the entire response has been parsed, it is necessary to Flush to the response stream. However, their answer is a bit unconventional with both the DELETE and the Synchronized.StreamWriter. Additionally, Asp.Net Core 3.x will throw an exception if the I/O is synchronous.
This is tested in Asp.Net Core 3.1:
[HttpGet]
public async Task Get()
{
Response.ContentType = "text/plain";
StreamWriter sw;
await using ((sw = new StreamWriter(Response.Body)).ConfigureAwait(false))
{
foreach (var item in someReader.Read())
{
await sw.WriteLineAsync(item.ToString()).ConfigureAwait(false);
await sw.FlushAsync().ConfigureAwait(false);
}
}
}
Assuming someReader is iterating either database results or some I/O stream with a large amount of content that you do not want to buffer before sending, this will write a chunk of text to the response stream with each FlushAsync().
For my purposes, consuming the results with an HttpClient was more important than browser compatibility, but if you send enough text, you will see a chromium browser consume the results in a streaming fashion. The browser seems to buffer a certain quantity at first.
Where this becomes more useful is with the latest IAsyncEnumerable streams, where your source is either time or disk intensive, but can be yielded a bit at at time:
[HttpGet]
public async Task<EmptyResult> Get()
{
Response.ContentType = "text/plain";
StreamWriter sw;
await using ((sw = new StreamWriter(Response.Body)).ConfigureAwait(false))
{
await foreach (var item in GetAsyncEnumerable())
{
await sw.WriteLineAsync(item.ToString()).ConfigureAwait(false);
await sw.FlushAsync().ConfigureAwait(false);
}
}
return new EmptyResult();
}
You can throw an await Task.Delay(1000) into either foreach to demonstrate the continuous streaming.
Finally, #StephenCleary 's FileCallbackResult works the same as these two examples as well. It's just a bit scarier with the FileResultExecutorBase from deep in the bowels of the Infrastructure namespace.
[HttpGet]
public IActionResult Get()
{
return new FileCallbackResult(new MediaTypeHeaderValue("text/plain"), async (outputStream, _) =>
{
StreamWriter sw;
await using ((sw = new StreamWriter(outputStream)).ConfigureAwait(false))
{
foreach (var item in someReader.Read())
{
await sw.WriteLineAsync(item.ToString()).ConfigureAwait(false);
await sw.FlushAsync().ConfigureAwait(false);
}
}
outputStream.Close();
});
}
It is possible to return null or EmptyResult() (which are equivalent), even when previously writing to Response.Body. It may be useful if the method returns ActionResult to be able to use all the other results aswell (e.g. BadQuery()) easily.
[HttpGet("test")]
public ActionResult Test()
{
Response.StatusCode = 200;
Response.ContentType = "text/plain";
using (var sw = new StreamWriter(Response.Body))
{
sw.Write("something");
}
return null;
}
I was wondering as well how to do this, and have found out that
the original question's code actually works OK on ASP.NET Core 2.1.0-rc1-final, neither Chrome (and few other browsers) nor JavaScript application do not fail with such endpoint.
Minor things I would like to add are just set StatusCode and close the response Stream to make the response fulfilled:
[HttpGet("test")]
public void Test()
{
Response.StatusCode = 200;
Response.ContentType = "text/plain";
using (Response.Body)
{
using (var sw = new StreamWriter(Response.Body))
{
sw.Write("Hi there!");
}
}
}
This question is a bit older, but I couldn't find a better answer anywhere for what I was trying to do. To send the currently buffered output to the client, you must call Flush() for each chunk of content you would like to write. Simply do the following:
[HttpDelete]
public void Content()
{
Response.StatusCode = 200;
Response.ContentType = "text/html";
// the easiest way to implement a streaming response, is to simply flush the stream after every write.
// If you are writing to the stream asynchronously, you will want to use a Synchronized StreamWriter.
using (var sw = StreamWriter.Synchronized(new StreamWriter(Response.Body)))
{
foreach (var item in new int[] { 1, 2, 3, 4, })
{
Thread.Sleep(1000);
sw.Write($"<p>Hi there {item}!</p>");
sw.Flush();
}
};
}
you can test with curl using the following command: curl -NX DELETE <CONTROLLER_ROUTE>/content
something like this might work:
[HttpGet]
public async Task<IActionResult> GetTest()
{
var contentType = "text/plain";
using (var stream = new MemoryStream(Encoding.ASCII.GetBytes("Hello World")))
return new FileStreamResult(stream, contentType);
}
Related
I had an REST API targeting AspNetCore 2.2 with an endpoint that allows download of some big json files. After migrating to AspNetCore 3.1 this code stopped working:
try
{
HttpContext.Response.StatusCode = (int)HttpStatusCode.OK;
HttpContext.Response.Headers.Add("Content-Type", "application/json");
using (var bufferedOutput = new BufferedStream(HttpContext.Response.Body, bufferSize: 4 * 1024 * 1024))
{
await _downloadService.Download(_applicationId, bufferedOutput);
}
}
catch (Exception ex)
{
_logger.LogError(ex, ex.Message);
}
This is the download method, that created the json that I want to return on the HttpContext.Response.Body:
public async Task Download(string applicationId, Stream output, CancellationToken cancellationToken = default(CancellationToken))
{
using (var textWriter = new StreamWriter(output, Constants.Utf8))
{
using (var jsonWriter = new JsonTextWriter(textWriter))
{
jsonWriter.Formatting = Formatting.None;
await jsonWriter.WriteStartArrayAsync(cancellationToken);
//write json...
await jsonWriter.WritePropertyNameAsync("Status", cancellationToken);
await jsonWriter.WriteValueAsync(someStatus, cancellationToken);
await jsonWriter.WriteEndArrayAsync(cancellationToken);
}
}
}
Now I get this exception: "Synchronous operations are disallowed in ASP.NET Core 3.0"
How can I change this code to work without using AllowSynchronousIO = true;
By calling FlushAsync and DisposeAsync before exiting using clause, you can stop synchronous operations that happen during writer dispositions. BufferedStream seems to have the synchronous write issue, how about control the buffer size in StreamWriter.
using (var streamWriter = new StreamWriter(context.Response.Body, Encoding.UTF8, 1024))
using (var jsonWriter = new JsonTextWriter(streamWriter))
{
jsonWriter.Formatting = Formatting.None;
await jsonWriter.WriteStartObjectAsync();
await jsonWriter.WritePropertyNameAsync("test");
await jsonWriter.WriteValueAsync("value " + new string('a', 1024 * 65));
await jsonWriter.WriteEndObjectAsync();
await jsonWriter.FlushAsync();
await streamWriter.FlushAsync();
await streamWriter.DisposeAsync();
}
In this way, you can make your code work with a small change without AllowSynchronousIO = true
AllowSynchronousIO option is disabled by default from .Net core 3.0.0-preview3 in (Kestrel, HttpSys, IIS in-process, TestServer) because these APIs are source of thread starvation and application hangs.
The option can be overridden on a per request for a temporary migration :
var allowSynchronousIoOption = HttpContext.Features.Get<IHttpBodyControlFeature>();
if (allowSynchronousIoOption != null)
{
allowSynchronousIoOption.AllowSynchronousIO = true;
}
You can find more info and follow the ASP.NET Core Issue Tracker : AllowSynchronousIO disabled in all servers
I currently am tasked with implementing some new functionality into an API that will eventually replace the existing code once everything (images) are transferred to the new system.
I have a controller which is currently not async
public HttpResponseMessage GetImage(long imageId)
{
//Old sync code
var imageStream = GetImage(imageId);
var response = Request.CreateResponse();
response.Content = new StreamContent(imageStream );
response.Content.Headers.ContentType = new MediaTypeHeaderValue("image/jpeg");
return response;
}
There is of course a bunch of synchronous calls to the DB and different resources already in this method.
If I made this method async and returned before the synchronous code in the new code, will the old code still function that same way and just be executed synchronously?
For example:
public async Task<HttpResponseMessage> GetImage(long imageId)
{
Stream imageStream = new MemoryStream();
var newImage = await IsNewImage(imageId);
if (newImage)
{
//Do async work
imageStream = await GetNewImage(imageId);
}
else
{
//Old sync code
imageStream = GetImage(imageId);
}
var response = Request.CreateResponse();
response.Content = new StreamContent(imageStream);
response.Content.Headers.ContentType = new MediaTypeHeaderValue("image/jpeg");
return response;
}
The reason for this is to try to minimize touching the old code that will be going away, and implementing the new code async. I just want to make sure introducing async functionality into this is not going to change the existing sync code? I do not have the option to create new controllers, it has to be changes to the existing controller in this instance.
I decided to convert the method that generates a PDF to asynchronous call. The asynchronous call doesn't produce the requested PDF and I have no idea why this is happening
The client who calls the async operation is as follows:
public QuotationResponse CreateQuotation(IQuotation quotation)
{
...
// Create Quotation PDF
_pdfWriter.GeneratePdfAsync(response);
return response;
}
The class responsible for generating the PDF is the following:
public class Writer
{
//... other class methods
public async Task GeneratePdfAsync(QuotationResponse response)
{
await new Task(() =>
{
var currentPath = System.Web.Hosting.HostingEnvironment.ApplicationPhysicalPath;
var formFile = Path.Combine(currentPath, Settings.Default.QUOTATION_TEMPLATE_PATH);
var newFile = Path.Combine(currentPath, Settings.Default.QUOTATION_PDF_PATH);
var reader = new PdfReader(formFile);
using (PdfStamper stamper = new PdfStamper(reader, new FileStream(newFile, FileMode.Create)))
{
....
// flatten form fields and close document
stamper.FormFlattening = true;
stamper.Close();
}
});
}
}
I suspect that I'm not doing something right with the async - await operation but don't know what. Could you please assist?
You should never use the Task constructor. Ever. It has absolutely no valid use cases. At all. For any kind of approach to any kind of problem. Full details on my blog.
Since there is no asynchronous work to do, you should just use synchronous methods:
public class Writer
{
//... other class methods
public void GeneratePdfAsync(QuotationResponse response)
{
var currentPath = System.Web.Hosting.HostingEnvironment.ApplicationPhysicalPath;
...
}
}
If you want to call this from a GUI application and don't want to block the UI thread, then you can call it on a background thread by using await Task.Run, as such:
QuotationResponse response = await Task.Run(() => CreateQuotation(quotation));
im trying to Reading/Writing Async Files for an Universal App in c#.
When i write and read a file for first time, it works... But when i retry it immeadiatly, there are two Errors: 1. UnauthorizedAccess 2. Handle with the OPLOCK has been closed
It seems that the methods arent finished yet and so the data is not free
(in my frame is a button which adds a new member to a List, then the list shall serialized in an XML data. When i reNavigate to that page, that XML sheet shall be deserialized back to that List, because the Content shall be displayed)
List<Immobilie> immoListe = new List<Immobilie>();
private const string FileName_ImmoObjects = "ImmoObjects.xml";
StorageFolder sFolder = Windows.Storage.ApplicationData.Current.LocalFolder;
IStorageFile latestImmoListFile;
public Startmenue()
{
this.InitializeComponent();
immoListe.Add(new Immobilie()); // for testing creating an XML first
immoListe[0].adresse = "Foo1";
immoListe.Add(new Immobilie());
immoListe[1].adresse = "Foo2";
WriteImmoListAsync();
ReadImmoListAsync(); // These two steps working
WriteImmoListAsync(); // everything more causes error
ReadImmoListAsync();
}
public async void WriteImmoListAsync()
{
try
{
IStorageFolder folder = await sFolder.CreateFolderAsync("Saves", CreationCollisionOption.OpenIfExists);
latestImmoListFile = await folder.CreateFileAsync(FileName_ImmoObjects, CreationCollisionOption.ReplaceExisting);
using (IRandomAccessStream stream = await latestImmoListFile.OpenAsync(FileAccessMode.ReadWrite))
using (Stream outputStream = stream.AsStreamForWrite())
{
DataContractSerializer serializer = new DataContractSerializer(typeof(List<Immobilie>));
serializer.WriteObject(outputStream, immoListe);
}
}
catch (Exception e)
{
var d = new MessageDialog(e.ToString());
await d.ShowAsync();
}
}
public async void ReadImmoListAsync()
{
int i = 0;
try
{
IStorageFolder folder = await sFolder.GetFolderAsync("Saves");
i = 1;
latestImmoListFile = await folder.GetFileAsync(FileName_ImmoObjects);
i = 2;
using (IRandomAccessStream stream = await latestImmoListFile.OpenAsync(FileAccessMode.Read))
{
i = 3;
using (Stream inputStream = stream.AsStreamForRead())
{
i = 4;
DataContractSerializer deserializer = new DataContractSerializer(typeof(List<Immobilie>));
i = 5;
immoListe = (List<Immobilie>)deserializer.ReadObject(inputStream);
}
}
}
catch (Exception e)
{
var d = new MessageDialog("Fehler I = " + i + "\n" + e.ToString());
await d.ShowAsync();
}
}
So what can i do and why is it so difficult??(normal I/O is easy-peasy).-.
As I describe in my MSDN article on async best practices, you should avoid async void:
public async Task WriteImmoListAsync();
public async Task ReadImmoListAsync();
Once your methods are properly async Task, then you can await them:
await WriteImmoListAsync();
await ReadImmoListAsync();
await WriteImmoListAsync();
await ReadImmoListAsync();
You can't start the methods again until you wait for them to complete. What that above code is trying to do is to write to a file, but while that's processing, it tries to open the file and write to it while the first method call hasn't completed. You need to wait for those method calls to finish before running them again - using the await keyword would be helpful here
It might be that the process writing/reading the file are still attached to the file. You might want to take a look at this pattern for async file read/write from Microsoft:
https://msdn.microsoft.com/en-ca/library/mt674879.aspx
Also, note that if the read and write are done from differents process, you're going to have to use a mutex. Here's a great explanation on how it works:
What is a good pattern for using a Global Mutex in C#?
I'm getting data from a web service.it took bit time. this is my controller
{
// THERE SO MANY FUNCTIONS BEFORE THIS.
//
try
{
string serviceUrl = "https://www.myserviceweb.com/assets/webhandler/details.ashx";
HttpWebRequest req = (HttpWebRequest)WebRequest.Create(serviceUrl);
HttpWebResponse resp = (HttpWebResponse)req.GetResponse();
StreamReader sr = new StreamReader(resp.GetResponseStream());
string result = sr.ReadToEnd();
sr.Close();
var rootResult = XElement.Parse(result);
System.Web.HttpContext.Current.Session["rootvss"] = rootResult;
}
catch (Exception ex)
{
Debug.WriteLine("error");
}
return View();
}
before I call to this function, there are lot functions. so altogether it took more time to load the index.what I want to do is
To run only above function asynchronously. how can I do that. I tried to use method like following.but I do not know how to use it.
public async Task webservce()
{
}
hope your help with this
if you want use Async , that structure like this :
public async Task<ActionResult> webservce()
{
return await _yourService.GetAllAsync(); // here you should call Async Methods like .ToListAsync ,...
}
All is explained on msdn here : https://msdn.microsoft.com/en-us/library/hh300224.aspx