I've just updated an app to version 1.1 and some code that was working started throwing exception saying
System.InvalidOperationException: OnStarting cannot be set, response has already started.
at Microsoft.AspNetCore.Server.Kestrel.Internal.Http.Frame.OnStarting(Func`2 callback, Object state)
at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.<InvokeNextResourceFilter>d__22.MoveNext()
It turns out that the issue was down to writing to Response.Body in my MVC actions. I've worked around this by downloading to a byte array and then returning File(array, contentType).
However I could not easily find any reason behind this - is this an error, desired functionality or just something wrong I'm doing?
@halter73 @Tratcher @kichalla - it's possible that this may be the issue:
Thanks for reporting this. It appears we're missing a check there. We'll look in to getting this fixed in the next patch release.
@nrandell Could you share more details about your app? Example the action code that you had earlier etc. The mentioned code in SaveTempDataFilter happens when executing an action result and if you were writing to the response stream directly, you might not have been returning an action result, so wanted to check. Also I did a quick verification and couldn't repro it.
This was my original code that worked before the upgrade. It basically downloads a blob from azure storage and streams that back to the client.
[HttpGet]
[Route("configuration/{id}")]
[ResponseCache(Duration = 10)]
public async Task ReadConfiguration(Guid id)
{
byte[] data;
if (!_cache.TryGetValue(id, out data))
{
var container = _blobClient.GetContainerReference(id.ToString("D"));
var blob = container.GetBlobReference("index.json");
using (var ms = new MemoryStream())
{
await blob.DownloadToStreamAsync(ms);
data = ms.ToArray();
_cache.Set(id, data, TimeSpan.FromSeconds(10));
}
}
Response.ContentType = "application/json";
await Response.Body.WriteAsync(data, 0, data.Length);
}
In trying to get it to work I made many changes, but ultimately ended up with
[HttpGet]
[Route("configuration/{id}")]
[ResponseCache(Duration = 10)]
public async Task<IActionResult> ReadConfiguration(Guid id)
{
byte[] data;
if (!_cache.TryGetValue(id, out data))
{
var container = _blobClient.GetContainerReference(id.ToString("D"));
var blob = container.GetBlobReference("index.json");
using (var ms = new MemoryStream())
{
await blob.DownloadToStreamAsync(ms);
data = ms.ToArray();
_cache.Set(id, data, TimeSpan.FromSeconds(10));
}
}
return File(data, "application/json");
}
Thanks @nrandell I was able to repro it. As @DamianEdwards mentioned, there should be a check here for response already started or not
Whats the recommended workaround for this? The error is causing my response to end prematurely.
Currently I'm removing the SaveTempDataAttribute from the filters on startup (The project doesn't use TempData) which works, but not convinced its a good fix
@scrouchman Following is a workaround that you can use:
Assuming that you are hitting this exception when you are trying to write to the response stream directly in an action for example, you can do the following:
```c#
class StreamActionResult : IActionResult
{
private readonly Func
public StreamActionResult(Func<HttpResponse, Task> writeToResponseFunc)
{
if(writeToResponseFunc == null)
{
throw new ArgumentNullException(nameof(writeToResponseFunc));
}
_writeToResponseFunc = writeToResponseFunc;
}
public Task ExecuteResultAsync(ActionContext context)
{
return _writeToResponseFunc(context.HttpContext.Response);
}
}
Now you can use this in a controller's action like below:
```c#
public IActionResult WriteToStreamFromAction()
{
return new StreamActionResult((response) =>
{
return response.WriteAsync("Hello World");
});
}
The reason this works is because the func provided in the action is not executed until the StreamActionResult is executed.
If your scenario is different than my assumption, could you share it?
Thanks for this. Your assumption is mostly right, though I'm writing bytes directly to Response.Body. This new action result does indeed fix the issue and is definitely nicer than removing the filter
@scrouchman you can also safely just return a stream from your action method. Or use ObjectResult and your stream as the payload if you want to set a content type. We have a formatter on by default that will copy a stream's contents to the body.
https://github.com/aspnet/Mvc/search?utf8=%E2%9C%93&q=StreamOutputFormatter
This patch bug is approved. Please use the normal code review process w/ a PR and make sure the fix is in the correct branch, then close the bug and mark it as done.
I discussed this with @rynowak and we think that the correct solution here is to move the OnStarting code into OnResourceExecuting so that the the action wont have a chance to start the Response Body before this handler is added.
Yup, sounds like a good plan.
Most helpful comment
@scrouchman Following is a workaround that you can use:
Assuming that you are hitting this exception when you are trying to write to the response stream directly in an action for example, you can do the following:
```c# _writeToResponseFunc;
class StreamActionResult : IActionResult
{
private readonly Func
}
The reason this works is because the func provided in the action is not executed until the StreamActionResult is executed.
If your scenario is different than my assumption, could you share it?