JsonSerializationError not correctly handled by middleware - c#

I encounter a very wierd error.
I have an Item with some properties that are JsonRequired.
When i try to call my route to get my Item when one property that is required is missing, my error is not automatically thrown as an error code 500 I get a 200 Ok instead.
Here is my route :
[HttpGet("{itemId}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult<Item>> GetItemByIdAsync(long installationId, Guid itemId)
{
return await _itemService.GetItemByIdAsync(installationId, itemId);
}
Here is my Item class :
public class Item
{
[JsonProperty("id")]
[JsonRequired]
public Guid Id { get; set; }
[JsonProperty("name")]
[JsonRequired]
public string Name { get; set; }
}
And here is my middleware :
public async Task Invoke(HttpContext context)
{
try
{
await _next(context);
}
catch (NotFoundException ex)
{
await HandleExceptionAsync(context, HttpStatusCode.NotFound, ex);
}
catch (UnauthorizedException ex)
{
await HandleExceptionAsync(context, HttpStatusCode.Unauthorized, ex, false);
}
catch (ConflictException ex)
{
await HandleExceptionAsync(context, HttpStatusCode.Conflict, ex);
}
catch (BadRequestException ex)
{
await HandleExceptionAsync(context, HttpStatusCode.BadRequest, ex);
}
}
private Task HandleExceptionAsync(HttpContext context, HttpStatusCode httpCode, Exception exception, bool displayException = true)
{
_logger.LogError(exception, $"Exception catched in middleware: {exception.Message}.");
context.Response.ContentType = "application/json";
context.Response.StatusCode = (int)httpCode;
var payload = JsonConvert.SerializeObject(new ApiError(displayException ? exception.Message : string.Empty));
return context.Response.WriteAsync(payload);
}
What I have tried :
If I try to add this catch in the middleware
catch (Exception ex)
{
await HandleExceptionAsync(context, HttpStatusCode.InternalServerError, ex);
}
There is still the same result I don't get a 500 error.
I don't really understand why my response is not overrided to be a 500 error.
Do you have any idea ?
Thanks a lot.

Since you do not show your _itemService.GetItemByIdAsync.It works well when I test with below code which has an 500 error.
public async Task<ActionResult<Item>> GetItemByIdAsync()
{
string json = #"{
'id': '2f5135a7-977c-4b26-a4e2-74b9e374a75e',
'name': null,
}";
Item x = JsonConvert.DeserializeObject<Item>(json);//throw 500 error using your Item model
return x;
}
You could also use Required property for JsonProperty like
[JsonProperty("name", Required = Required.Always)] //could not be null
public string Name { get; set; }
The definition for it is:
//
// Summary:
// Indicating whether a property is required.
public enum Required
{
//
// Summary:
// The property is not required. The default state.
Default = 0,
//
// Summary:
// The property must be defined in JSON but can be a null value.
AllowNull = 1,
//
// Summary:
// The property must be defined in JSON and cannot be a null value.
Always = 2,
//
// Summary:
// The property is not required but it cannot be a null value.
DisallowNull = 3
}

Related

Mock a controller error in c# unit testing

I am trying to create different types of errors in an ASP.NET controller (which communicates with a service). Right now I am focusing on a ServiceNotFound error, but DbContextMock() always returns true. What is the correct way to do this?
(In OrdersController.cs)
public OrdersController(IOrdersService ordersService)
{
_ordersService = ordersService ?? throw new ArgumentNullException(nameof(ordersService));
}
[HttpPut]
[ProducesResponseType((int)HttpStatusCode.NoContent)]
[ProducesResponseType((int)HttpStatusCode.InternalServerError)]
public async Task<IActionResult> PutOrdersAsync()
{
try
{
await _ordersService.PutOrdersAsync();
return new StatusCodeResult(StatusCodes.Status204NoContent);
}
catch (ServiceException ex)
{
return StatusCode(
(int)ex.StatusCode,
Responses.ErrorResponse with { Message = ex.Message, Content = JsonConvert.SerializeObject(ex), RequestId = Request.HttpContext.Connection.Id }
);
}
catch (Exception ex)
{
return StatusCode(
StatusCodes.Status500InternalServerError,
Responses.ErrorResponse with { Message = ex.Message, Content = JsonConvert.SerializeObject(ex), RequestId = Request.HttpContext.Connection.Id }
);
}
}
(In Tests.cs)
...
var context = new DbContextMock();
...
var service = GetOrdersService(context.Object, _pdcClientFactory.Object);
...
await service.PutOrdersAsync();

How to return the same status code from a second API call

I have an ASP.NET Core API calling a second API.
I throw an exception in my services layer, if there is an error from the second API:
var response = await httpClient.SendAsync(request); //call second API
if (!response.IsSuccessStatusCode)
{
//return HTTP response with StatusCode = X, if response.StatusCode == X
throw new HttpRequestException(await response.Content.ReadAsStringAsync());
//this always returns 400
}
How can I throw an exception that will return a response with the same status code from the second API call?
If I use HttpRequestException it will always return 400, even if the response object had StatusCode = 500.
EDIT:
The first API endpoint looks like this:
public async Task<ActionResult<HttpResponseMessage>> CreateTenancy([FromBody]TenancyRequest tenancy)
{
//Make some calls...
return Created(string.Empty, new { TenancyID = newTenancyExternalId });
}
The second API endpoint looks like this:
[HttpPost]
public IHttpActionResult CreateTenancy([FromBody]TenancyDTO tenancyDTO)
{
var tenancy = GetTenancy();
return Created(string.Empty, tenancy);
}
I've tried using throw new HttpResponseException(response); but this removes the descriptive Exception message, the payload ends up like this:
{
"Code": 500,
"CorrelationId": "2df08016-e5e3-434a-9136-6824495ed907",
"DateUtc": "2020-01-30T02:02:48.4428978Z",
"ErrorMessage": "Processing of the HTTP request resulted in an exception. Please see the HTTP response returned by the 'Response' property of this exception for details.",
"ErrorType": "InternalServerError"
}
I'd like to keep the ErrorMessage value in the original payload:
{
"Code": 400,
"CorrelationId": "ff9466b4-8c80-4dab-b5d7-9bba1355a567",
"DateUtc": "2020-01-30T03:05:13.2397543Z",
"ErrorMessage": "\"Specified cast is not valid.\"",
"ErrorType": "BadRequest"
}
The end goal is to have this returned:
{
"Code": 500,
"CorrelationId": "ff9466b4-8c80-4dab-b5d7-9bba1355a567",
"DateUtc": "2020-01-30T03:05:13.2397543Z",
"ErrorMessage": "\"Specified cast is not valid.\"",
"ErrorType": "InternalServerError"
}
I tried something simple as changing the return type of the API endpoint and returning the object as it when there is an error. Otherwise, build your own HttpResponseMessage and return that. This snippet below uses text but you can use a serializer to serialize other content if you have.
public async Task<HttpResponseMessage> Test(string str)
{
var httpClient = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, $"myAPI that returns different errors 400, 404, 500 etc based on str");
var response = await httpClient.SendAsync(request);
if (!response.IsSuccessStatusCode)
return response;
// do something else
return new HttpResponseMessage(System.Net.HttpStatusCode.OK) { Content = new StringContent("Your Text here") };
}
Other approach of using Filters
The other approach of using IHttpActionResult as your return type, you can use Filters to conform all your HttpResponseMessages to IHttpActionResult.
Filter: Create a separate cs file and use this filter definition.
public class CustomObjectResponse : IHttpActionResult
{
private readonly object _obj;
public CustomObjectResponse(object obj)
{
_obj = obj;
}
public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
{
HttpResponseMessage response = _obj as HttpResponseMessage;
return Task.FromResult(response);
}
}
and in your API, you would use your filter like so,
public async Task<IHttpActionResult> Test(string str)
{
var httpClient = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, $"http://localhost:4500/api/capacity/update-mnemonics/?mnemonic_to_update={str}");
var response = await httpClient.SendAsync(request);
if (!response.IsSuccessStatusCode)
return new CustomObjectResponse(response);
// Other Code here
// Return Other objects
KeyValuePair<string, string> testClass = new KeyValuePair<string, string>("Sheldon", "Cooper" );
return new OkWithObjectResult(testClass);
// Or Return Standard HttpResponseMessage
return Ok();
}
You could simply make your API call and copy its response code into something compatible with IStatusCodeActionResult.
An alternative s to throw a custom exception. Create something like
public class ApiCallException : Exception
{
public APiCallException(int statusCode, ...)
{
ApiStatusCode = statusCode;
}
int ApiStatusCode { get; }
...
}
and copy over the status code from your API result, and then throw the exception.
var response = await httpClient.SendAsync(request); //call second API
if (!response.IsSuccessStatusCode)
{
var content = await response.Content.ReadAsStringAsync();
throw new ApiCallException(500, content);
}
You can then register an exception filter to deal with the result when calling AddMvc.
services.AddMvc(options => options.Filters.Add<ExceptionFilter>());
where ExceptionFilter could be something like
public class ExceptionFilter : IExceptionFilter
{
// ...
public void OnException(ExceptionContext context)
{
if (context.Exception is ApiCallException ace)
{
var returnObject = CreateReturnObjectSomehow();
context.Result = new ObjectResult(returnObject) { StatusCode = ace.StatusCode };
}
else
{
// do something else
}
}
}
Thanks Jawad and Kit for providing great answers which helped me work out the solution below:
Turns out there was some middleware handling the exception:
public async Task Invoke(HttpContext httpContext)
{
try
{
await _next(httpContext);
}
catch (Exception exception)
{
if (httpContext.Response.HasStarted) throw;
var statusCode = ConvertExceptionToHttpStatusCode(exception);
httpContext.Response.Clear();
httpContext.Response.StatusCode = (int)statusCode;
httpContext.Response.ContentType = "application/json";
if (statusCode != HttpStatusCode.BadRequest)
{
_logger.Error(exception, "API Error");
}
await httpContext.Response.WriteAsync(JsonConvert.SerializeObject(new Error(statusCode, httpContext.Request.CorrelationId(), exception.Message, statusCode.ToString())));
}
}
The Error class looks like this:
public class Error
{
public int Code { get; }
public Guid? CorrelationId { get; }
public DateTime DateUtc { get; }
public string ErrorMessage { get; }
public string ErrorType { get; }
public Error(HttpStatusCode code, Guid? correlationId, string errorMessage, string errorType)
{
Code = (int)code;
CorrelationId = correlationId;
DateUtc = DateTime.UtcNow;
ErrorMessage = errorMessage;
ErrorType = errorType;
}
}
I created this class:
public class ApiCallException : Exception
{
public int StatusCode { get; }
public override string Message { get; }
public ApiCallException(int statusCode, string message)
{
StatusCode = statusCode;
Message = message;
}
}
Then updated my original code to have this:
if (!response.IsSuccessStatusCode)
{
throw new ApiCallException((int)response.StatusCode, await response.Content.ReadAsStringAsync());
}

