Unhandled TaskCancelledException when request is aborted by client in ASP.NET Core MVC - asp.net-core

ASP.NET Core MVC provides approach to handle situations when request is aborted by the client. Framework passes CancellationToken that can be accessed via HttpContext.RequestAborted property, or can be bound into controller's action.
In terms of .NET, this approach looks pretty clear, consistent and natural. What doesn't look natural and logical to me is that framework, which initializes, populates and 'cancels' this access token doesn't handle appropriate TaskCancelledException.
So, if
I create a new project from the "ASP.NET Core Web API" template,
Add an action with CancellationToken argument, something like this:
[HttpGet("Delay")]
public async Task<IActionResult> GetDelayAsync(CancellationToken cancellationToken)
{
await Task.Delay(30_000, cancellationToken);
return Ok();
}
And then send request via postman and cancel it before completion
Then the application records this error in the log:
fail: Microsoft.AspNetCore.Server.Kestrel[13]
Connection id "0HMCHB3SQHQQR", Request id "0HMCHB3SQHQQR:00000002": An unhandled exception was thrown by the application.
System.Threading.Tasks.TaskCanceledException: A task was canceled.
<<>>
My expectation is that exception in this particular case exception is handled and absorbed by asp.net, with no "fail" records in logs.
Error-wise behavior should be the same as with synchronous action:
[HttpGet("Delay")]
public IActionResult GetDelay()
{
Thread.Sleep(30_000);
return Ok();
}
This implementation doesn't record any errors in logs when request is aborted.
Technically exception can be absorbed and hided by exception filter, but this approach looks weird and overcomplicated. At least because this is routine situation, and writing code for any application doesn't make any sense. Also, I want to hide "exception caused by aborted request when client isn't interested in response" and behavior related to other unhandled TaskCancelledException should remain as is...
I'm wondering how and when it's supposed to properly handle and absorb exception when request is aborted by client?
There are number of articles how to access cancellation token, however I was unable to find any explicit statement that answers my question.

From https://learn.microsoft.com/en-us/dotnet/standard/parallel-programming/task-cancellation:
If you are waiting on a Task that transitions to the Canceled state, a
System.Threading.Tasks.TaskCanceledException exception (wrapped in an
AggregateException exception) is thrown. Note that this exception
indicates successful cancellation instead of a faulty situation.
Therefore, the task's Exception property returns null.
That's why this block does not throw (there's no task awaited that is tied to a cancellation token):
[HttpGet("Delay")]
public IActionResult GetDelay(CancellationToken cancellationToken)
{
Thread.Sleep(30_000);
return Ok();
}

