ASP.NET Core Web Api Middleware Converts Custom Exception into Base Exception
I have created a custom exception class and use it throw an exception. The middleware catches it but throws base exception instead of custom exception. I am not able to understand why it does not catch custom exception.
Custom Exception
public class CustomException : Exception
{
public int HttpStatusCode { get; private set; }
public CustomException()
{
}
public CustomException(int httpStatusCode, string message):base(message)
{
HttpStatusCode = httpStatusCode;
}
}
Middleware
public class ExceptionMiddleware
{
public readonly RequestDelegate _next;
public ExceptionMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext httpContext)
{
try
{
await _next(httpContext);
}
catch(CustomException ex)
{
await HandleExceptionAsync(httpContext, ex);
}
catch(Exception ex)
{
await HandleExceptionAsync(httpContext, ex);
}
}
private static Task HandleExceptionAsync(HttpContext context, Exception ex)
{
int statusCode = (int)HttpStatusCode.InternalServerError;
if (ex is CustomException)
{
CustomException se = ex as CustomException;
statusCode = se.HttpStatusCode;
}
context.Response.ContentType = "application/json";
context.Response.StatusCode = statusCode;
return context.Response.WriteAsync(JsonConvert.SerializeObject(new InternalServerErrorResponse(ex.Message)));
}
}
Exception thrown
throw new CustomException(StatusCodes.Status422UnprocessableEntity, "User is already registered!");
When exception is thrown, middleware does not catch custom exception but base Exception. It always goes to the below code block
catch(Exception ex)
{
await HandleExceptionAsync(httpContext, ex);
}
Please advise.
You can use exception filter to handle exceptions and easily manipulate response.
public class ExceptionFilter : IExceptionFilter
{
public ExceptionFilter(IHostingEnvironment env, ILogger<JsonExceptionFilter> logger)
{
_env = env;
_logger = logger;
}
public void OnException(ExceptionContext context)
{
var error = new ApiResponse();
var exceptionName = context.Exception.GetType().Name;
var message = context.Exception.Message;
if (_env.IsDevelopment())
{
error.Message = context.Exception.Message;
error.Detail = context.Exception.StackTrace;
}
else
{
//Prevent To Show Exception Messages On Production
error.Message = "Server Error Occured";
error.Detail = "Something wrong happened";
}
context.Result = new ObjectResult(error)
{
//Manipulate Status Code
StatusCode = 500
};
}
}
And register to Startup.cs
public void ConfigureServices(IServiceCollection services){
services.AddMvc(options =>
{
//Catch the exceptions
options.Filters.Add<ExceptionFilter>();
}).SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
}
you have to tell your app to use this middleware in configure method in your startup class like this -
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerManager logger){
app.ExceptionMiddleware();
}
Related
How to create a Custom Exception Handler Middleware. This middleware should generate a custom response based on the Calling Client. If the client is requesting via AJAX then the response should be a JSON Response describing the Error otherwise Redirect the client to Error page.
Controller code
public IActionResult Index()
{
return View();
}
public IActionResult Privacy()
{
return View();
}
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public IActionResult Error()
{
return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
}
middleware code
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;
var customError = new CustomError();
switch (error)
{
case AppException e:
// custom application error
customError.StatusCode = (int)HttpStatusCode.BadRequest;
break;
case KeyNotFoundException e:
// not found error
customError.StatusCode = (int)HttpStatusCode.NotFound;
break;
default:
// unhandled error
customError.StatusCode = (int)HttpStatusCode.InternalServerError;
break;
}
customError.ErrorMessage = error?.Message;
if (context.Request.ContentType == "application/json;")
{
var result = JsonSerializer.Serialize(customError);
await response.WriteAsync(result);
}
else
{
context.Response.Redirect("/Errors/CustomError");
}
}
}
Custom Error class code
public class CustomError
{
public int StatusCode { get; set; }
public string ErrorMessage { get; set; }
}
Error View model
public class ErrorViewModel
{
public string RequestId { get; set; }
public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
}
you could add the code in your startup class:
app.UseStatusCodePagesWithReExecute("/errors/{0}");
add a controller(In my case I tested with HttpContext.Request.Headers["Content-Type"] ,it should be context.Request.ContentType == "application/json;" for MVC project ):
public class ErrorsController : Controller
{
[Route("errors/{statusCode}")]
public IActionResult CustomError(int statusCode)
{
if (HttpContext.Request.Headers["Content-Type"] == "application/json")
{
var cuserr = new CustomError() { ErrorMessage = "err", StatusCode = statusCode };
return new JsonResult(cuserr);
}
else
{
if (statusCode == 404)
{
return View("~/Views/Errors/404.cshtml");
}
return View("~/Views/Errors/500.cshtml");
}
}
}
and the views:
The result:
for more details,you could read the offcial document:
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/error-handling?view=aspnetcore-6.0
Following is my Test class which is responsible for testing POST method.
#RunWith(JUnitParamsRunner.class)
#ApplicationTest
#WithMockUser("mike")
public class AnalysisRecordParam {
ObjectMapper objectMapper=new ObjectMapper();
MockHttpSession session=new MockHttpSession();
#ClassRule
public static final SpringClassRule SCR = new SpringClassRule();
#Rule
public final SpringMethodRule springMethodRule = new SpringMethodRule();
private RestTemplate restTemplate = new TestRestTemplate("operations", "operations");
#Test
#Parameters(method= "parametersForSavesCustomer")
public void savesCustomer(String name,AnalysisRecordTO analysisRecordTO) throws Exception{
AnalysisRecordTO result = restTemplate.postForObject("http://localhost:8080/api/v1/analysisrecord/", analysisRecordTO, AnalysisRecordTO.class);
assertNotEquals(null, result);
}
#SuppressWarnings("unused")
public Object[] parametersForSavesCustomer() {
Object[] array=null;
Properties analysisMap=new Properties();
try {
analysisMap.load(AbstractJobArgumentProvider.class.getResourceAsStream("/config/analysis-list.properties"));
Set<Object> set=analysisMap.keySet();
array=new Object[analysisMap.size()];
int i=0;
for(Object key:set){
Object[] arr=new Object[2];
arr[0]=key.toString();
String jsonFilePath=(String)analysisMap.get(key);
AnalysisRecordTO analysis=this.objectMapper.readValue(new File(jsonFilePath), AnalysisRecordTO.class);
arr[1]=analysis;
array[i]=arr;
i++;
}
} catch (IOException e) {
e.printStackTrace();
}
return array;
}
}
I have one interceptor which looks for CONSUMER_TYPE and CONSUMER_ID in session before forwarding the request.
Code for interceptor is as below :
public class ConsumerTypeInterceptor extends HandlerInterceptorAdapter {
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String method = request.getMethod();
HttpSession session = request.getSession(false);
if(!method.equals("OPTIONS") && (session == null || session.getAttribute("CONSUMER_TYPE") == null || session.getAttribute("CONSUMER_ID") == null)){
System.out.println("Invalid request");
response.sendError(HttpServletResponse.SC_PRECONDITION_FAILED, "Invalid login token");
return false;
}
return true;
}
}
Using Rest template request is given properly but it is denied by interceptor.
I just wanted to know how to set these session attributes?
I'm looking to use JACKSON instead of grails JSON ,without changing the auto-scaffolded contollers.
Is it possible to seamlessly replace the current grails JSON converter with another one ?
Are there any classes to implement other then AbstractConverter...
Should be pretty simple. AbstractConverter is all you need to extend to do:
render result as JackSON
Something like this***:
*** taken from here: https://github.com/sjhorn/grails-jackson/blob/master/src/groovy/com/hornmicro/JackSON.groovy
class JackSON extends AbstractConverter {
Object target
public JackSON() {
}
public JackSON(Object target) {
this()
setTarget(target)
}
public void render(Writer out) throws ConverterException {
try {
ObjectMapper mapper = new ObjectMapper()
mapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS)
mapper.configure(JsonGenerator.Feature.FLUSH_PASSED_TO_STREAM, false)
mapper.configure(JsonGenerator.Feature.AUTO_CLOSE_JSON_CONTENT, false)
mapper.writeValue(out, target)
} catch(e) {
throw new ConverterException(e)
}
try {
out.flush()
out.close()
} catch (Exception e) {
log.warn("Unexpected exception while closing a writer: " + e.getMessage())
}
}
public void render(HttpServletResponse response) throws ConverterException {
response.setContentType(GrailsWebUtil.getContentType("application/json", "UTF-8"));
try {
render(response.getWriter())
} catch (IOException e) {
throw new ConverterException(e)
}
}
public Object getWriter() throws ConverterException {
throw new ConverterException("Not Implemented")
}
public void convertAnother(Object o) throws ConverterException {
throw new ConverterException("Not Implemented")
}
public void build(Closure c) throws ConverterException {
throw new ConverterException("Not Implemented")
}
public ObjectMarshaller lookupObjectMarshaller(Object target) {
return null
}
public void setTarget(Object target) {
this.target = target
}
}
Am writing a Restful Webservice Impl, where i consume and produce response in JSON format by annotating #Produces("application/json"). Am producing JSON response as well. Here am handling exception with a class where it has error code and error message. When am getting exception it is not produced in application/json format. I used ExceptionMapper to find a solution but it is `text/plain format.
snippet
public Class Confiuration{
#Path("getData")
#Consumes("application/json")
#Produces("application/json")
public JSONGetDataResponseVo getData(GetDataRequestVo datarequestVO)
throws FaultResponse {
JSONGetDataResponseVo response=new JSONGetDataResponseVo ();
DataServiceValidator.validateGetConfigurationAndDataRequest(datarequestVO);
....
....
}catch(ApplicationException applicationException){
throw new FaultResponse(applicationException,locale);
}
}
FaultResponseMapper
#Provider
public class FaultResponseMapper implements ExceptionMapper<FaultResponse> {
#Context
private HttpHeaders headers;
public Response toResponse(FaultResponse faultResponse) {
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(faultResponse).type(MediaType.APPLICATION_JSON).build();
}
}
Application Exception
public abstract class ApplicationException extends Exception{
private java.lang.String errorCode;
public ApplicationException(String errorCode, String message) {
super(message);
this.errorCode = errorCode;
}
public ApplicationException(String message) {
super(message);
}
public java.lang.String getErrorCode() {
return this.errorCode;
}
public abstract String getLocaleMessage(Locale locale);
}
FaultResponse
public class FaultResponse extends WebApplicationException {
private String errorCode;
private String errorMessage;
private String localErrorMessage;
public FaultResponse(String errorCode, String errorMessage,
String localErrorMessage) {
this.errorCode = errorCode;
this.errorMessage = errorMessage;
this.localErrorMessage = localErrorMessage;
}
public FaultResponse(ApplicationException applicationException,
Locale locale) {
this.errorCode = applicationException.getErrorCode();
this.errorMessage = applicationException.getMessage();
if (locale != null
&& applicationException.getLocaleMessage(locale) != null) {
this.localErrorMessage = applicationException
.getLocaleMessage(locale);
} else {
this.localErrorMessage = applicationException.getMessage();
}
}
}
So here how can i produce my faultResponse in JSON format.
This has to do with the fact that you are returning an exception as a response. I would
Make an exception mapper for ApplicationException.
Refactor FaultResponse to not extend and exception. Just create it in the mapper.
In order to see the response, you will need to send a status other than No Content. You can't have a body in it. Send somethng like Bad Request.
You can just declare the resource method as throws ApplicationException. You don't need to catch it and rethrow.
I've made these changes, and it works fine.
UPDATE: with complete test
Added getters (required for marshalling) to FaultResponse and remove the exception extension
public class FaultResponse {
...
public String getErrorCode() { return errorCode; }
public String getErrorMessage() { return errorMessage; }
public String getLocalErrorMessage() { return localErrorMessage; }
...
}
Created a Service for testing and ApplicationException implementation
public class ApplicationExceptionImpl extends ApplicationException {
public ApplicationExceptionImpl(){
this("400", "Bad Request");
}
public ApplicationExceptionImpl(String errorCode, String message) {
super(errorCode, message);
}
#Override
public String getLocaleMessage(Locale locale) {
return "Bad Request";
}
}
public class FaultService {
public void doSomething() throws ApplicationException {
throw new ApplicationExceptionImpl();
}
}
Resource class
#Path("fault")
public class FaultResource {
FaultService service = new FaultService();
#GET
public Response getException() throws ApplicationException {
service.doSomething();
return Response.ok("Cool").build();
}
}
ExceptionMapper
#Provider
public class ApplicationExceptionMapper implements ExceptionMapper<ApplicationException> {
#Override
public Response toResponse(ApplicationException exception) {
FaultResponse response = new FaultResponse(exception, Locale.ENGLISH);
return Response.status(Response.Status.BAD_REQUEST)
.entity(response).type(MediaType.APPLICATION_JSON).build();
}
}
ApplicationException class is left the same
curl -v http://localhost:8080/api/fault
{"errorCode":"400","errorMessage":"Bad Request","localErrorMessage":"Bad Request"}
If after this you are still not seeing JSON, it's possible you do not have a provider configured. If this is the case, please show your application configuration, along with your project dependencies.
Hi I have custom junit runner
public class InterceptorRunner extends BlockJUnit4ClassRunner {
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.TYPE)
public #interface InterceptorClasses {
public Class<?>[] value();
}
public InterceptorRunner(Class<?> klass) throws InitializationError {
super(klass);
}
#Override
public Statement methodInvoker(FrameworkMethod method, Object test) {
InterceptorStatement statement = new InterceptorStatement(super.methodInvoker(method, test));
InterceptorClasses annotation = test.getClass().getAnnotation(InterceptorClasses.class);
Class<?>[] klasez = annotation.value();
try {
for (Class<?> klaz : klasez) {
statement.addInterceptor((Interceptor) klaz.newInstance());
}
} catch (IllegalAccessException ilex) {
ilex.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
return statement;
}
#Override
public void run(RunNotifier notifier) {
FailListener listener = new FailListener();
notifier.addListener(listener);
super.run(notifier);
notifier.removeListener(listener);
}
}
and custom listener
public class FailListener extends RunListener {
#Override
public void testFailure(Failure failure) throws Exception {
System.out.println("test fails");
super.testFailure(failure);
}
public void testStarted(Description description) throws Exception {
System.out.println("Test started");
super.testStarted(description);
}
}
How can I log not only System.out.println("test fails"); but also Exception and some other information?
It seems to me that it possible to use failure, but I don't know how to.
The Failure object has a method getException().