I'm using web API and implementing delegating handler.
I have a customization of Json serializer / deserializer which is registered as a formatter in the API configuration.
var globalFormatters = GlobalConfiguration.Configuration.Formatters;
var jsonFormatter = globalFormatters.JsonFormatter;
jsonFormatter.SerializerSettings.Formatting = Newtonsoft.Json.Formatting.Indented;
jsonFormatter.SerializerSettings.Converters.Add(...)
As for exception handling, I've added an ExceptionFilterAttribute, and also added it as a filter in the configuration.
public class MethodAttributeExceptionHandling : ExceptionFilterAttribute
{
public override void OnException(HttpActionExecutedContext actionExecutedContext)
{
var errorHandler = new ErrorHandler();
var response = errorHandler.ProcessError(actionExecutedContext);
actionExecutedContext.Response = response;
}
}
This seemed to be working well, until I encountered a deserialization exception,
which did not get caught in my filter.
I've read the exception handling documentation Here which mentions serialization exception are not caught by the filter (It does not mention serialization however), and I couldn't find any solution to catch it and handle it properly.
In case it will help someone else,
In order to deal with this issue I implemented an ActionFilterAttribute, where in my implementation of the OnActionExecuting I'm checking if the model state is invalid, and do error handling stuff if so.
That's the spot just before the action execution and after the object deserialization.
public class ValidateModelAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
if (actionContext.ModelState.IsValid)
{
return;
}
//Do error handling Stuff...
}
}
Related
I have created by some example the ErrorController, which is handling Exception. Currently I have this:
[Route("[controller]")]
[ApiController]
[Authorize]
[ApiExplorerSettings(IgnoreApi = true)]
public class ErrorController : ControllerBase
{
public IActionResult ServerError()
{
var feature = HttpContext.Features.Get<IExceptionHandlerFeature>();
ErrorResponse response;
if (feature != null && feature.Error.GetType() == typeof(HttpResponseException))
{
response = new ErrorResponse
{
Error = "Error during processing request",
Message = feature.Error.Message
};
HttpContext.Response.StatusCode = ((HttpResponseException) feature.Error).HttpCode;
}
else
{
response = new ErrorResponse
{
Error = "Unexpected Server Error",
Message = feature?.Error.Message
};
}
return Content(JsonSerializer.Serialize(response), "application/json");
}
}
So whenever I throw in my method in controllers HttpResponseException, It will read it, and create response with corresponding code. But doing it through this, will log the HttpResponseException, which is not desired behaviour.
I have found solutions with Request.CreateResponse(), but that method does not exists, but when I replicated this method by myself, it is not desired behaviour (because of Swashbuckle/Swagger UI - The returning type is not the model object, but the HttpResponseMessage).
Found also something about ExceptionFilterAttribute, where I produced this code:
public class HttpExceptionFilterAttribute : ExceptionFilterAttribute
{
public override void OnException(ExceptionContext context)
{
if (!(context.Exception is HttpResponseException)) return;
var exception = (HttpResponseException) context.Exception;
context.Result = new ObjectResult(""){StatusCode = exception.HttpCode};
}
}
But can't tell where to globally registered (based on this article).
So how to correctly manage returning desired object or some error with code, in a way that it will not be logged as warn?
Filters are registered globally in the Startup.cs via the options of AddMVC or AddControllersWithViews options.
Another way to handle exceptions globally is using an exception handling middleware which would catch also unexpected exceptions (which is my preferred way).
I have received the assignment to make our C# Webapi return always error responses with the following format:
{
"error": {
"code": 15,
"message": "My custom error message"
}
}
Because of that I registered my own ExceptionFilterAttribute:
public class CustomExceptionFilterAttribute : ExceptionFilterAttribute
{
class CustomError
{
public int code { get; set; }
public String message { get; set; }
}
public override void OnException(HttpActionExecutedContext context)
{
if (context.Exception is BaseException)
{
BaseException exception = (BaseException)context.Exception;
HttpError error = new HttpError();
CustomError customError = new CustomError
{
code=exception.CustomError.code,
message=exception.CustomError.message
};
error.Add("error", customError);
context.Response = context.Request.CreateErrorResponse(HttpStatusCode.InternalServerError, error);
}
else
{
context.Response = new HttpResponseMessage(HttpStatusCode.NotImplemented);
}
}
This is working very well when the exception was throwed in a controller. But if the exception was throwed in an attribute (AuthorizeAttribute or EnableQueryAttribute), althoug my custom ExceptionFilter is called and the same code is executed, the response generated ignores the given HttpError and the response has the following body:
{
"error": {
"code": "",
"message": ""
}
}
I'm not very skilled in c#, I'm pretty sure that I'm doing something wrong, but I don't know what :S
Thanks in advance.
Edit 1:
I'm applying the Attribute that throws the exception at each method where the attribute is needed. For example I have an Odata Controller for an entity called "event":
[CustomAuthentication]
[CustomEnableQueryAttribute(PageSize = 20)]
public IQueryable<Event> Get()
{
(...)
return result;
}
As said, if the exception is throwed in the body of the controller, then my CustomExceptionFilter is invoked, and the custom response is created correctly.
But if the exception is thrown in the CustomAuthenticationAttribute or in the CustomEnableQueryAttribute, then altough my CustomExceptionFilter is invoked and the very same code is executed, the body response is wrong (see example).
Filter are mean for only controller but for global errors you need to use Global Error Filter in WebAPI.
to handle error thrown from attribute you need to Create Global Error Hnandler : https://learn.microsoft.com/en-us/aspnet/web-api/overview/error-handling/exception-handling
class OopsExceptionHandler : ExceptionHandler
{
public override void HandleCore(ExceptionHandlerContext context)
{
context.Result = new TextPlainErrorResult//you have to create this class
{
Request = context.ExceptionContext.Request,
Content = "Oops! Sorry! Something went wrong." +
"Please contact support#contoso.com so we can try to fix it."
};
}
}
there are a number of cases that exception filters can't handle. For example:
Exceptions thrown from controller constructors.
Exceptions thrown from message handlers.
Exceptions thrown during routing.
Exceptions thrown during response content serialization .
I am using asp.net web api.
[Route("api/employee")]
[HttpPost]
public dynamic GetData(EmployeeModel model)
{
EmployeeService emp = new EmployeeService();
emp.GetData(model);
}
This is how I am handling error globally:
public class ExceptionFilter : ExceptionFilterAttribute
{
public override void OnException(HttpActionExecutedContext context)
{
//Code to log the exception goes here:
}
}
in the WebApiConfig.cs file, I am registering the filter:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
....
config.Filters.Add(new ExceptionFilter());
}
}
Whenever there is an exception, the exception filter is able to catch the exception & It can log the exception. All this is working fine.
What I want: With every exception, I want to log specific details, those details are available in respective methods but how do I pass them to exception filter? The only work around I see is, add try catch block in individual methods and log the exception along with specific details.
Another thing that I want to capture is the request object of each request. In this case it will be ExployeeModel. Even if I somehow get the request object, how to I type cast into correct type. One endpoint can expect EmployeeModel other can expect DepartmentModel.
I'd like to have a way to intercept the exception that occurs when you send in malformed json to a webapi endpoint, so that I can return a semantic error code as opposed to just 500. (e.g. "Fix your broken JSON or go to hell")
You can create your custom validation filter attribute by deriving from ActionFilterAttribute:
public class ValidationFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
if (!actionContext.ModelState.IsValid)
{
actionContext.Response = actionContext
.Request
.CreateErrorResponse(HttpStatusCode.BadRequest,
actionContext.ModelState);
}
}
}
Now, you may either decorate your actions with it:
[HttpGet]
[ValidationFilter()]
public string DoSomethingCool()
or register it globally via your config:
config.Filters.Add(new ValidationFilterAttribute());
Hopefully there are some WCF wizards out there that can spot my mistake here.
I am trying to set up a global error handler via an IErrorHandler based behaviorExtension on a RESTful JSON WCF Service. The method is decorated as such:
[OperationContract]
[WebGet(UriTemplate = "screens/info", ResponseFormat = WebMessageFormat.Json)]
The IErrorHandler implementation is:
public class ErrorHandler : IErrorHandler
{
public void ProvideFault(Exception error,
MessageVersion version,
ref Message fault)
{
var error = new JsonError
{
Message = error.Message,
FaultCode = -1,
StackTrace = error.StackTrace
};
fault = Message.CreateMessage(version,
"",
ideaScreeningError,
new DataContractJsonSerializer(
ideaScreeningError.GetType()));
// tell WCF to use JSON encoding rather than default XML
var wbf = new WebBodyFormatMessageProperty(WebContentFormat.Json);
fault.Properties.Add(WebBodyFormatMessageProperty.Name, wbf);
//Modify response
var rmp = new HttpResponseMessageProperty
{
StatusCode = HttpStatusCode.BadRequest,
StatusDescription = "Bad Request"
};
fault.Properties.Add(HttpResponseMessageProperty.Name, rmp);
}
public bool HandleError(Exception error)
{
return true;
}
}
I can verify (via breakpoints) that the extension is being called and is executing properly. When I look at the result of the AJAX call in the browser, I can see that WCF is still returning a 500 Internal Server Error rather than the Fault details that I've specified in the error handler.
If I change Exception types being thrown in the WCF method, those are reflected in the result in the browser so I can surmise that WCF is doing something to handle the Exception and return something internally.
How do I make it stop!?
EDIT
I'm adding the custom Behavior Element:
public class ErrorBehaviorElement : BehaviorExtensionElement
{
protected override object CreateBehavior()
{
return new ErrorBehavior();
}
public override Type BehaviorType
{
get { return typeof(ErrorBehavior); }
}
}
And Behavior:
internal class ErrorBehavior : WebHttpBehavior
{
protected override void AddServerErrorHandlers(ServiceEndpoint endpoint,
EndpointDispatcher endpointDispatcher)
{
// clear default error handlers.
endpointDispatcher.ChannelDispatcher.ErrorHandlers.Clear();
// add the Json error handler.
endpointDispatcher.ChannelDispatcher.ErrorHandlers.Add(
new ErrorHandler());
}
}
The issue here lies with the WCF Rest Starter Kit (which I didn't realize was in use since I didn't start this project), more specifically WebServiceHost2. I opened the ServiceHost in Reflector and found this lovely little piece of code in OnOpening():
if (endpoint.Behaviors.Find<WebHttpBehavior>() != null)
{
endpoint.Behaviors.Remove<WebHttpBehavior>();
WebHttpBehavior2 item = new WebHttpBehavior2();
// other code omitted
endpoint.Behaviors.Add(item);
}
As you can see, no matter what behavior you want added to the endpoint, as long as it inherits from WebHttpBehavior the Rest Start Kit components will hijack your handler, remove it, and replace it with its own.
Keep in mind that WebHttpBehavior2 also inherits from WebHttBehavior so inheriting from WebHttpBehavior2 in my extension did nothing to help the matter.
The first step was to create a new WebSeriveHost that derived from WebServiceHost2 and overrode OnOpening() and re-hijack what the Rest Starter Kit stole from me:
if(endpoint.Behaviors.Find<WebHttpBehavior>() != null)
{
endpoint.Behaviors.Remove<WebHttpBehavior>();
var item = ErrorBehavior();
// other code
endpoint.Behaviors.Add(item);
}
And then create a new WebServiceHostFactory that returned my custom WebServiceHost type.
Don't forget to set the ContentType of the response as well:
rmp.Headers[HttpResponseHeader.ContentType] = "application/json";
Based on comments I would try to remove common webHttpBehavior. You have defined your own behavior derived from webHttp. There is no reason to have two webHttp behaviors in your service configuration. Moreover webHttp behavior adds its own error handler which behaves exactly as you describe. Maybe it will not help but you can give it a try.