This is my Startup() method in Startup.cs:
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
if (env.IsDevelopment())
{
loggerFactory.AddNLog();
}
app.UseApplicationInsightsRequestTelemetry(); // Needs to be first
app.UseExceptionHandler(errorApp =>
{
errorApp.Run(async context =>
{
context.Response.StatusCode = 500;
context.Response.ContentType = "application/json";
var error = context.Features.Get<IExceptionHandlerFeature>();
if (error != null)
{
var ex = error.Error;
await context.Response.WriteAsync(new ErrorResponse
{
Code = 500,
Message = ex.Message
}.ToString(), Encoding.UTF8);
}
});
});
app.UseApplicationInsightsExceptionTelemetry(); // After error page
app.UseMvc();
}
This is my route in a controller.
[HttpGet]
[Route("Test")]
public string Test()
{
if(!string.IsNullOrEmpty(Request.Query["test"]))
throw new Exception("Testing..");
return "Hello.";
}
ErrorResponse.cs
public class ErrorResponse
{
public int Code { get; set; }
public string Message { get; set; }
public override string ToString()
{
return JsonConvert.SerializeObject(this);
}
}
When I go to http://website/test it shows "Hello." but when I go to http://website/test?test=asd it throws my exception (the one in the route) instead of catching it with UseExceptionHandler and stops the program.
My goal is to make it show the following json instead:
{
"Error": 500,
"Message": "Testing..."
}
I am using asp.net core.
Old post, but I just hit the same issue.
There is likely some exception filter applied globally. Look up the MVC configuration in Startup.ConfigureServices. I had services.AddMvcOptions(o => o.Filters.Add<ErrorHandlerAttribute>()) which was the problem.
Your app is stopped on exception cause you run it in debug mode in VS and Debugger is attached. Run app from console for example (or disable "break on exception" option in VS.
In my case it was that app.UseDeveloperExceptionPage() was already catching the exceptions and conflicting in some way with this middleware.
}
Related
I wrote a code but for some reason it doesn't work...can you tell me what's wrong?
I want the app not to stop when I get an exception, only to send that exception back as a json message.
Startup.cs Configure method:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v2/swagger.json", "My API");
});
}
//this is the question...?
app.UseExceptionHandler(c => c.Run(async context =>
{
var exception = context.Features.Get<IExceptionHandlerPathFeature>().Error;
var response = new { Msg = exception.Message };
await context.Response.WriteAsJsonAsync(response);
}));
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseCors(x => x
.AllowAnyMethod()
.AllowAnyHeader()
.SetIsOriginAllowed(origin => true)
.AllowCredentials());
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapHub<EventHub>("/events");
});
}
Logic the method where I throw an exception:
public IEnumerable<object> Search(string text)
{
if (text.Length >= 3)
{
var result = new List<object>
{
clubRepository.GetAll().Where(club => club.ClubName.Contains(text)),
playerRepository.GetAll().Where(player => player.PlayerName.Contains(text)),
managerRepository.GetAll().Where(manager => manager.ManagerName.Contains(text)),
stadiumRepository.GetAll().Where(stadium => stadium.StadiumName.Contains(text))
};
return result;
}
else
{
throw new ArgumentException("The text is not long enough!");
}
}
So I would like to get this exception message as json!
Now it is happening --> Image1
I want that to happen --> Image2
You can extract exception elements as a key value in a dictionary.
And serialize that into JSON.
Inspired by this answer, here is my method to extract key/value from exception:
public static Dictionary<string, string> GetExceptionDetails(Exception exception)
{
var properties = exception.GetType()
.GetProperties();
var fields = properties
.Select(property => new
{
Name = property.Name,
Value = property.GetValue(exception, null)
})
.Select(x => $"{x.Name} = {(x.Value != null ? x.Value.ToString() : string.Empty)}")
.ToDictionary(k => k, v => v);
return fields;
}
For my test I have done this:
private static void CallSome()
{
throw new Exception("xx");
}
in your try/catch you do the following:
try
{
CallSome();
}
catch (Exception e)
{
string str = JsonConvert.SerializeObject(GetExceptionDetails(e));
Console.WriteLine(str);
}
This will return you a JSON payload.
I use dotnet 6 console app. I have also installed the Newtonsoft.Json package. You can also you dotnet JsonSerializer:
var str = JsonSerializer.Serialize(GetExceptionDetails(e));
Note: it also worth reading this also this.
You can use Middleware to handle any exception.
First: Create a ErrorHandlerMiddleware.cs like below.
public class ErrorHandlerMiddleware
{
private readonly RequestDelegate _next;
public ErrorHandlerMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
try
{
await _next(context);
}
catch (Exception error)
{
var response = context.Response;
//Set response ContentType
response.ContentType = "application/json";
//Set custome error message for response model
var responseContent = new ResponseContent()
{
error = error.Message
};
//handler many Exception types
switch (error)
{
case ArgumentException _ae:
response.StatusCode = StatusCodes.Status400BadRequest;
break;
default:
response.StatusCode = StatusCodes.Status500InternalServerError;
break;
}
//Using Newtonsoft.Json to convert object to json string
var jsonResult = JsonConvert.SerializeObject(responseContent);
await response.WriteAsync(jsonResult);
}
}
//Response Model
public class ResponseContent
{
public string error { get; set; }
}
}
Next: In Startup.cs, use the middleware
app.UseMiddleware<ErrorHandlerMiddleware>();
Here is project structure of my simple example :
Goodluck!
I don't know if you misunderstood but your app is only stopping because you are running it inside VS Code (Debug Mode). If you run your app externally (in command, run "dotnet run") you'll see that the app will not stop.
Now, it's just an advice. Your app is already sending back the json but with status code 500 (internal server error). A better practice for validation errors, is returning as bad request (status code 400). You can add one line like below.
app.UseExceptionHandler(c => c.Run(async context =>
{
var exception = context.Features.Get<IExceptionHandlerPathFeature>().Error;
var response = new { Msg = exception.Message };
context.Response.StatusCode = (int)HttpStatusCode.BadRequest;
await context.Response.WriteAsJsonAsync(response);
}));
Then, if you want to improve a little more. You can replace exceptions with notification pattern. Here are some links if you are interested.
https://martinfowler.com/articles/replaceThrowWithNotification.html
https://timdeschryver.dev/blog/creating-a-new-csharp-api-validate-incoming-requests
I have an application I would like to add middleware error handling to but the exception never seem to bubble up. I've read several articles about this having to do with async behavior but I can't see what I'm doing wrong.
For example this SO post (Exceptions not bubbling up to Error Handling Middleware?) is very similar but I already have async as that is how it was originally written before we added the middleware error handling.
I'll post what I think is relevant.
ExceptionMiddleware.cs:
public async Task Invoke(HttpContext context)
{
try
{
await _next(context);
}
catch (Exception ex)
{
//we never get here????
await HandleExceptionAsync(context, ex, _options);
}
}
APIService:
public async Task<Response<PaginationModel>> GetPagination(int result, int pageNumber,...)
{
_logger.GetPaginationInformation($"Enter with parameters result: {result},.....");
try
{
....do stuff
}
catch (Exception ex)
{
//we do get here
throw; //return CreateErrorMessage<PaginationModel>("GetPagination", ex);
}
}
Startup.cs:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseABCExceptionHandler(options => options.AddErrorDetails = FormatExceptionResponse);
app.UseCors();
// add http for Schema at default url /graphql
app.UseWebSockets();
app.UseGraphQLWebSockets<ISchema>();
app.UseGraphQL<ISchema>("/graphql");
app.UseGraphQLPlayground();
}
private void FormatExceptionResponse(HttpContext context, Exception exception, Response<PaginationModel> response)
{
response.message = exception.Message;
}
ExceptionMiddlewareExtensions.cs:
public static class ExceptionMiddlewareExtensions
{
public static IApplicationBuilder UseABCExceptionHandler(this IApplicationBuilder builder)
{
var options = new ExceptionOptions();
return builder.UseMiddleware<ExceptionMiddleware>(options);
}
public static IApplicationBuilder UseABCExceptionHandler(this IApplicationBuilder builder, Action<ExceptionOptions> configureOptions)
{
var options = new ExceptionOptions();
configureOptions(options);
return builder.UseMiddleware<ExceptionMiddleware>(options);
}
}
I set debug breakpoints and everything seems to register and all "hooks" seem to be set and execution flows as expected first through ExceptionMiddleware Invoke _next(context) then to ApiService GetPagination but even if I throw a hard exception or remove the try catch block in GetPagination it never flows back up to Invoke catch?
I'm sure this has something to do with lack of understanding how to handle globally with async Task but I follow the articles on it and it doesn't seem to matter??
Update
Based upon the comment from Andy I'm adding this information, it could be helpful.
GetPagination is NOT an API endpoint. It is a service class called by the GraphQL query.
GraphQL Query:
FieldAsync<Response...>(
"PaginationSearch",
"Returns paginated for specified search terms",
arguments: new QueryArguments(... { Name = "result" },
resolve: async context =>
{
var result = context.GetArgument<int>("result");
//Is this using statement introducing some unexpected behavior as it Disposes behind the scenes??
using (_logger.GetScope("PaginationSearch"))
{
return Service.GetPagination(result...);
}
}
);
Update 2
Moving the registration line to the bottom of the configure method as mentioned in the comments actually makes it so it doesn't flow through the middleware Invoke BUT moving it to the first line does.
To be clear at project start BOTH first line and last line it does flow through invoke. I'm specifically referring to execution when a graphql query is received.
When startup.cs has registration last line Middleware Invoke is not used
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
.....
app.UseABCExceptionHandler(options => options.AddErrorDetails = FormatExceptionResponse);
}
ExceptionMiddleware.cs:
{
try
{
await _next(context); //breakpoint is NOT hit when request received
}
catch (Exception ex)
{
await HandleExceptionAsync(context, ex, _options);
}
}
When startup.cs has registration line first Middleware Invoke is used
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseABCExceptionHandler(options => options.AddErrorDetails = FormatExceptionResponse);
.....
}
ExceptionMiddleware.cs:
{
try
{
await _next(context); //breakpoint **IS** hit when request received
}
catch (Exception ex)
{
await HandleExceptionAsync(context, ex, _options);
}
}
Also not sure if it matters but the API service that is registerd in startup.cs is a singleton.
services.AddSingleton<IAPIService, APIService>();
and the shared HTTP client (using HTTP Typed clients) is added to the services httpclient collection.
services.AddHttpClient<ISecurityClient, SecurityClient>();
I'm trying to setup exception handling in a new .net Core 2.2 Web application project.
Currently I tried two approaches - being
Use the built-in ExceptionHandlers: app.UseExceptionHandler, app.UseStatusCodePages, etc...
Created my own middleware to handle exceptions which I've added to the bottom of this question.
Now, the main issue is that I can use my own custom middleware, but (of course) this is used on all requests, and not only on API calls.
What I would like to achieve is have separated exception logic for Web API and Web Pages, so that I can use my custom middleware and return format for API, and use app.UseDeveloperExceptionPage() for example for web pages.
Exception middleware for reference:
public static class ExceptionMiddleware
{
public static void ConfigureExceptionHandler(this IApplicationBuilder app, ILoggerFactory loggerFactory,
bool includeStackTrace = false)
{
app.UseExceptionHandler(builder =>
builder.Run(async context =>
{
var logger = loggerFactory.CreateLogger(nameof(ExceptionMiddleware));
// Set the response status code
context.Response.StatusCode = (int) HttpStatusCode.InternalServerError;
context.Response.ContentType = "application/json";
var contextFeature = context.Features.Get<IExceptionHandlerFeature>();
if (contextFeature != null)
{
// Get the current exception
var currentException = contextFeature.Error;
logger.LogError(currentException, $"Something went wrong: {currentException}");
ErrorDetails errorDetails;
if (includeStackTrace)
errorDetails = new ExtendedErrorDetails
{
StatusCode = (HttpStatusCode) context.Response.StatusCode,
Message = currentException.Message,
StackTrace = currentException.StackTrace
};
else
errorDetails = new ErrorDetails
{
StatusCode = (HttpStatusCode) context.Response.StatusCode,
Message = "Internal Server Error."
};
// Write the response
await context.Response.WriteAsync(JsonConvert.SerializeObject(errorDetails));
}
}));
}
private class ErrorDetails
{
public HttpStatusCode StatusCode { [UsedImplicitly] get; set; }
public string Message { [UsedImplicitly] get; set; }
}
private class ExtendedErrorDetails : ErrorDetails
{
public string StackTrace { [UsedImplicitly] get; set; }
}
}
You could not identify the error from exception, I suggest you try to make different route for mvc and web api. For web api, add attribute route with api, and then check the request path in the middleware or exception handler like
app.UseExceptionHandler(builder =>
{
builder.Run(async context =>
{
context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
var error = context.Features.Get<IExceptionHandlerFeature>() as ExceptionHandlerFeature;
var requestPath = error.Path;
});
});
Update:
app.UseDeveloperExceptionPage();
app.UseWhen(context => context.Request.Path.StartsWithSegments("/api"), subApp =>
{
subApp.UseExceptionHandler(builder =>
{
builder.Run(async context =>
{
context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
await context.Response.WriteAsync("Error");
});
});
});
I'm trying to figure out where I went wrong. I want to catch 404 errors that come to my api. I have the middleware setup, but the exception never occurs when I try to hit the page that doesn't exist.
public async Task Invoke(HttpContext context)
{
try
{
await _requestDelegate.Invoke(context);
}
catch (Exception exception)
{
await HandleExceptionAsync(context, exception);
}
}
//in startup
app.UseMiddleware<ExceptionHandler>();
and I register it in the startup, its the first thing I do to ensure it handles the rest.
Sounds like you want UseStatusCodePagesWithReExecute to catch the 404 and modify the response, log, ect.
//Startup.cs
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
...
app.UseStatusCodePagesWithReExecute("/error/", "?statusCode={0}");
...
}
//SomeController.cs
public IActionResult Error(int? statusCode = null)
{
if(statusCode == 404) return new ObjectResult(new { message = "404 - Not Found" });
...
}
I have a MediatR Pipeline behavior for validating commands with the FluentValidation library. I've seen many examples where you throw a ValidationException from the behavior, and that works fine for me. However in my scenario I want to update my response object with the validation errors.
I am able to build and run the following code. When I set a break point within the if statement the CommandResponse is constructed with the validation errors as expected - but when the response is received by the original caller it is null:
public class RequestValidationBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse> where TRequest : IRequest<TResponse>
{
private readonly IEnumerable<IValidator<TRequest>> _validators;
public RequestValidationBehavior(IEnumerable<IValidator<TRequest>> validators)
{
_validators = validators;
}
public Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next)
{
var context = new ValidationContext(request);
// Run the associated validator against the request
var failures = _validators
.Select(v => v.Validate(context))
.SelectMany(result => result.Errors)
.Where(f => f != null)
.ToList();
if(failures.Count != 0)
{
var commandResponse = new CommandResponse(failures) { isSuccess = false };
return commandResponse as Task<TResponse>;
}
else
{
return next();
}
}
}
I think it has to do with my attempt to cast it as Task - but without this I get compiler errors. I'm returning the same type that my command handler would if validation passes so I am at a loss as to why it returns a null instance of the expected response. I feel like there is a better way to handle this, but I've tried a number of variations to no avail. Any suggestions? Is there a better pattern to use? I'd prefer to keep this in the pipeline as it will be reused a lot.
I ended up adding exception handling middleware to the MVC project. Instead of trying to pass back the validation errors as an object I throw a ValidationException inside of the pipeline behavior and the middleware handles any and all exceptions across the entire project. This actually worked out better as I handle all exceptions in one place higher up in the processing chain.
Here is the updated portion of the code I posted:
if(failures.Count != 0)
{
// If any failures are found, throw a custom ValidationException object
throw new ValidationException(failures);
}
else
{
// If validation passed, allow the command or query to continue:
return next();
}
Here is the exception handling middleware:
public class ErrorHandlingMiddleware
{
private readonly RequestDelegate next;
public ErrorHandlingMiddleware(RequestDelegate next)
{
this.next = next;
}
public async Task Invoke(HttpContext context /* other dependencies */)
{
try
{
await next(context);
}
catch (Exception ex)
{
await HandleExceptionAsync(context, ex);
}
}
private static Task HandleExceptionAsync(HttpContext context, Exception exception)
{
// Log issues and handle exception response
if (exception.GetType() == typeof(ValidationException))
{
var code = HttpStatusCode.BadRequest;
var result = JsonConvert.SerializeObject(((ValidationException)exception).Failures);
context.Response.ContentType = "application/json";
context.Response.StatusCode = (int)code;
return context.Response.WriteAsync(result);
}
else
{
var code = HttpStatusCode.InternalServerError;
var result = JsonConvert.SerializeObject(new { isSuccess = false, error = exception.Message });
context.Response.ContentType = "application/json";
context.Response.StatusCode = (int)code;
return context.Response.WriteAsync(result);
}
}
}
You then register the middleware in your Startup before MVC is added:
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseMiddleware(typeof(ErrorHandlingMiddleware));
app.UseMvc();
}
Note: You can also create an extension method for your middleware:
public static class ErrorHandlingMiddlewareExtension
{
public static IApplicationBuilder UseErrorHandlingMiddleware(
this IApplicationBuilder builder)
{
return builder.UseMiddleware<ErrorHandlingMiddleware>();
}
}
Which allows you to register it like this:
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseErrorHandlingMiddleware();
app.UseMvc();
}
I am using .Net core 3.1 and I was not able to catch the exceptions when I added the middleware before the following block in Configure function of Startup
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
check in the configure method. Make sure to register it after the above statement like this. It is quite obvious but may help someone like me.
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseMiddleware<ErrorHandlingMiddleware>();