I stumbled upon the same issue you described in your post. Genuinely speaking, middleware might not be the worst approach. I found good example in Ocelot API gateway on Github.
Pay attention it will return HTTP 499 Client Closed Request afterwards.
You may modify it in way that no logs will be written.
/// <summary>
/// Catches all unhandled exceptions thrown by middleware, logs and returns a 500.
/// </summary>
public class ExceptionHandlerMiddleware : OcelotMiddleware
{
private readonly RequestDelegate _next;
private readonly IRequestScopedDataRepository _repo;
public ExceptionHandlerMiddleware(RequestDelegate next,
IOcelotLoggerFactory loggerFactory,
IRequestScopedDataRepository repo)
: base(loggerFactory.CreateLogger<ExceptionHandlerMiddleware>())
{
_next = next;
_repo = repo;
}
public async Task Invoke(HttpContext httpContext)
{
try
{
httpContext.RequestAborted.ThrowIfCancellationRequested();
var internalConfiguration = httpContext.Items.IInternalConfiguration();
TrySetGlobalRequestId(httpContext, internalConfiguration);
Logger.LogDebug("ocelot pipeline started");
await _next.Invoke(httpContext);
}
catch (OperationCanceledException) when (httpContext.RequestAborted.IsCancellationRequested)
{
Logger.LogDebug("operation canceled");
if (!httpContext.Response.HasStarted)
{
httpContext.Response.StatusCode = 499;
}
}
catch (Exception e)
{
Logger.LogDebug("error calling middleware");
var message = CreateMessage(httpContext, e);
Logger.LogError(message, e);
SetInternalServerErrorOnResponse(httpContext);
}
Logger.LogDebug("ocelot pipeline finished");
}
private void TrySetGlobalRequestId(HttpContext httpContext, IInternalConfiguration configuration)
{
var key = configuration.RequestId;
if (!string.IsNullOrEmpty(key) && httpContext.Request.Headers.TryGetValue(key, out var upstreamRequestIds))
{
httpContext.TraceIdentifier = upstreamRequestIds.First();
}
_repo.Add("RequestId", httpContext.TraceIdentifier);
}
private void SetInternalServerErrorOnResponse(HttpContext httpContext)
{
if (!httpContext.Response.HasStarted)
{
httpContext.Response.StatusCode = 500;
}
}
private string CreateMessage(HttpContext httpContext, Exception e)
{
var message =
$"Exception caught in global error handler, exception message: {e.Message}, exception stack: {e.StackTrace}";
if (e.InnerException != null)
{
message =
$"{message}, inner exception message {e.InnerException.Message}, inner exception stack {e.InnerException.StackTrace}";
}
return $"{message} RequestId: {httpContext.TraceIdentifier}";
}
}
If you use multiple middlewares it should be first on the invocation list (It's .NET 6)
app.UseMiddleware(typeof(ExceptionHandlerMiddleware));
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();

Related

in asp.net core, should i still use try-catch if i already have exception filter?

I'm talking about general functions in controller repositories: database stuff and logic.
I presume that a-sync calls to-from other api should always be with try catch in case those fail.
But about all the other stuff, should i still use try-catch or just one exception filter and be done with it??
I'm not sure what the common architecture use in the industry.
thanks
In my preference and experience Yes, even if you have exception filters in ASP.NET Core, you should still use try-catch blocks. While useful, exception filters might not be able to handle every possible error circumstance in your application.
Try-catch blocks let you handle failures that might happen inside the scope of a particular method or block of code as well as more precisely handle exceptions.
Additionally, they let you to capture exceptions and carry out further processing or logging before re-throwing the exception for handling by a higher-level handler, such an exception filter.
It is possible to handle errors globally with the built-in middleware. So you need to create a class
namespace GlobalErrorHandling.Extensions
{
public static class ExceptionMiddlewareExtensions
{
public static void ConfigureExceptionHandler(this IApplicationBuilder app,
ILoggerManager logger)
{
app.UseExceptionHandler(appError =>
{
appError.Run(async context =>
{
context.Response.StatusCode = (int)HttpStatusCode
.InternalServerError;
context.Response.ContentType = "application/json";
var contextFeature = context.Features
.Get<IExceptionHandlerFeature>();
if(contextFeature != null)
{
logger.LogError($"Ooops. : {contextFeature.Error}");
await context.Response.WriteAsync(new ErrorDetails()
{
StatusCode = context.Response.StatusCode,
Message = "Internal Server Error."
}.ToString());
}
});
});
}
}
}
and
public void Configure(IApplicationBuilder app, IWebHostEnvironment env,
ILoggerManager logger)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.ConfigureExceptionHandler(logger);
// ... the other code is omitted for the brevity
}
Read more in this great article.

Core 7 - Api error handling, model state validation + UseExceptionhandler

