In my application, Need to handle exceptions in two ways:
Can be handled by Front desk
Try
{
//code
}
catch(Exception ex)
{
Exception("Contact Admin");
}
Displaying user friendly exact error message to user that can be handled by users itself.
Try
{
enter code here
}
Catch(Exception ex)
{
`code here`
}
How to achieve the 2nd way in application?
The way I have seen it previously are the following:
To provide an optional list of user messages as part of your response:
Example:
MyExtendedObject o = new MyExtendedObject();
try {
// do your processing
o.setValue(...);
return o;
} catch (Exception e) {
log.error(e);
o.addErrorMessage("The processing has been unable to complete, please try again");
// getValue() will be empty for the front-end.
return o;
}
If you are in a web application, Return a user-friendly message as part of your http error response
Related
I'm learning Blazor.
I have created a Blazor WASM App with the "ASP.NET Core Hosted" option.
So I have 3 projects in the solution: Client, Server and Shared.
The following code is in the Client project and works perfectly when the endpoint is correct (obviously). But at some point I made a mistake and messed up the request URI, and then I noticed that the API returned an HTML page with code 200 OK (as you can see in the Postman screenshot below the code).
I expected one of my try-catches to get this, but the debugger jumps to the last line (return null) without throwing an exception.
My first question is why?
My second question is how can I catch this?
I know fixing the endpoint fixes everything, but would be nice to have a catch that alerts me when I have mistyped an URI.
Thanks.
private readonly HttpClient _httpClient;
public async Task<List<Collaborator>> GetCollaborators()
{
string requestUri = "api/non-existent-endpoint";
try
{
var response = await _httpClient.GetFromJsonAsync<CollaboratorsResponse>(requestUri);
if (response == null)
{
// It never enters here. Jumps to the last line of code.
}
return response.Collaborators;
}
catch (HttpRequestException)
{
Console.WriteLine("An error occurred.");
}
catch (NotSupportedException)
{
Console.WriteLine("The content type is not supported.");
}
catch (JsonException)
{
Console.WriteLine("Invalid JSON.");
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
return null;
}
it is a never good idea to use GetFromJsonAsync, You are not the first who are asking about the strange behavior. Try to use GetAsync. at least you will now what is going on.
var response = await client.GetAsync(requestUri);
if (response.IsSuccessStatusCode)
{
var stringData = await response.Content.ReadAsStringAsync();
var result = JsonConvert.DeserializeObject<CollaboratorsResponse>(stringData);
... your code
}
else
{
var statusCode = response.StatusCode.ToString(); // HERE is your error status code, when you have an error
}
I have an application with both MVC and 'new' ApiController endpoints in ASP.NET Core 2.2 co-existing together.
Prior to adding the API endpoints, I have been using a global exception handler registered as middleware using app.UseExceptionHandler((x) => { ... } which would redirect to an error page.
Of course, that does not work for an API response and I would like to return an ObjectResult (negotiated) 500 result with a ProblemDetails formatted result.
The problem is, I'm not sure how to reliably determine in my 'UseExceptionHandler' lambda if I am dealing with an MVC or a API request. I could use some kind of request URL matching (eg. /api/... prefix) but I would like a more robust solution that won't come back to bite me in the future.
Rough psuedo-code version of what I'm trying to implement is:
app.UseExceptionHandler(x =>
{
x.Run(async context =>
{
// extract the exception that was thrown
var ex = context.Features.Get<IExceptionHandlerFeature>()?.Error;
try
{
// generically handle the exception regardless of what our response needs to look like by logging it
// NOTE: ExceptionHandlerMiddleware itself will log the exception
// TODO: need to find a way to see if we have run with negotiation turned on (in which case we are API not MVC!! see below extensions for clues?)
// TODO: ... could just use "/api/" prefix but that seems rubbish
if (true)
{
// return a 500 with object (in RFC 7807 form) negotiated to the right content type (eg. json)
}
else
{
// otherwise, we handle the response as a 500 error page redirect
}
}
catch (Exception exofex)
{
// NOTE: absolutely terrible if we get into here
log.Fatal($"Unhandled exception in global error handler!", exofex);
log.Fatal($"Handling exception: ", ex);
}
});
});
}
Any ideas?
Cheers!
This might be a bit different than what you expect, but you could just check if the request is an AJAX request.
You can use this extension:
public static class HttpRequestExtensions
{
public static bool IsAjaxRequest(this HttpRequest request)
{
if (request == null)
throw new ArgumentNullException(nameof(request));
if (request.Headers == null)
return false;
return request.Headers["X-Requested-With"] == "XMLHttpRequest";
}
}
And then middleware with an invoke method that looks like:
public async Task Invoke(HttpContext context)
{
if (context.Request.IsAjaxRequest())
{
try
{
await _next(context);
}
catch (Exception ex)
{
//Handle the exception
await HandleExceptionAsync(context, ex);
}
}
else
{
await _next(context);
}
}
private static Task HandleExceptionAsync(HttpContext context, Exception exception)
{
//you can do more complex logic here, but a basic example would be:
var result = JsonConvert.SerializeObject(new { error = "An unexpected error occurred." });
context.Response.ContentType = "application/json";
context.Response.StatusCode = 500;
return context.Response.WriteAsync(result);
}
see this SO answer for a more detailed version.
If you want to check whether the request is routed to ApiController, you could try IExceptionFilter to hanlde the exceptions.
public class CustomExceptionFilter : IExceptionFilter
{
public void OnException(ExceptionContext context)
{
if (IsApi(context))
{
HttpStatusCode status = HttpStatusCode.InternalServerError;
var message = context.Result;
//You can enable logging error
context.ExceptionHandled = true;
HttpResponse response = context.HttpContext.Response;
response.StatusCode = (int)status;
response.ContentType = "application/json";
context.Result = new ObjectResult(new { ErrorMsg = message });
}
else
{
}
}
private bool IsApi(ExceptionContext context)
{
var controllerActionDesc = context.ActionDescriptor as ControllerActionDescriptor;
var attribute = controllerActionDesc
.ControllerTypeInfo
.CustomAttributes
.FirstOrDefault(c => c.AttributeType == typeof(ApiControllerAttribute));
return attribute == null ? false : true;
}
}
Thanks to all of the advice from others, but I have realised after some more thought and ideas from here that my approach wasn't right in the first place - and that I should be handling most exceptions locally in the controller and responding from there.
I have basically kept my error handling middleware the same as if it was handling MVC unhandled exceptions. The client will get a 500 with a HTML response, but at that point there isn't much the client can do anyway so no harm.
Thanks for your help!
I'm using Nancy to create a web api. I have a signed token that is passed in from the user to authenticate. This authentication is doen in the RequestStartup method in my own Bootstrapper. Now in some cases, for instance when I can't veryfy the signed token I would like to just be able to throw an exception and have that handled byt the OnError hanhdler in Nancy. However an exception thrown before the RequestStartup is finsihed isn't caught. The request generates a 500 error and I would like to return something else with my own error information.
I have the obvious case where I throw an exception but also possibilities of an exception being thrown in the GetIdentity() method.
I'm looking for any input in how to handle this.
protected override void RequestStartup(TinyIoCContainer container, IPipelines pipelines, NancyContext context)
{
base.RequestStartup(container, pipelines, context);
pipelines.OnError.AddItemToStartOfPipeline((ctx, exception) =>
container.Resolve<IErrorHandler>().HandleException(ctx, exception));
var identity = container.Resolve<IAuthenticationController>().GetIdentity();
var configuration = new StatelessAuthenticationConfiguration(_ => identity);
StatelessAuthentication.Enable(pipelines, configuration);
var logManager = new LogManager(context);
pipelines.AfterRequest.AddItemToEndOfPipeline(_ => logManager.Log());
try
{
X509Certificate2 clientCert = context.Request.ClientCertificate as X509Certificate2;
container.Resolve<ICertificateValidator>().Validate(clientCert);
}
catch (Exception ex)
{
throw new MklServerAuthenticationException(ErrorCodes.WrongOrNonexistingCertificate, ex);
}
}
Figured out a way to solve the above problem and thought somebody else might like to know. Replace the line in my code above, containing the GetIdentity() call, with the following:
Identity identity = null;
try
{
identity = container.Resolve<IAuthenticationController>().GetIdentity(requestInfo);
}
catch (Exception ex)
{
var exception = new MklAuthentcationException(ErrorCodes.TokenInvalid, ex);
context.Response = container.Resolve<IErrorHandler>().HandleException(context, exception);
pipelines.BeforeRequest.Invoke(context, CancellationToken.None);
}
I'm using the fact stated in nancy that:
The PreRequest hook is called prior to processing a request. If a hook returns a non-null response then processing is aborted and the response provided is returned.
So by setting a response (my error in this case) on the PreRequest hook and invoking it my error is returned and execution is stopped.
Maybe not the nicest solution... If you can figure out something better please let me know.
Api Call
http://localhost:8888/api/v1/users/100 //doesn't exist
Html Call
http://localhost:8888/admin/users/100 //doesn't exist
Obviously, I don't want the Html Call exception to return json data and I don't want the Api Call to return Html Data.
I am not exception handling in the controller. I am exception handling in my UserRepository. As such, my controllers are just returning a result from the user repository.
class Sentry2UserRepository implements UserInterface {
public function findById($id) {
try {
return Sentry::findUserById($id);
}
catch (\Cartalyst\Sentry\Users\UserNotFoundException $e) {
// Do something here
return false;
}
}
}
Question 1: What is the normal / proper way of passing an error back to controller so that it will know what to display?
Question 2: Is there a standard json API format for exceptions / errors?
Question 3: Is it good practice for a Web UI to consume an internal JsonApi? Or am I doing things the right way at the moment with my WebUi controllers Querying the same Repositories as the Api?
Try this magic in your filters.php:
App::error(function(Exception $exception, $httpCode)
{
if (Request::is('api/*')){
return Response::json( ['code' => $exception->getCode(), 'error' => $exception->getMessage()], $httpCode );
}else{
$layout = View::make('layouts.main');
$layout->content = View::make('errors.error')->with('code', $exception->getCode())->with('error', $exception->getMessage())->with('httpCode',$httpCode);
return Response::make($layout, $httpCode);
}
});
First of all, I think your approach in Sentry2UserRepository is not bad, it's ok, IMO.
Question 1: What is the normal / proper way of passing an error back
to controller so that it will know what to display?
Well, IMO, depending on the application you should determine how you should handle exceptions. You mentioned so that it will know what to display and in this case it depends on how and what information you need from an exception to take the next action after an exception occured. now if you need the error message then you may return the return $e->getMessage() so you'll exactly know what actually happened. There are many ways to do this, for example, using a single catch :
try{
// ...
}
catch( Exception $e )
{
if ($e instanceof UserNotFoundException) {
// it's an instance of UserNotFoundException, return accordingly
}
elseif ($e instanceof SomethinElseException) {
// it's an instance of SomethinElseException, return accordingly
}
}
Also, you can use different custom exception classes and may use multiple catch blocks, i.e.
class AnException extends Exception
{
public function customErrorMessage()
{
return `AnException occurred!`
}
}
class AnotherException extends Exception
{
public function customErrorMessage()
{
return `AnotherException occurred!`
}
}
Then catch using multiple catch blocks, i.e.
try
{
// ...
}
catch(AnException $e)
{
return $e->customErrorMessage();
}
catch(AnotherException $e)
{
return $e->customErrorMessage();
}
catch(Exception $e)
{
return $e->getMessage();
}
Question 2: Is there a standard json API format for exceptions / errors?
Question 3: Is it good practice for a Web UI to consume an internal JsonApi? Or am I doing things the right way at the moment with my WebUi controllers Querying the same Repositories as the Api?
Actually I don't know about such an api and you are doing right, IMO. It's because, you have this
class Sentry2UserRepository implements UserInterface {
public function findById($id) {
try {
return Sentry::findUserById($id);
}
catch (\Cartalyst\Sentry\Users\UserNotFoundException $e) {
// Do something here
return false;
}
}
}
So, it's possible to write code in controller something like this
if(findById(5)) {
// found and dump it to the view
}
else {
// show "Not Found !", false will be back only for UserNotFoundException
}
But, if you had this in your UserNotFoundException catch
return $e; // or anything else (maybe an array containing status and message)
Then It's not possible to write this simple code
if(findById(5)) {
// found and dump it to the view
}
Because, is statement will be true for $e object oe for an array, so you have to check it again, using somrthing like this
$result = findById(5);
if($result && $result->code && $result->code === 0) {
// something according to code
}
Or maybe, something like this
if($result && $result->code) {
// error happened, now determine the code
switch($result->code){
case 0:
// show message
break;
case 1:
// show message
break;
}
}
So, IMO, why, you need to show the user, what error happened in your application, why not just to states, either you got data or you didn't get it. isn't it simple ? Just KISS. This is my opinion only, that's it.
So I have a UI thread. Person clicks something because they feel like it. So click triggers some function calls. One of the underlying function calls uses CDROM driver that reads dirty discs by trying a couple of times and making that crazy thumping.
So I want a responsive UI and i put await on my function call. So when person clicks, function relinquishes control to UI thread. Function tries to read the CDROM, but it is really dirty so it throws an exception to its caller. That caller counts the number of retries and keeps trying three times.
So, if this is all await, where do I keep the count?
If I keep the count in a lower level and that level relinquishes with await, it can't keep retrying until three attempts because IT IS RELINQUISHED.
But if I don't relinquish, I can't maintain a responsive UI.
Do I keep the count in the Task object? And exactly which thread/await level can be responsible for checking the retry count?
You can put your retry logic wherever is most appropriate. await works perfectly well with try:
public async Task PerformOperationAsync(int retries)
{
while (retries != 0)
{
try
{
await PerformSingleOperationAsync();
return;
}
catch (Exception ex)
{
Log(ex);
--retries;
}
}
}
The code above will ignore failures if it runs out of retries. You can also throw the last error:
public async Task PerformOperationAsync(int retries)
{
while (true)
{
try
{
await PerformSingleOperationAsync();
return;
}
catch (Exception ex)
{
Log(ex);
if (--retries == 0)
throw;
}
}
}
Throwing the first error or a collection of all the errors is left as an exercise for the reader. ;)