C# Exception Handling: Get elements of extended CustomException

I want to catch custom exceptions and return a custom object containing information about the exception. For this I have created a custom CustomInvalidException that extends Extension:
[Serializable]
public class CustomInvalidException<T> : Exception
where T : Entity
{
public string Title { get; set; }
public HttpStatusCode StatusCode { get; set; }
public CustomInvalidException(string message)
: base($"{typeof(T).Name}: {message}")
{
this.Title = "Custom invalid exception";
this.StatusCode = HttpStatusCode.BadRequest;
}
}
At some point I can throw this exception:
if (something) throw new CustomInvalidException<CustomEntity>(message);
And I can catch this exception and handle it in some middleware I use for this:
private static Task HandleExceptionAsync(HttpContext context, Exception exception)
{
// Here I want to create a response with error information
context.Response.ContentType = "application/json";
context.Response.StatusCode = (int)exception.StatusCode;
var result = new {exception.Title, exception.StatusCode, exception.Message}
return context.Response.WriteAsync(JsonConvert.SerializeObject(result));
}
This gives errors since I try to get exception.Title and exception.StatusCode, and these elements do not exist on the Exception class. In debug mode I can see that these elements are present since I thrown the CustomInvalidException.
I want to keep the HandleExceptionAsync method as generic as possible, but I can only get these values by explicitly casting to CustomInvalidException (which is not what I want). How can I achieve the result as generic as possible?
EDIT
HandleExceptionAsync is called in the ErrorHandlingMiddleware class:
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)
{
// Handle exception
}
}
Which in turn is called in the Configure of Startup.cs:
app.UseMiddleware(typeof(ErrorHandlingMiddleware));