I am currently working on implementing some Apis using swagger/swashbuckle in net core 7 and implementing some error handling, I've gone down the route of using an exception handler. With separate endpoints from dev/prod.
E.g. Startup.cs
if (env.IsDevelopment())
{
...details ommited
app.UseExceptionHandler("/dev-error");
}
else
{
...details ommited
app.UseExceptionHandler("/error");
}
ErrorController.cs
[AllowAnonymous]
[ApiExplorerSettings(IgnoreApi = true)]
public class ErrorController : Controller
{
private ILogger _logger;
public ErrorController(ILogger logger)
{
_logger = logger;
}
[Route("dev-error")]
public IAttempt DevError()
{
var context = HttpContext.Features.Get<IExceptionHandlerFeature>();
var exception = context.Error;
return Attempt.Fail(exception);
}
[Route("error")]
public IAttempt Error()
{
var context = HttpContext.Features.Get<IExceptionHandlerFeature>();
var exception = context.Error;
_logger.Log(LogLevel.Error, exception, exception.Message);
switch (exception)
{
case UnauthorizedAccessException:
Response.StatusCode = (int) HttpStatusCode.Unauthorized;
return Attempt.Fail("Unauthorised");
default:
Response.StatusCode = (int) HttpStatusCode.InternalServerError;
return Attempt.Fail("Generic Error");
}
}
}
The idea is that all responses are of IAttempt, so that the FE user can check if its succeeded etc. and whether to handle the result or exception in a user friendly way.
This has been working great up until now when I've been implementing Api's that require the model to be validated. I wanted to amend the IAttempt class to provide modelstate feedback, however I have tried many approaches and cant seem to get modelstate validation flow through the exception handler.
I wanted to implement a custom ValidationException that contains the errors which is then handled in these controllers. But when an exception is thrown in either an IActionFilter or when overriding the InvalidModelStateResponseFactory the exception isn't caught by the exception handler.
Is there a work around? Am I missing something?
Alternatively I could define a InvalidModelStateResponseFactory that returns a similar model(IAttempt), but it would be nice for Failed requests to be handled in one place.
Cheers in advance
I think you can make the InvalidModelStateResponseFactory redirect to the ErrorController, sending the required data to create your response
According to your description, I suggest you could consider using the customer action filter to achieve your requirement.
Inside the custom action filter, we could get the model state's results, then you could throw the exception inside it.
More details, you could refer to below codes:
1.Create the custom action filter:
public class CustomValidationActionFilter : IActionFilter
{
public void OnActionExecuted(ActionExecutedContext context)
{
if (!context.ModelState.IsValid)
{
var errorList = context.ModelState.Values
.SelectMany(m => m.Errors)
.Select(m => m.ErrorMessage)
.ToList();
throw new Exception();
}
}
public void OnActionExecuting(ActionExecutingContext context) { }
}
2.Inside the program.cs
builder.Services.AddControllersWithViews(options =>
{
options.Filters.Add(new CustomValidationActionFilter());
});
Then if it thrown the exception, it will go to the controller's error action method, since you set the global exception handler.
I was unnecessarily over complicating things so I have dropped what I attempted to do as in theory responses should be handled accordingly to their response status code rather then the object thats passed in.

Blazor WASM controller: read request body causes the IIS process to crash

