Reputation: 14409
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?
Upvotes: 5
Views: 6213
Reputation: 3161
Another approach is returning Content.
Here is how I do it (end-to-end). From my API:
return Content(HttpStatusCode.<statuscode>, "ResponseContent: " + "my_custom_error");
Here, HttpStatusCode. and "my_custom_error" can be the response returned from another API Layer. In that case, I simply read the response from that layer and pass it to the client side.
//If I'm getting output from another API/Layer then I pass it's output like this
var output = response.Content.ReadAsStringAsync().Result;
return Content(response.StatusCode, "ResponseContent: " + output);
For more details on HttpStatusCodes, please refer HttpStatusCode Enumeration
And in the Angular code, I read it like this:
$http({ method: methodType, url: endpoint })
.then(function (response) {
response.status; //gets you the HttpStatusCode to play with
response.data; //gets you the ReponseContent section
}, function (response) {
response.status; //gets you the HttpStatusCode
response.data; //gets you the ReponseContent section
});
Make sure while making http calls the responseType
is not set to 'JSON'. Since the data returned by the API is not in JSON format at this stage.
From $http service in AngularJS, the response object has these properties:
data – {string|Object} – The response body transformed with the transform functions.
status – {number} – HTTP status code of the response.
headers – {function([headerName])} – Header getter function.
config – {Object} – The configuration object that was used to generate the request.
statusText – {string} – HTTP status text of the response.
Upvotes: 1
Reputation: 5159
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.
Upvotes: 5
Reputation: 2048
We have just finished a large MVC app with an Angular frontend.
We just let errors flow, eg any webpages just get an error has but without the stack trace (not the yellow screen, the nice Error has *** but within your master page etc)
Web API calls just return the correct HTTP status. They can include some details if you wish.
But, you dont want to lose these errors, so we just installed elmah with
Install-Package elmah
and, it JUST WORKS, errors just end up in the log, users get told something as not worked etc.. NO extra work needed
To make our UI nicer we did the following, for
Unhandled errors
MVC Pages
Just let MVC front end do its job, it will tell the user in a nice way something has gone wrong.
Angular web calls
in the .error function alert the user eg
}).error(function (data, status, headers, config) {
$scope.addAlert('danger', 'Error deleting autosave details');
}
Let errors stack, if you don't to lose an error due to an error overwriting it. addAlert just writes to an array that is data bound to on the front end.
Handled Errors If you are handling them, then you have managed what happens, but to log these
MVC
If you wish to just log them, the elmah
API has a single call for this
ErrorSignal.FromCurrentContext().Raise(exception );
If you are using error attributes then you can use this
public class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new ElmahHandledErrorLoggerFilter());
filters.Add(new HandleErrorAttribute());
}
}
public class ElmahHandledErrorLoggerFilter : IExceptionFilter
{
public void OnException(ExceptionContext context)
{
// Log only handled exceptions, because all other will be caught by ELMAH anyway.
if (context.ExceptionHandled)
ErrorSignal.FromCurrentContext().Raise(context.Exception);
}
}
Also, for general logging to the ELMAH framework checkout https://github.com/TrueNorthIT/Elmah
Upvotes: 2
Reputation: 2941
Typically, I Have been doing this kind of thing within our WEB API, returning the correct status code is key with this, this is of course completely agnostic as to which of the many front end frameworks you want to use.
public IHttpActionResult Get(DateTime? updatesAfter = null)
{
try
{
// Do something here.
return this.Ok(result);
}
catch (Exception ex) // Be more specific if you like...
{
return this.InternalServerError(ex);
throw;
}
}
The helper methods that are now shipped with Web Api v2 ApiControllers are excellent...
this.BadRequest()
this.InternalServerError()
this.Ok()
this.Unauthorized()
this.StatusCode()
this.NotFound()
Some of them (such as InternalServerError) allow you to pass an exception or message (or simply an object) as a param.
Typically as with any front end framework or library there will be a fail or error callback that you can provide when initialising the ajax call to your API method, this will be called in scenarios where error status codes are returned.
Upvotes: 3
Reputation: 415
For my API Controllers I return HttpResponseMessage
. Please see my example below. I hope this helps.
On the response message you can also pass back your object to the front end.
WEB API
// Get all
[HttpGet]
public HttpResponseMessage Get()
{
try
{
return Request.CreateResponse(HttpStatusCode.OK, myObject);
}
catch (Exception e)
{
return Request.CreateErrorResponse(HttpStatusCode.InternalServerError, "Error Message");
}
}
Angular JS
// Get Request on Page Load
$http({
url: '/api/department/',
method: 'GET',
}).success(function (data) {
$scope.departments = data;
}).error(function (error) {
$scope.error = error;
});
HTML
<div class="alert alert-danger animate-show" role="alert" ng-show="error != null">{{ error }} </div>
<div class="alert alert-success animate-show" role="alert" ng-show="success != null">{{ success }}</div>
Upvotes: 0