Wrapping all responses

I want to wrap all my http responses.
For example we have an action which returns some JSON data:
public IActionResult Get()
{
var res = new
{
MessageBody = "Test",
SomeData = 1
};
return Ok(res);
}
I want my response looks like:
{
"StatusCode":200,
"Result":
{
"MessageBody ":"Test",
"SomeData":1
}
}
If there is error then response must contain ErrorMessage field in a response.
In the mvc 5 I used the DelegationHandler, but in the asp.net core this class is not implemented. Now, we have to use middlewares.
This is code for mvc 5:
public class WrappingHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var response = await base.SendAsync(request, cancellationToken);
return BuildApiResponse(request, response);
}
private static HttpResponseMessage BuildApiResponse(HttpRequestMessage request, HttpResponseMessage response)
{
object content;
string errorMessage = null;
if (response.TryGetContentValue(out content) && !response.IsSuccessStatusCode)
{
HttpError error = content as HttpError;
if (error != null)
{
content = null;
errorMessage = error.Message;
#if DEBUG
errorMessage = string.Concat(errorMessage, error.ExceptionMessage, error.StackTrace);
#endif
}
}
var newResponse = request.CreateResponse(response.StatusCode, new ApiResponse(response.StatusCode, content, errorMessage));
foreach (var header in response.Headers)
{
newResponse.Headers.Add(header.Key, header.Value);
}
return newResponse;
}
}
and, a middleware for asp.net core. There are no TryGetContentValue, HttpError and other stuff in asp.net core. So, I am trying to read response body first:
public class FormatApiResponseMiddleware
{
private readonly RequestDelegate _next;
public FormatApiResponseMiddleware(RequestDelegate next)
{
_next = next;
}
private bool IsSuccessStatusCode(int statusCode)
{
return (statusCode >= 200) && (statusCode <= 299);
}
public async Task Invoke(HttpContext context)
{
object content = null;
string errorMessage = null;
if (!IsSuccessStatusCode(context.Response.StatusCode))
{
content = null;
//how to get error
}
var body= context.Response.Body;
}
}
But, Body stream has CanRead equal false and I get error that stream cannot be read. How to properly wrap response?
I suggest using ExceptionHandlerMiddleware as a template/sample on how your middleware should be implemented.
For example, you should be aware about case, when response has already started
// We can't do anything if the response has already started, just abort.
if (context.Response.HasStarted)
{
_logger.LogWarning("The response has already started, the error handler will not be executed.");
throw;
}
or don't forget to clear current response, if you want to replace it:
context.Response.Clear();
Moreover, maybe you will find useful just to reuse it, and implement your own error handler instead of a full middleware. That way you can send a custom JSON error to the client. For that, define a class that will represent your custom error:
public class ErrorDto
{
public int Code { get; set; }
public string Message { get; set; }
// other fields
public override string ToString()
{
return JsonConvert.SerializeObject(this);
}
}
Then register an exception handler middleware in the Configure method. Pay attention to the order in which the middleware is registered, and make sure it’s registered before MVC for example:
app.UseExceptionHandler(errorApp =>
{
errorApp.Run(async context =>
{
context.Response.StatusCode = 500; // or another Status
context.Response.ContentType = "application/json";
var error = context.Features.Get<IExceptionHandlerFeature>();
if (error != null)
{
var ex = error.Error;
await context.Response.WriteAsync(new ErrorDto()
{
Code = 1, //<your custom code based on Exception Type>,
Message = ex.Message // or your custom message
// … other custom data
}.ToString(), Encoding.UTF8);
}
});
});

