Best practice for interrupting streaming async action - asp.net-core

I am working on an asp.net core controller action that exposes stream for X seconds or until canceled depending on what happens first.
My first approach was to copy stream asynchronously into response, this way I get to use cancellation token rather easily by passing it into CopyToAsync.
[HttpGet]
[Route("getstream1")]
public async Task GetStream1(CancellationToken cancellationToken)
{
var stream = await new HttpClient().GetStreamAsync("http://localhost:4747/video");
var cst = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
var ct = cst.Token;
Response.OnStarting(() =>
{
cst.CancelAfter(5000);
return Task.CompletedTask;
});
Response.StatusCode = 200;
Response.ContentType = "video/stream";
await Response.StartAsync(ct);
try
{
await stream.CopyToAsync(Response.Body, 1024, ct);
}
catch (Exception ex)
{
if (ex is TaskCanceledException || ex is OperationCanceledException)
{
return;
}
throw;
}
}
My second idea was to return it as a file stream response and to fire and forget task which awaits given amount of time and aborts HttpContext or is cancelled by user via cancellation token.
[HttpGet]
[Route("getstream2")]
public async Task<IActionResult> GetStream2(CancellationToken cancellationToken)
{
var stream = await new HttpClient().GetStreamAsync("http://localhost:4747/video");
Task.Factory.StartNew(async () =>
{
await Task.Delay(5000, cancellationToken);
try
{
HttpContext.Abort();
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
}, cancellationToken).ContinueWith(task =>
{
// handle exception
}
);
return new FileStreamResult(stream, "video/stream");
}
My question is. Is one of those approaches better that the other in some way? Is there a better way to implement this? Is it ok to call Abort on HttpContext when I want to end my response?

Related

OnStarting cannot be set because the response has already started. - SignInManager

I can't get past this error in my simple code. Why does SignInManager keep throwing error:
OnStarting cannot be set because the response has already started.
I am calling my _SignIn method from blazor Onclick Event
public async Task _SignIn()
{
SignInByUsernamePasswordModel model = new();
model.Username = this.Username;
model.Password = this.Password;
try
{
await signInComponent._SignInByUsernamePassword(model);
}
catch (Exception ex)
{
}
}
public async Task<SignInResult> _SignInByUsernamePassword(SignInByUsernamePasswordModel model)
{
string username = model.Username;
string password = model.Password;
SignInResult signInResult2 = new SignInResult() ;
try
{
SignInResult signInResult = await _SignInManager.PasswordSignInAsync(model.Username, model.Password, false, false); ;
return signInResult;
}
catch (Exception ex){
*****Exception is thrown here!****
}
return signInResult2;
}
Cookie Settings were previously giving me another error until I fixed this code, however, this can't be the culprit. I can't imagine at what point it is throwing error i.e at the time of creating a cookie?
builder.Services.AddAuthenticationCore().AddAuthentication()
.AddCookie(IdentityConstants.ApplicationScheme, options =>
{
options.ExpireTimeSpan = TimeSpan.FromMinutes(20);
options.SlidingExpiration = true;
});
Researching Top errors in below Error Stack, led to the conclusion that the SigninManager would immediately start sending out the Response or reversing the Request flow to Response flow in Request/Response Pipeline. The cause behind it is, Signin Manager makes use of HTTPContext to create cookies.
at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpProtocol.ThrowResponseAlreadyStartedException(String value)
at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpProtocol.OnStarting(Func`2 callback, Object state)
This article describes https://startblazoring.com/Blog/SignInManager
that SigninManager cannot be used with Blazor directly.

How to detect when client has closed stream when writing to Response.Body in asp.net core

I'm trying to write an infinite length response body and detect when a client disconnects so I can stop writing. I'm used to getting socket exceptions or similar when a client closes the connection but that doesn't seem to be happening when writing directly to Response.Body. I can close the client applications and the server side just keeps on writing. I've included the relevant code below. It's entirely possible there is a better way to do it but this came to mind. Basically I have a live video feed which should go on forever. I'm writing to ResponseBody as chunked content (No content length, flushing after each video frame). The video frames are received via an event callback from elsewhere in the program so I'm subscribing to the events in the controller method and then forcing it to stay open with the await Task.Delay loop so the Response stream isn't closed. The callback for H264PacketReceived is formatting the data as a streaming mp4 file and writing it to the Response Stream. This all seems to work fine, I can play the live stream with ffmpeg or chrome, but when I close the client application I don't get an exception or anything. It just keeps writing to the stream without any errors.
public class LiveController : ControllerBase
{
[HttpGet]
[Route("/live/{cameraId}/{stream}.mp4")]
public async Task GetLiveMP4(Guid cameraId, int stream)
{
try
{
Response.StatusCode = 200;
Response.ContentType = "video/mp4";
Response.Headers.Add("Cache-Control", "no-store");
Response.Headers.Add("Connection", "close");
ms = Response.Body;
lock (TCPVideoReceiver.CameraStreams)
{
TCPVideoReceiver.CameraStreams.TryGetValue(cameraId, out cameraStream);
}
if (this.PacketStream == null)
{
throw new KeyNotFoundException($"Stream {cameraId}_{stream} not found");
}
else
{
connected = true;
this.PacketStream.H264PacketReceived += DefaultStream_H264PacketReceived;
this.PacketStream.StreamClosed += PacketStream_StreamClosed;
}
while(connected)
{
await Task.Delay(1000);
}
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
finally
{
connected = false;
this.PacketStream.H264PacketReceived -= DefaultStream_H264PacketReceived;
this.PacketStream.StreamClosed -= PacketStream_StreamClosed;
}
}
private bool connected = false;
private PacketStream PacketStream;
private Mp4File mp4File;
private Stream ms;
private async void PacketStream_StreamClosed(PacketStream source)
{
await Task.Run(() =>
{
try
{
Console.WriteLine($"Closing live stream");
connected = false;
ms.Close();
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
});
}
private async void DefaultStream_H264PacketReceived(PacketStream source, H264Packet packet)
{
try
{
if (mp4File == null && packet.IsIFrame)
{
mp4File = new Mp4File(null, packet.sps, packet.pps);
var _p = mp4File.WriteHeader(0);
await ms.WriteAsync(mp4File.buffer, 0, _p);
}
if (mp4File != null)
{
var _p = mp4File.WriteFrame(packet, 0);
var start = mp4File._moofScratchIndex - _p;
if (_p > 0)
{
await ms.WriteAsync(mp4File._moofScratch, start, _p);
await ms.FlushAsync();
}
}
return;
}
catch (Exception ex)
{
connected = false;
Console.WriteLine(ex.ToString());
}
}
Answering my own question.
When the client disconnects mvc core sets the cancellation token HttpContext.RequestAborted
By monitoring and/or using that cancellation token you can detect a disconnect and clean everything up.
That said, the entire design can be improved by creating a custom stream which encapsulates the event handling (producer/consumer). Then the controller action can be reduced to.
return File(new MyCustomStream(cameraId, stream), "video/mp4");
The File Method already monitors the cancellation token and everything works as you'd expect.

How to implement an identical POST like the regular PUT in an odata controller to update an entity

Given the following typical implementation of an ODataController's PUT method, how would I make the exact same method ALSO be available as a POST?
I am developing an OData end-point that will be called from an external system that I have no control over. It appears that that system implements the Update semantics (to tell my system to update an entity) wrongly by sending a POST with a uri key instead of using a PUT.
public async Task<IHttpActionResult> Put([FromODataUri] int key, Product update)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
if (key != update.Id)
{
return BadRequest();
}
db.Entry(update).State = EntityState.Modified;
try
{
await db.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!ProductExists(key))
{
return NotFound();
}
else
{
throw;
}
}
return Updated(update);
}
My first guess was to annotate the method with [AcceptVerbs("PUT", "POST")] to make the same exact method implementation be available as a POST, but that doesn't work. It's probably that the ODataConventionModelBuilder default setup doesn't know about this...
Ideally I'd like to keep the standards based PUT and the regular POST for inserts, but add a special post that is identical to the put but differs only in the verb.
Thanks
After finding some not so evident documentation on salesforce.com on odata endpoint implementation for External Data Source/External Objects, it became evident to me that salesforce.com tries to call a POST for Update semantics on the external object but also adds the X-HTTP-METHOD set as PATCH.
So, the solution was to implement the following class:
public class MethodOverrideHandler : DelegatingHandler
{
readonly string[] _methods = { "DELETE", "HEAD", "PUT", "PATCH", "MERGE" };
const string _header1 = "X-HTTP-Method-Override";
const string _header2 = "X-HTTP-Method";//salesforce special behavior???
protected override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{
// Check for HTTP POST with the X-HTTP-Method-Override header.
if (request.Method == HttpMethod.Post && request.Headers.Contains(_header1))
{
// Check if the header value is in our methods list.
var method = request.Headers.GetValues(_header1).FirstOrDefault();
if (_methods.Contains(method, StringComparer.InvariantCultureIgnoreCase))
{
// Change the request method.
request.Method = new HttpMethod(method);
}
}
else if (request.Method == HttpMethod.Post && request.Headers.Contains(_header2))
{
// Check if the header value is in our methods list.
var method = request.Headers.GetValues(_header2).FirstOrDefault();
if (_methods.Contains(method, StringComparer.InvariantCultureIgnoreCase))
{
// Change the request method.
request.Method = new HttpMethod(method);
}
}
return base.SendAsync(request, cancellationToken);
}
}
and register it in WebApiConfig.Register(HttpConfiguration config) as such:
config.MessageHandlers.Add(new MethodOverrideHandler());
Now, the non-odata compliant POST for salesforce update operations on the External Object will get delegated to the standards compliant odata implementation (in the ODataController) of PUT method I originally posted.
I hope that this helps someone in the future...
My approach would be to throw some more logic into the method to check and see if a record already exists in the database using update.Id then checking whether or not the data is null.
public async Task<IHttpActionResult> Put([FromODataUri] int key, Product update)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
//might have to get rid of this condition for the sake of new entries
//if (key != update.Id)
//{
//return BadRequest();
//}
try
{
//not sure what the name of your table is so I'm going to call it ProductTable
var foo = db.ProductTable.Where(p => p.Id == update.Id).FirstOrDefault();
if(foo == null)
{
db.Entry(update).State = EntityState.Added;
await db.SaveChangesAsync();
return StatusCode(HttpStatusCode.Accepted);
}
else
{
db.Entry(update).State = EntityState.Modified;
await db.SaveChangesAsync();
return Updated(update);
}
}
catch (DbUpdateConcurrencyException ex)
{
if (!ProductExists(key))
{
return NotFound();
}
else
{
throw new DbUpdateConcurrencyException(ex.Message);
}
}
}
EDIT
Just noticed the ProductExists method... I would take that out of the catch block and throw it into the try
//for Post, pass in a 0 for key's argument
public async Task<IHttpActionResult> Put([FromODataUri] int key, Product update)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
//might have to get rid of this condition for the sake of new entries
//if (key != update.Id)
//{
//return BadRequest();
//}
try
{
if (!ProductExists(key))
{
db.Entry(update).State = EntityState.Added;
await db.SaveChangesAsync();
return StatusCode(HttpStatusCode.Accepted);
}
else
{
db.Entry(update).State = EntityState.Modified;
await db.SaveChangesAsync();
return Updated(update);
}
}
catch (DbUpdateConcurrencyException ex)
{
throw new DbUpdateConcurrencyException(ex.Message);
}
}

Replacing response stream in ASP.NET Core 1.0 middleware

I want to write Custom Middleware in my ASP.NET Core 1.0 project which will replace original framework's Http Response Stream to my own, so I will be able to perform read / seek / write operations on it (first 2 are not possible on the original stream) in the further code i.e. in Actions or Filters.
I've started with the following code:
public class ReplaceStreamMiddleware
{
protected RequestDelegate NextMiddleware;
public ReplaceStreamMiddleware(RequestDelegate next)
{
NextMiddleware = next;
}
public async Task Invoke(HttpContext httpContext)
{
using (var responseStream = new MemoryStream())
{
var fullResponse = httpContext.Response.Body;
httpContext.Response.Body = responseStream;
await NextMiddleware.Invoke(httpContext);
responseStream.Seek(0, SeekOrigin.Begin);
await responseStream.CopyToAsync(fullResponse);
}
}
}
The problem with the following code is that sometimes the fullResponse stream is already closed at the time of invoking await responseStream.CopyToAsync(fullResponse); so it throws an exception Cannot access a closed Stream.
This weird behaviour is easy to observe when I load the page in the browser and then refresh, before it loads completely.
I would like to know:
why this happens?
how to prevent it?
is my solution a good idea or there is another way to replace response stream?
The exception doesn't come from your CopyToAsync. It's from one of your code's callers:
You're not restoring the original response stream in HttpContext. Therefore, whoever calls your middleware will get back a closed MemoryStream.
Here's some working code:
app.Use(async (httpContext, next) =>
{
using (var memoryResponse = new MemoryStream())
{
var originalResponse = httpContext.Response.Body;
try
{
httpContext.Response.Body = memoryResponse;
await next.Invoke();
memoryResponse.Seek(0, SeekOrigin.Begin);
await memoryResponse.CopyToAsync(originalResponse);
}
finally
{
// This is what you're missing
httpContext.Response.Body = originalResponse;
}
}
});
app.Run(async (context) =>
{
context.Response.ContentType = "text/other";
await context.Response.WriteAsync("Hello World!");
});

MVC aynchronous method

I am working on a MVC project that submits a request via a third party.
In my controller, I have a SubmitClaims() action that receive ajax request and then calls RunAsync(). RunAsync submits a request by using HttpClient.
I am not sure if I did a right thing here.
Also I have two version of SubmitClaims(), both work. But I don't know which version is better.
version 1
[HttpPost]
public async Task<string> SubmitClaims()
{
string result = "";
result = await RunAsync();
return result;
}
version 2 learn from Cannot implicitly convert type 'string' to 'System.Threading.Tasks.Task<string>'
[HttpPost]
public async Task<string> SubmitClaims()
{
return await Task.Run(() =>
{
return RunAsync();
});
}
static async Task<string> RunAsync()
{
string result = "Failed.";
using (var client = new HttpClient())
{
try
{
client.BaseAddress = new Uri("http://peter:8001/internal/uickpost");
client.DefaultRequestHeaders.Add("contenttype", "application/xml");
client.DefaultRequestHeaders.Add("hiconline.protocol.content.role", "REQUEST");
client.DefaultRequestHeaders.Add("hiconline.protocol.content.transactionid", "asdfsdf");
client.DefaultRequestHeaders.Add("hiconline.protocol.remote.contenttype", "TestDataType");
client.DefaultRequestHeaders.Add("hiconline.protocol.remote.mode", "P");
client.DefaultRequestHeaders.Host = "peter:8001";
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("text/plain"));
string opv = "Test Data";
HttpContent _content = new StringContent(opv);
_content.Headers.ContentType = new MediaTypeHeaderValue("application/xml");
_content.Headers.Add("contenttype", "TestDataType");
HttpResponseMessage response1 = await client.PostAsync(client.BaseAddress, _content);
if (response1.IsSuccessStatusCode)
{
Uri gizmoUrl = response1.Headers.Location;
result = response1.Content.ReadAsStringAsync().Result;
}
}
catch (Exception ex)
{
result = ex.Message;
}
return result;
}
}
Option 1 is better. RunAsync() already returns a task, so why create another one?
Even better would be return await RunAsync();. Even better would just be calling RunAsync directly, since the wrapper doesn't add anything.