I have this situation (method in Repository):
public string Get(string name)
{
string response;
try
{
using (var context = new MyDB())
{
var row = context.TblSomething.FirstOrDefault();
response = row.GetType().GetProperty(name).GetValue(row, null).ToString();
}
return response;
}
catch (SqlException e)
{
throw e;
}
catch (Exception e)
{
throw e;
}
}
When there is content other than the Property in the name field, it throws an exception
The method is called in the Controller
public IActionResult Get(string name)
{
string response;
try
{
response = _module.MyRepository().Get(name);
}
catch (ValidationException e)
{
return BadRequest(new { error = new { message = e.Message, value = e.Value } });
}
return Ok(response);
}
How to make it not return a 500 error to the user but should be BadRequest?
The way to make it return 400 instead of 500 is to actually catch the exception. You already have a catch block that returns BadRequest, so the only assumption that can be made is that ValidationException is not what's being thrown. Catch the actual exception being thrown and you're good.
That said, absolute do not catch an exception merely to throw the same exception. All you're doing is slowing down your app. You should also never catch Exception, unless you're simply trying to generally log all exceptions and then rethrow. If you don't have a specific handler for an exception type, then don't catch it. In other words, remove these lines:
catch (SqlException e)
{
throw e;
}
catch (Exception e)
{
throw e;
}
If you're not going to handle any exceptions as your repo code does, then don't use a try block at all.
It's also worth mentioning that you shouldn't rely on exceptions unless you have to. Throwing exceptions is a drain on performance. In a situation like this, you should simply return null, instead of throwing an exception when there's no matching property. Then, you can do a null check to verify instead of a try/catch.
You could create your own Exception Handling Middleware to catch 500 error and return your custom error status code and message.
1.Create the middleware:
public class ExceptionHandlingMiddleware
{
private readonly RequestDelegate _next;
public ExceptionHandlingMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context )
{
try
{
await _next(context);
}
catch (Exception ex)
{
await HandleExceptionAsync(context, ex);
}
}
private static Task HandleExceptionAsync(HttpContext context, Exception exception)
{
HttpStatusCode httpStatusCode = HttpStatusCode.InternalServerError;
string message = "Something is wrong!";
httpStatusCode = HttpStatusCode.BadRequest; // Or whatever status code you want to return
message = exception.Message; // Or whatever message you want to return
string result = JsonConvert.SerializeObject(new
{
error = message,
});
context.Response.StatusCode = (int)httpStatusCode;
context.Response.ContentType = "application/json";
return context.Response.WriteAsync(result);
}
}
2.Add it into the middleware pipeline after app.UseDeveloperExceptionPage();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseMiddleware(typeof(ExceptionHandlingMiddleware));
}
Related
I am developing WebAPI and want to catch all my ApiException custom exceptions and display WebAPI-friendly responses. The ApiException exception can be thrown from Action or Filter like IAuthorizationFilter or ActionFilterAttribute.
First I tried to use IExceptionFilter but later I found that the IExceptionFilter handles only exceptions thrown from Actions and not from other Filters.
public class ApiExceptionFilter : IExceptionFilter
{
public void OnException(ExceptionContext context)
{
var exception = context.Exception;
if (exception is not ApiException responseException)
{
responseException = new ApiException(ResponseMessageType.UnhandledException);
}
context.Result = new ObjectResult(new ResultMessageDto(responseException))
{
StatusCode = responseException.HttpStatusCode
};
}
}
The second approach that I found many suggest to use is the Middleware but this is not the correct way by WebAPI design.
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 exception)
{
var response = context.Response;
response.ContentType = "application/json";
if (exception is not ApiException responseException)
{
responseException = new ApiException(ResponseMessageType.UnhandledException);
}
response.StatusCode = responseException.HttpStatusCode;
await response.WriteAsJsonAsync(new ResultMessageDto(responseException), new JsonSerializerOptions()
{
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
PropertyNamingPolicy = null
});
}
}
}
The Middleware exception handling skips WebAPI MVC OutputFormaters and responds only in JSON or what is set by the developer. This solution is bad by design because do not respect Accept header.
How to handle Exceptions in Actions and Filters without leaving MVC scope?
I feel it is best to handle in Middleware, Here is a sample code. I am using Json but over here, you can use any output formatter (even custom ones).
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
try
{
await next(context);
}
catch (Exception ex)
{
await HandleExceptionAsync(context, ex);
}
}
private async Task HandleExceptionAsync(HttpContext context, Exception exception)
{
int statusCode = StatusCodes.Status500InternalServerError;
string exceptionType, exceptionDetail;
context.Response.ContentType = "application/json";
switch (exception)
{
case ArgumentNullException:
exceptionType = "Argument Null Exception";
exceptionDetail = exception.Message;
break;
case ApiException:
exceptionType = "API Exception";
exceptionDetail = exception.Message;
break;
default:
exceptionType = "Unhandled"
exceptionDetail = "Something went wrong";
break;
}
var problemDetails = new Microsoft.AspNetCore.Mvc.ProblemDetails
{
Status = statusCode,
Type = exceptionType,
Title = "An error has occurred within the PromComm Application",
Detail = exceptionDetail,
Instance = context.Request.Path
};
// You can use any formatter here even with custom messages
var problemDetailsJson = System.Text.Json.JsonSerializer.Serialize(problemDetails);
context.Response.StatusCode = statusCode;
await context.Response.WriteAsync(problemDetailsJson);
}
I'm kind of not sure as to where to catch an application and any other unexpected exception, but i do want to show on the front end which exception occurred either an application or any other exception.
If i just 'throw' from service manager then it will be catched in the controller, but what if there was an exception in the service manager and the controller?
This also seems verboose.
This is my service manager where I'm calling an API.
public async Task<int> CreateCategory(CategoryViewModel model)
{
logger.LogInformation("In {service}, Creating {CategoryModel}", nameof(CategoryServiceManager), model.ToString());
try
{
model.Guard(model.ToString());
int categoryId = await apiClient.PostAsync<int, CategoryViewModel>("Category", model);
return categoryId;
}
// Guard wil throw
catch (ApplicationException ex)
{
logger.LogError("Exception thrown for {model}: {Message}, {Stacktrace}", model.ToString(),ex.Message, ex.StackTrace);
throw new ApplicationException($"Exception thrown in service when creating category: {ex.Message}");
}
catch (Exception ex)
{
logger.LogError("Unexpected error thrown in service when creating a category : {Message}, {Stacktrace}", ex.Message, ex.StackTrace);
throw new Exception("Unexpected error thrown in service when creating a category");
}
}
This is the Guard extension used in the service manager.
public static class GuardExtensions
{
public static void Guard(this string input, string inputName)
{
if (string.IsNullOrEmpty(input))
{
throw new ApplicationException($"{inputName} must be provided");
}
}
public static void Guard(this object input, string inputType)
{
if (input == null)
{
throw new ApplicationException($"{inputType} must be provided");
}
}
}
This is the controller where I'm using the the service manager.
public async Task<IActionResult> Create(CategoryViewModel model)
{
logger.LogInformation("In {controller}, Creating {CategoryViewModel}", nameof(CategoryController), model.ToString());
try
{
if (ModelState.IsValid)
{
int createdCategoryId = await categoryService.CreateCategory(model);
List<CategoryPictureViewModel> categoryPictureViewModels = new List<CategoryPictureViewModel>();
foreach (int picId in TransformTypes.SplitStringIntoListOfInt(model.uploadedImageIds))
{
categoryPictureViewModels.Add(new CategoryPictureViewModel
{
CategoryId = createdCategoryId,
PictureId = picId
});
//model.CategoryPictures.ToList().Add(new CategoryPictureViewModel
//{
// CategoryId = createdCategoryId,
// PictureId = item
//});
}
int res = await categoryPictureService.CreateCategoryPictureAsync(categoryPictureViewModels);
return RedirectToAction(nameof(Index));
}
}
catch (ApplicationException ex)
{
logger.LogError("In {controller}, Creating category: {Message}, {Stacktrace}", nameof(CategoryController), ex.Message, ex.StackTrace);
throw new ApplicationException($"Exception thrown controller when creating category: {ex.Message}");
}
catch (Exception ex)
{
logger.LogError("Unexpected error in {controller} when creating category: {Message}, {Stacktrace}", nameof(CategoryController), ex.Message, ex.StackTrace);
throw new Exception($"Unexpected error in controller when creating category: {ex.Message}");
}
return StatusCode(StatusCodes.Status422UnprocessableEntity);
}
You can handle your exception by Action filter or using a custom exception handling middleware.
It depends on your scenario. but Having a custom exception middleware or exception filter to handle your exception can work and it's better for sake of separation of concern.
using Middleware :
Microsoft
ExceptionFilter:
StackOverflow
i have these class
//Class 1, ViewModel
public async System.Threading.Tasks.Task<JObject> ExecuteSystemObject(string parameters)
{
...
dynamic j = await ExternalProject.ExecuteSomething<MyModel>(parameters);
//How i can catch the error from the another class?
...
}
//Class2, Manager
public async Task<Object> ExecuteSomething<T>() where T : IModel, new()
{
...
WebResponse response = await ExternalProject.ExecuteRequestAsync(PostRequest);
...
}
//Class 3, from a binding Project
public static async Task<WebResponse> ExecuteRequestAsync(WebRequest request)
{
try
{
return await request.GetResponseAsync();
}
catch(WebException e)
{
var resp = new StreamReader(e.Response.GetResponseStream()).ReadToEnd();
dynamic obj = JsonConvert.DeserializeObject(resp);
//I have the message error here
var messageFromServer = obj.error.text;
throw e;
}
}
I can get the error only in the last class, if i try to get the WebException in the other, it will return null for me. Then, how can i pass that error to the main class(1ยบ one, ViewModel)?
Always use throw; whenever you want to rethrow an exception for you to be able to retain the stacktrace.
public async System.Threading.Tasks.Task<JObject> ExecuteSystemObject(string parameters)
{
try
{
dynamic j = await ExternalProject.ExecuteSomething<MyModel>(parameters);
//How i can catch the error from the another class?
...
}
catch(Exception e)
{
//WebException will be caught here
}
}
public async Task<Object> ExecuteSomething<T>() where T : IModel, new()
{
try
{
WebResponse response = await ExternalProject.ExecuteRequestAsync(PostRequest);
}
catch(Exception)
{
throw;
}
}
public static async Task<WebResponse> ExecuteRequestAsync(WebRequest request)
{
try
{
//return await request.GetResponseAsync();
throw new WebException("Test error message");
}
catch(WebException e)
{
throw;
}
}
EDIT: Just a rule of thumb, only catch exceptions when you have something to do with it. If you are catching exceptions just to log it. Don't do it.
My goal is to verify if an object's name already exists in my EF Core db, if so: throw a specific error. However, I receive a 500 internal server error.
First I created an index on name in DbContext, including IsUnique and some code to catch the exception in the repository.
Can I maybe add something in the controller that says if errorcode == 2601 then throw "the required exception"? Or is there another way to overcome this 500 error? Thanks in advance for the help!
DbContext:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Car>()
.HasIndex(c => c.Name)
.IsUnique();
}
Repository:
public async Task<bool> SaveAsync()
{
try
{
return (await _context.SaveChangesAsync() >= 0);
}
catch (DbUpdateException dbEx)
{
SqlException sqlException = dbEx.InnerException as SqlException;
if (sqlException.Number == 2601)
{
throw new Exception("Name already exists. Please provide a different name.");
}
throw new Exception(dbEx.Message);
}
catch (Exception ex)
{
throw new Exception(ex.Message);
}
}
Controller:
Public async Task<IActionResult> AddCar([FromBody] Car car)
...
if (!await _repository.SaveAsync())
{
throw new Exception("Fail on save...");
}
...
If you are using ASP.Net Core, you can create you own exception handling middleware.
The error handling middleware class itself may look something like:
public class ExceptionHandlingMiddleware
{
private readonly RequestDelegate m_next;
public ErrorHandlingMiddleware(RequestDelegate next)
{
m_next = next;
}
public async Task Invoke(HttpContext context /* other dependencies */)
{
try
{
await m_next(context);
}
catch (Exception ex)
{
await HandleExceptionAsync(context, ex);
}
}
private static Task HandleExceptionAsync(HttpContext context, Exception exception)
{
HttpStatusCode httpStatusCode = HttpStatusCode.InternalServerError;
string message = "Something is wrong!";
if (exception is MyException)
{
httpStatusCode = HttpStatusCode.NotFound; // Or whatever status code you want to return
message = exception.Message; // Or whatever message you want to return
}
string result = JsonConvert.SerializeObject(new
{
error = message
});
context.Response.StatusCode = (int)httpStatusCode;
context.Response.ContentType = "application/json";
return context.Response.WriteAsync(result);
}
}
You register in Startup.Configure() as:
app.UseMiddleware(typeof(ErrorHandlingMiddleware));
Change stdoutLogEnabled="false" to true and then check the logs at stdoutLogFile=".\logs\stdout". The error(s) there might tell you something.
Check that you set up right Environment Name using ASPNETCORE_ENVIRONMENT environment variable as so use correct settings like connection string. On your machine by default you have "Development" environment.
You may use Error Handling middlewares for showing exceptions like
app.UseDeveloperExceptionPage();
Out of need have created application exception which wraps a MongoDuplicateKeyException and throwing that exception like below
Public class AppException : Exception
{
// all constructor implementation
public int ErrorCode { get; set; }
public string AppMessage { get; set; }
}
In method catching and throwing exception
public async Task<Response> Method1(parameter ...)
{
try
{
//some insert/update operation to DB
return <instance of Response>;
}
catch(MongoduplicateKeyException ex)
{
var exception = new AppException(ex.Message, ex)
{
ErrorCode = 22,
AppMessage = "some message",
};
throw exception;
}
}
Method that calls Method1() above
try
{
//some other operation
var response = await Method1();
}
catch(AppException ex)
{
SomeOtherLoggingMethod(ex, other parameter);
}
catch(Exception ex)
{
SomeMethod(ex, other parameter);
}
Surprisingly the catch(AppException ex) catch block never gets catched even though am throwing an AppException from Method1(). It always catch the generic catch block catch(Exception ex).
After debugging, found that in catch(Exception ex) catch block the exception type ex.GetType() is actually a WriteConcernException type (MongoduplicateKeyException : WriteConcernException).
So essentially that specific catch block not hitting cause the exception type is not AppException rather WriteConcernException But
Not sure why is it so? am I missing something obvious here? Please suggest.
You found the answer while debugging. The catch(AppException ex) block is not executed because public async Task<Response> Method1 does not throw an AppException it throws a WriteConcernException.
The API shows a WriteConcernException is the superclass of DuplicateKeyException so the catch block in Method1 is not hit and the exception bubbles up to the 2nd catch block in the caller.
So if you update your code to catch the appropriate exception it should work as you intend.
public async Task<Response> Method1(parameter ...)
{
try
{
//some insert/update operation to DB
return <instance of Response>;
}
catch (MongoServerException mse)
...