We are putting an angular front end on an existing asp.net c# MVC applicaiton. In our server code, we extensilvely use custom exceptions to return buisness rule errors.
Is there a best practice or slickest way to handle an exception on an mvc controller or an webApi controller (actually bullbing up from the buisness layer) and getting it across to angular and displaying it in a "user error" popup? How are folks solving this problem?
Other guys already gave great answers, but I want to elaborate my approach since I guess it will be covering both ends (frontend and server) with more details.
Here's my complete approach to error and exception handling in WebAPI + AngularJS applications.
I have a specific DTO for communicating Validation Errors to the client, since I believe they are different from Exception
s. An exception will result in a 500 error, where a validation result should result in 400 (Bad Request) error.
So, here's my ApiValidationResult
class:
public class ApiValidationResult
{
public List<ApiValidationError> Errors { get; set; }
public static ApiValidationResult Failure(string errorKey)
{
return new ApiValidationResult {Errors = new List<ApiValidationError> {new ApiValidationError(errorKey)}};
}
// You can add a bunch of utility methods here
}
public class ApiValidationError
{
public ApiValidationError()
{
}
public ApiValidationError(string errorKey)
{
ErrorKey = errorKey;
}
// More utility constructors here
public string PropertyPath { get; set; }
public string ErrorKey { get; set; }
public List<string> ErrorParameters { get; set; }
}
I always use my own base class for WebApi (and MVC) controllers, so I can use them to add handy result method, such as this:
public abstract class ExtendedApiController : ApiController
{
protected IHttpActionResult ValidationError(string error)
{
return new ValidationErrorResult(ApiValidationResult.Failure(error), this);
}
// More utility methods can go here
}
It uses a custom IHttpActionResult
that I've created specifically for this purpose:
public class ValidationErrorResult : NegotiatedContentResult<ApiValidationResult>
{
public ValidationErrorResult(ApiValidationResult content, IContentNegotiator contentNegotiator, HttpRequestMessage request, IEnumerable<MediaTypeFormatter> formatters)
: base(HttpStatusCode.BadRequest, content, contentNegotiator, request, formatters)
{
}
public ValidationErrorResult(ApiValidationResult content, ApiController controller)
: base(HttpStatusCode.BadRequest, content, controller)
{
}
}
As a result, I can cleanly use codes such as this in my controller actions:
[HttpPost]
public IHttpActionResult SomeAction(SomeInput input)
{
// Do whatever...
if (resultIsValid)
{
return Ok(outputObject);
}
return ValidationResult(errorMessage);
}
As I said, I believe that only real unhandled Exception
s should result in a 500 (Internal server error) responses.
Such unhandled exceptions are automatically converted to a 500 result by WebApi. The only thing I need to do about them, is to log them. So, I create an implementation of IExceptionLogger
interface and register it like this:
GlobalConfiguration.Configuration.Services.Add(typeof(IExceptionLogger), new UnhandledExceptionLogger());
AngularJS allows intercepting all HTTP calls sent from $http
service. I use this to centralize all message popups. Here's my interceptor code:
appModule.factory("errorsHttpInterceptor", [
"$q", "$rootScope", "$injector",
($q: ng.IQService, $rootScope: IAppRootScopeService, $injector) => {
return {
'responseError': rejection => {
// Maybe put the error in $rootScope and show it in UI
// Maybe use a popup
// Maybe use a 'toast'
var toastr = $injector.get('toastr');
toastr.error(...);
return $q.reject(rejection);
}
};
}
]);
You can do all sorts of things in the interceptor, such as logging debug messages, or applying key to display-string translation of error codes. You can also distinguish between 500 and 400 errors, and display different types of error messages.
I use toastr library which I think shows a nice UI and is very handy in API level.
Finally, I register the interceptor like this:
appModule.config([
'$httpProvider',
($httpProvider: ng.IHttpProvider) => {
$httpProvider.interceptors.push('errorsHttpInterceptor');
}
]);
The syntax is in TypeScript, which is very similar to JavaScript and I'm sure you can figure out what it means.
Collected from the Internet
Please contact [email protected] to delete if infringement.
Comments