Angular on top of ASP.NET MVC, displaying Errors

Eric Brown - Cal

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?

Iravanchi

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.

Step 1. WebApi controllers in Server side

I have a specific DTO for communicating Validation Errors to the client, since I believe they are different from Exceptions. 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);
    }

Step 2. Handling unexpected exceptions

As I said, I believe that only real unhandled Exceptions 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());

Step 3. Intercepting and showing errors in Client side

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.

edited at
0

Comments

0 comments
Login to comment

Related