Web Api always returns http status code 200 when an exception occurs

public class GlobalExceptionHandler : ExceptionHandler
{
public override void Handle(ExceptionHandlerContext context)
{
context.Result = new NiceInternalServerExceptionResponse("The current operation could not be completed sucessfully.);
}
}
When a call this Get action:
[HttpGet]
public async Task<IHttpActionResult> Get()
{
Convert.ToInt16("this causes an exception state");
var data = await service.Get();
return Ok(data);
}
An exception is raised... and my global exc handler is triggered.
When my custom response is returned to the client my fiddler always says:
Result: 200
I could also change the return Ok(data); to return NotFound();
That will not change anything in the result status code.
How can I overwrite/intercept the http status creation and return my own status code 500 instead?
On my web client I need to show a nice error dialog with a logging id + error message ONLY when status code 500 is returned.
You need to set the status code on the IHttpActionResult:
public class NiceInternalServerExceptionResponse : IHttpActionResult
{
public string Message { get; private set; }
public HttpStatusCode StatusCode { get; private set; }
public NiceInternalServerExceptionResponse(
string message,
HttpStatusCode code)
{
Message = message;
StatusCode = code;
}
public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
{
var response = new HttpResponseMessage(StatusCode);
response.Content = new StringContent(Message);
return Task.FromResult(response);
}
}
And in your GlobalExceptionHandler pass HttpStatusCode.InternalServerError (500):
public override void Handle(ExceptionHandlerContext context)
{
context.Result = new NiceInternalServerExceptionResponse(
"The current operation could not be completed sucessfully.",
HttpStatusCode.InternalServerError);
}
I do it like this...
[HttpPost]
public HttpResponseMessage Post()
{
try
{
// Do stuff
}
catch (Exception ex)
{
// Something went wrong - Return Status Internal Server Error
return new HttpResponseMessage(HttpStatusCode.InternalServerError);
}
}
Works same for a Get.
You could use next code for custom error:
return Content(HttpStatusCode.NotFound, "Foo does not exist.");

Categories

Resources