So I am trying to simply read the body (with string content) in a Blazor WASM ApiController. My code on the server-side:
[AllowAnonymous]
[ApiController]
[Route("[controller]")]
public class SmartMeterDataController : ControllerBase
{
[HttpPost("UploadData")]
public async void UploadData()
{
string body = null;
if (Request.Body.CanRead && (Request.Method == HttpMethods.Post || Request.Method == HttpMethods.Put))
{
Request.EnableBuffering();
Request.Body.Position = 0;
body = await new StreamReader(Request.Body).ReadToEndAsync();
}
}
}
My app builder in Program.cs is pretty much out of the box:
//enable REST API controllers
var mvcBuillder = builder.Services.AddMvcCore(setupAction: options => options.EnableEndpointRouting = false).ConfigureApiBehaviorOptions(options => //activate MVC and configure error handling
{
options.InvalidModelStateResponseFactory = context => //error 400 (bad request)
{
JsonApiErrorHandler.HandleError400BadRequest(context);
return new Microsoft.AspNetCore.Mvc.BadRequestObjectResult(context.ModelState);
};
});
builder.Services.AddControllersWithViews();
builder.Services.AddRazorPages();
...
app.UseRouting();
app.UseMvcWithDefaultRoute();
app.MapRazorPages();
app.MapControllers();
The request body looks like this:
{"api_key":"K12345667565656", "field1":"1.10", "field2":"0.76",
"field3":"0.65", "field4":"455", "field5":"0", "field6":"1324",
"field7":"433761", "field8":"11815" }
Yes, this is JSON. No, I don't want to parse it with [FromBody] or similar.
POSTing to this endpoint causes the following exception (as seen in the Windows event viewer thingy):
Application: w3wp.exe
CoreCLR Version: 6.0.1222.56807
.NET Version: 6.0.12
Description: The process was terminated due to an unhandled exception.
Exception Info: System.ObjectDisposedException: Cannot access a disposed object.
Object name: 'HttpRequestStream'.
at Microsoft.AspNetCore.Server.IIS.Core.HttpRequestStream.ValidateState(CancellationToken cancellationToken)
at Microsoft.AspNetCore.Server.IIS.Core.HttpRequestStream.ReadAsync(Memory`1 destination, CancellationToken cancellationToken)
at Microsoft.AspNetCore.Server.IIS.Core.WrappingStream.ReadAsync(Memory`1 destination, CancellationToken cancellationToken)
at Microsoft.AspNetCore.WebUtilities.FileBufferingReadStream.ReadAsync(Memory`1 buffer, CancellationToken cancellationToken)
at System.IO.StreamReader.ReadBufferAsync(CancellationToken cancellationToken)
at System.IO.StreamReader.ReadToEndAsyncInternal()
After that, a second error is always logged. It states something like it is described here.
Note that it's usually not the first, but the second or third POST that causes this. After this, the error keeps happening with every POST and after a short while the application stops working and the Windows Server 2019 need to be rebooted.
According to the internet, the code should work. Anyone have a guess why it doesn't?
I use this HttpContext extension method to read the request body and cache it in the context in case needed later in the pipeline. It works for me.
Notice the condition around EnableBuffering. Perhaps adding that condition to your code will help.
public static async Task<string> GetRequestBodyAsStringAsync(
this HttpContext httpContext)
{
if (httpContext.Items.TryGetValue("BodyAsString", out object? value))
return (string)value!;
if (!httpContext.Request.Body.CanSeek)
{
// We only do this if the stream isn't *already* rewindable,
// as EnableBuffering will create a new stream instance
// each time it's called
httpContext.Request.EnableBuffering();
}
httpContext.Request.Body.Position = 0;
StreamReader reader = new(httpContext.Request.Body, Encoding.UTF8);
string bodyAsString = await reader.ReadToEndAsync().ConfigureAwait(false);
httpContext.Request.Body.Position = 0;
httpContext.Items["BodyAsString"] = bodyAsString;
return bodyAsString;
}
EDIT ...
Possibly, your issue could also be related to fact your controller method is returning a void instead of Task?
Finally, I found the original article I used for my extension method. Interestingly, if you that extension method for the FIRST time after model-binding then it won't work (in my project I do call it from middleware).
https://markb.uk/asp-net-core-read-raw-request-body-as-string.html
Adding:
public class EnableRequestBodyBufferingMiddleware
{
private readonly RequestDelegate _next;
public EnableRequestBodyBufferingMiddleware(RequestDelegate next) =>
_next = next;
public async Task InvokeAsync(HttpContext context)
{
context.Request.EnableBuffering();
await _next(context);
}
}
and
app.UseMiddleware<EnableRequestBodyBufferingMiddleware>();
may therefore also help.

How to a I redirect to Custom Error Handler Page

I want a generic error page for all application errors.
I have followed the guidelines to create a custom error handler in ASP.NET core and this catches the errors as expected. However, I cannot see how to redirect to a generic error handling the page. Examples seemed to be focused on Web API, not UI.
I have the following custom error handling code
private static Task HandleExceptionAsync(HttpContext context, Exception exception)
{
context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
int exceptionId = ExceptionManager.Publish(exception);
return context.Response.WriteAsync(new ErrorViewModel()
{
ExceptionId = exceptionId
}.ToString());
}
The exception details are logged to a database and return an Id. I have a controller action that displays the Id so the users can report it.
How do I redirect to my error view?
In Startup.cs method you need to call ExceptionHandlerMiddleware like below.
app.UseMiddleware(typeof(ExceptionHandlerMiddleware));
create a middleware class and write below code
public class ExceptionHandlerMiddleware
{
private readonly RequestDelegate next;
public ExceptionHandlerMiddleware(RequestDelegate next)
{
this.next = next;
}
public async Task Invoke(HttpContext context)
{
try
{
//Write you logic
}
catch (Exception ex)
{
await HandleExceptionAsync(context, ex);
}
}
private static async Task HandleExceptionAsync(HttpContext context, Exception exception)
{
context.Response.StatusCode = 500;
if (IsRequestAPI(context))
{
//when request api
context.Response.ContentType = "application/json";
await context.Response.WriteAsync(JsonConvert.SerializeObject(new
{
State = 500,
message = exception.Message
}));
}
else
{
//when request page
context.Response.Redirect("/Home/Errorpage");
}
}
}
Middleware is "waterfalled" down through until either all have been executed, or one stops execution (in the case of our exception handling, we'll be writing ours so it stops the execution. More on that later).
The first things passed to your middleware is a request delegate. This is a delegate that takes the current HttpContext object and executes it. Your middleware saves this off upon creation and uses it in the Invoke() step.
Invoke() is where the work is done. Whatever you want to do to the request/response as part of your middleware is done here. Some other usages for middleware might be to authorize a request based on a header or inject a header into the request or response. For more examples, check out the Middleware documentation.

Nsb for certain type of exceptions: skip SLR + ability to ignore error queue

I want to have a central place to implement exceptions handling logic for specific type of exception.
If specific exception type occurs, I'd like to be able to run one of the following depending on internal configuration:
send message to error queue immediately without further second level retries;
hide the message, not sending it to both processing queue or error queue;
I've found this topic which covers first case but not second one, as a message would be put into error queue if we return TimeSpan.MinValue:
NServiceBus error handling
So how could I implement 2nd case? better both to be be implemented in one place, one class
Prior to version 6 of NServiceBus, you could use IManageMessageFailures to manage message failures. You can handle the case of a serialization exception or - more relevant to your problem at hand - when a message can not be handled gracefully after first-level retries are attempted.
Here's how to implement a custom FaultManager that'd ignore exceptions of certain type or send failed messages with other errors back to the error queue. Note that the First-Level retires still happens and this kicks-in instead of Second-Level retry.
public class IssueOrder : ICommand
{
public bool NotFound { get; set; }
public bool HasFaulted { get; set; }
}
public class OrderHandler : IHandleMessages<IssueOrder>
{
public void Handle(IssueOrder message)
{
if(message.NotFound)
throw new OrderNotFoundException();
if(message.HasFaulted)
throw new ApplicationException();
}
}
public class OrderNotFoundException : Exception
{
}
public class CustomFaultManager : IManageMessageFailures
{
private ISendMessages sender;
private MessageForwardingInCaseOfFaultConfig config;
private BusNotifications notifications;
private static ILog Logger = LogManager.GetLogger<CustomFaultManager>();
public CustomFaultManager(ISendMessages sender, IProvideConfiguration<MessageForwardingInCaseOfFaultConfig> config)
{
this.sender = sender;
this.config = config.GetConfiguration();
}
public void SerializationFailedForMessage(TransportMessage message, Exception e)
{
}
public void ProcessingAlwaysFailsForMessage(TransportMessage message, Exception e)
{
if (e is OrderNotFoundException)
{
//Ignore the exception;
Logger.WarnFormat("OrderNotFoundException was thrown. Ignoring the message Id {0}.", message.Id);
}
else
{
//Check if you have performed enough retries, ultimately send to error queue
SendToErrorQueue(message, e);
}
}
private void SendToErrorQueue(TransportMessage message, Exception ex)
{
message.TimeToBeReceived = TimeSpan.MaxValue;
sender.Send(message, new SendOptions(config.ErrorQueue));
Logger.WarnFormat("Message {0} will was moved to the error queue.", message.Id);
}
public void Init(Address address)
{
}
}
And to register the custom FaultManager:
var config = new BusConfiguration();
//Other configuration code
config.RegisterComponents(c =>
{
c.ConfigureComponent<CustomFaultManager>(DependencyLifecycle.InstancePerCall);
});
In Version 6 of NServiceBus however, the IManageMessageFailures interface is deprecated. The new Recoverability api in version 6 allows for better customization, althrough there's no direct way of ignoring/muting an exception. For that purpose you need a custom behavior in the NServiceBUs pipeline and run it in a step between one of the known steps (e.g. before a message is moved to the error queue).