dotnet/aspnetcore

Global exception handling

julienGrd opened this issue Β· 60 comments

Hello guys, I wanted to know what is the better way to catch exception at application level, to avoid put try catch in all my method and component, and centralize the exception management in one point (typically show an error notification and log the exception).

so there is a better way to do this in preview8, or something planned for the RTM ?

thanks !

All unhandled exceptions should end up in the Logger. If you're seeing otherwise, please let us know and we'd be happy to investigate. Looking at the logs should be the way to go here.

OK, what i understand is i have to provide my own implementation of ILogger ? and its on my implementation i have to deal with the exception ? (for example show notification, write exception in a file for server side and send it to controller for client-side)
actually the only thing if found is this package https://github.com/BlazorExtensions/Logging
I don't really understand how to use it, it seem we can only log in the console with this.

But even with this extension, if there is an unhandled exception in the code, the app crash.

so if i understand, i don't have the choice to surround all of my code with try catch ?

thanks for your explanation

Any of the logger providers listed here: https://docs.microsoft.com/en-us/aspnet/core/fundamentals/logging/?view=aspnetcore-2.2#built-in-logging-providers as also community developed providers (https://github.com/aspnet/Extensions/tree/master/src/Logging#providers) should work.

But even with this extension, if there is an unhandled exception in the code, the app crash.

For server-side Blazor, an unhandled exception should disconnect the circuit, but not crash the application. If a block is meant to throw an exception you can tolerate, a try-catch seems like the most reasonable way to proceed. What do you plan on doing with unhandled exceptions if you do catch arbitrary exception?

Client side blazor does not log any exceptions. They are just printed to console. Here is the code from WebAssemblyRenderer

protected override void HandleException(Exception exception)
{
    Console.Error.WriteLine($"Unhandled exception rendering component:");
    if (exception is AggregateException aggregateException)
    {
        foreach (var innerException in aggregateException.Flatten().InnerExceptions)
        {
            Console.Error.WriteLine(innerException);
        }
    }
    else
    {
        Console.Error.WriteLine(exception);
    }
}

Only way I found that can intercept exceptions is described here
https://remibou.github.io/Exception-handling-in-Blazor/

But I would not call this exception handling...

I would like to have a global exception handler so I could write my code without catch blocks...

try
{
   IsLoading = true;
   await SomeAsync();
}
finally
{
   IsLoading = false;
   //force rerender after exception
    StateHasChanged();
}

If SomeAsync method throws an exeption my IsLoading varialbe is set to false but my view is not rerendered. You have to call SetStateHasChanged in finally block to force view rerender

for client side blazor, i finally write my own implementation on ILogger (inspired by the code of default implementation which log in the console) .
With this implementation, i can call a LoggerController on my server, wich a ILogger is inject too (but this time, its a NLog implementation whish is inject, so i can properly write all exception on the server)

I abandon the idea of global exception handling, which seem not be the logic of blazor, and put try/catch everywhere and inject an ILogger in all component.

Except if you think there is better to do, you can close this issue.

I think this is a missing feature.. When exception is thrown I would also like to have a centralized place to capture all unhandled exceptions and do something.. Either display it in a popup, log it somewhere, send it to the server or whatever.. without any harm to app state
Something like UnhandledException event in WPF
https://docs.microsoft.com/en-us/dotnet/api/system.appdomain.unhandledexception?redirectedfrom=MSDN&view=netframework-4.8

Isn't the ILogger already that centralized place?

@SteveSandersonMS look at my comment above
#13452 (comment)
Exceptions (in client side blazor) are written to console using Console.Error.WriteLine, they are not logged using ILogger.. You could implement a custom TextWriter and replace Console.Error
https://remibou.github.io/Exception-handling-in-Blazor/
but you get a string representation of an exception.. Parsing that string, which inludes a stack trace, and trying to get (for example) only an exception message is cumbersome to say at least.
I would like to get an access to exception object so I can do my own logging (or what ever)

@rborosak Thanks for the clarification!

Any update on this issue ?

Currently my sever side blazor app just stopped responding to user input after an exception is thrown.

Its not a good practice to cover all lines of code with try-catch (nor to expect the developer to do so) , in case of "un-predicted" exception we should have a generic handler that allows us :

1.Choose whether to break the circuit.
2.Show our user a "friendly" GUI message or just redirect him to generic error page.

Currently after un-handleded exception the app can not notify the user on a problem and the user can only re-run the app (close the browser and goto to the app URL again) or refresh the page and the loose all the state (which is sometimes good , but the app developer should choose this).

@hagaygo Very good summary!

@SteveSandersonMS Please consider this for the 3.1.x milestone ! Please do not move this to 5.0.0

Hi,

I get this kind of error when the circuit breaks in the Console of Developer Window .

image

I dont think there anyway presently that can notify the user that an error has occurred after this in the screen and ask to refresh it without manually opening the developer window to check and refreshing the page

@SteveSandersonMS Could you clarify wheter or not the app.UseExceptionHandler() works for server-side blazor?

I have seen several cases where my custom ErrorHandler does not catch exceptions being thrown by my application. Example code

Startup.cs:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IServiceProvider serviceProvider)
{
    ...
    app.UseExceptionHandler(new ExceptionHandlerOptions { ExceptionHandler = ErrorHandler.HandleError });
    ...
}

ErrorHandler.cs:

public static async Task HandleError(HttpContext context)
{
    var error = context.Features.Get<IExceptionHandlerFeature>()?.Error;
    var message = error?.Message ?? "[EXCEPTION NOT FOUND]";        
    return;
}

An example are when my repository are throwing an exception as such:
The instance of entity type cannot be tracked because another instance with the same key value for {'Id'} is already being tracked

My MVC solution are catching all exceptions and it is using similar ErrorHandling implementations.

Moving this to 5.0 release to distill the specific asks here and see whether we can address some of these.

the biggest issue for me is error boundaries. i want to be able to use a third party component and, somehow, wrap my use of that component in error handling so that a bug in the library doesn’t kill my whole web application. right now there’s no β€œplace to put the try catch” - the whole component tree is supervised by the blazor renderer, which will die if anything deep in the tree messes up.

+1 for this feature.
Having to put try/catch block in all methods isn't a reasonible way to go.

I personnaly used to throw exception to manage business errors. At a high level, I catch the exception and according to its type, I decide if the exception must be logged, displayed to the user and if displayed if I display the complete message (business exceptions) or just a "Something goes wrong").

Actually, the only way I found to do something like this is to create a ComponentBaseEx class which inherit to ComponentBase and override OnInitialized(Async), OnParametersSet(Async), ...
Unfortunately, even with this way to go, I have to manage manually all user's actions (delete a customer, ...)

Do you think

  1. something can be done ?
  2. if yes, when ?

Here's my scenario: Blazor desktop/hybrid app using Blazor server. One user logged in at a time and state persists across reloads. So, if there's an unhandled exception, there's no way to get the app back to a trustworthy state other than restarting it. We need to catch unhandled exceptions, show appropriate UX, and then shut down. Standard desktop-app pattern.

Temporary hack: we have logging set up with Serilog. Serilog has an API that lets you filter all log messages and decide whether they are emitted. We abuse this API by checking whether a log event has a CircuitId key in its properties-dictionary. If it does, that means a circuit has died - ie., there's an unhandled exception. We can get the offending exception out of the log message object that we examined. Presto - you have your hands on the unhandled exception at the very top and can do whatever you want with it.

Hello guys, like the others, i'm realy worried if you move this to the .NET 5 milestone.
After one year of work on blazor by rewriting an old silverlight app, i'm suppose to deploy my app in production in may/june (when webasssembly will be production ready).

But i definitively can't do that without a strong way to catch or at least log all unexpected errors.

Please consider make something, even not perfect, before the 5.0 release (juste a method with the exception is OK for me, at least we can log...)

Thans you very much !

Hello, I'm in the same situation that @julienGrd
Please do something to help/guide us.
Regards!

The intention is that you should be able to log all uncaught exceptions using the ILogger interface. This means you can plug in your own custom logging system that does whatever you want with the exception info.

However there's still a piece of this we haven't yet implemented. This is being tracked by #19533. We'll be sure to get this done soon.

you should be able to log all uncaught exceptions using the ILogger interface

@SteveSandersonMS I think what we're after here is a much more general API, that just hands you an uncaught exception to do whatever we want with the exception itself. Imagine we want to examine the exception and possibly react somehow, maybe send the exception to an exception-tracking service like Raygun, or present some UI based on the contents. Without such an API, we need to misuse the ILogger interface to accomplish all these things, which is awkward. Why not just provide that much more general API, instead of forcing all possible scenarios through an ILogger implementation?

The intention is that you should be able to log all uncaught exceptions using the ILogger interface. This means you can plug in your own custom logging system that does whatever you want with the exception info.

@SteveSandersonMS By any chance do you have a reference example to do this in a Server Side Blazor app? My intention is after log an uncaught exception, show a friendly message to the user. Help will be appreciated. Regards.

@eliudgerardo Blazor Server already displays some user-facing UI when there's an unhandled error. You can customize this UI. Docs about it are at https://docs.microsoft.com/en-us/aspnet/core/blazor/handle-errors?view=aspnetcore-3.1#detailed-errors-during-development

@SteveSandersonMS I'm aware of that option you mention, but I'm afraid that in that case the connection Circuit is broken, and I don't want to wrap all my APIs calls with try/catch. So is there a way to accomplish that?

@eliudgerardo Why does it matter that the circuit is broken? Is your goal to somehow let the user continue using the circuit even after the unhandled exception?

@SteveSandersonMS We don't want the circuit to break!. If an exception occurs (in our app or somewhere in the third party library that we are using) we would like a place where we could display a dialog saying "Something went wrong {exception.Message}" and the app would continue to work. We have a partial solution to this, wrap lifecycle methods and our methods into try catch block but that is cumbersome and there is a problem with third party libraries...

@partyelite If an exception is unhandled, then that circuit is now in an unknown state. We can no longer say whether any resources inside it are still usable, whether your security logic has been executed fully, or anything else. If the circuit continued to be used, that could create undefined behavior including potential security issues, which are problematic given that this code is running on your server.

One possibility we are considering for the future is having an optional "per component unhandled exception" mechanism. If this mechanism was enabled, then the framework would do its own try/catch logic around all calls into the component's lifecycle methods, event handlers, and rendering logic. If the framework caught an exception at that level, it would consider that individual component dead (and perhaps replace it in the UI with some sort of "error" component) but would not affect execution of the rest of the circuit.

Do you think that kind of approach would meet your needs?

I certainly like the idea of having a built-in component boundary for errors.

That would be excellent. But I think we still need a method (that has an exception that occurred as an input parameter) which we could override and do something with that exception - like display a dialog with exception details or send exception details to server where it would be logged to DB or something..
I know that something similar can now be done using ILogger but we get a string as an input parameter and getting some specific data from the exception in a string format is really hard.

per-component unhandled-exception traps would be very good. ideally this would be something which can be specified externally and/or in certain components, so that we can segregate portions of the ui or functionality into error boundaries. the main necessity is to be able to do something with semantics like this:

<MyApp>
    <HandleErrors>
        <ThirdPartyComponent />
    </HandleErrors>
    <HandleErrors>
        <SomeOtherComponent />
    </HandleErrors>
</MyApp>

if either of the components fails to render, we need the rest of the application to be to continue on. it's equivalent to a rich-data desktop application in which you present some sidebar or execute some business logic function, which fails, but the app does not crash and throw away data in form fields.

in something like WPF, the use of strict view/model separation mitigates this concern; best practice is to use MVVM or MVC or whatever and have error handling around the logic that's triggered by or that updates the view. however, blazor idiomatic views are too imperative and error-prone for that to be reasonable. it's difficult to write a .xaml file which will bring down the application, but in .razor it's trivial to have an unhandled exception during rendering, whether from a data binding to an ORM-backed property or simply because you wrote

@if (mightBeNull.Value) {
    <br>
}

Isn't the ILogger already that centralized place?

Like @julienGrd I need a way to globally catch MsalUiRequiredException so I can redirect the user to a signin to request additional scopes. In MVC there is an action exception filter that handles this but Blazor has no equivalent.

Maybe ILogger is the place I am supposed to do this somehow? If so, it feels bad. My exception is an auth-related one, so it is a cross-cutting concern and I need a way to deal with it that doesn't pollute my entire application.

@kenchilada I know it feels like adding a "global" exception handler would be a quick solution to your problem, but any time you use that approach, your code completely loses track of the flow that was underway. You have no way to preserve state, and no way to provide a UX that doesn't just throw away whatever was going on at the time. In general it's much better to put a specific try/catch within the UI that might trigger it so that you can proceed without discarding all the local state.

However if you really do want to handle this at a "global" level then yes, the ILogger is a way you can do that.

The problem is that try-catch based handling is not possible with the RenderTreeBuilder model. You can write

@try
{
    <p>@{throw new Exception();}</p>
}
catch (Exception e)
{
    <p>An error occurred.</p>
}

..but it won't display "An error occurred.", it will bring down the whole circuit. You don't even get the console log and #blazor-error-ui.

@SteveSandersonMS Im totally agree with you in "theory" but im in a specific context where i don't know how to handle it, maybe you will have some answers.

I synchronise lot of code from an existing silverlight app, which i make the technical migration to blazor, but keep compatibility and share code between the two apps because the amount of work is huge and the two apps need to works side by side during a while.

so my problem is the ViewModel synchronised on the client side call wcf services like this :

public void MyFunction(){
wcfClient.MyFunctionCompleted += MyFunctionCompleted;
wcfClient.MyFunction();
}

private void MyFunctionCompleted(object sender, MyFunctionCompletedArgs args){
//result is here, and potential exception too
//we have to consider this code not safe so have a way to catch it
}

so when i call the method of my view model in my blazor view, it's useless to doing something like this

private void ButtonClick(){
try{
myViewModel.MyFunction();
}
catch(Exception ex){
//i can't catch the exception in the service or happened in the callback
}
}

So i don't know how to handle exception return by the service, or exception happened directly in the callback, i lost the hand.

Thanks for your help !

in that case, you could actually use a TaskCompletionSource. let's say that MyFunctionCompletedArgs contains either an exception or a value of type TResult, it'd be something like this:

public Task MyFunction() 
{
    var tcs = new TaskCompletionSource<TResult>();
    wcfClient.MyFunctionCompleted += MyFunctionCompleted(tcs);
    wcfClient.MyFunction();
    return tcs.Task;
}

private EventHandler<TResult> MyFunctionCompleted(TaskCompletionSource<TResult> tcs) => (object sender, MyFunctionCompletedArgs args) =>
{
    if (args.Error != null)
    {
        tcs.SetException(args.Error);
    }
    else
    {
        tcs.SetResult(args.ReturnValue);
    }
};

then on the Blazor side, wait for the promise to be completed:

private async Task ButtonClick()
{
    try
    {
        await myViewModel.MyFunction();
    }
    catch (Exception ex)
    {
    }
}

There are many programming/software scenarios.

My conclusion from this discussion is that Microsoft (At least currently) does not want us to use Blazor for "big" apps or LOB apps since they expect us to put try/catch blocks every where.

You can maybe build a "small" apps in this way. (also , as i mentioned , its not a good practice)

But LOB apps with many use cases and 10-20 (and even more) developers in a team can not work like that.

There are of course more issues with Blazor at the moment, but this issue came up about 10-15 minutes after starting to build a POC.

There are many programming/software scenarios.

My conclusion from this discussion is that Microsoft (At least currently) does not want us to use Blazor for "big" apps or LOB apps since they expect us to put try/catch blocks every where.

You can maybe build a "small" apps in this way. (also , as i mentioned , its not a good practice)

But LOB apps with many use cases and 10-20 (and even more) developers in a team can not work like that.

There are of course more issues with Blazor at the moment, but this issue came up about 10-15 minutes after starting to build a POC.

This is where we landed as well. We are using it for a backend admin interface but it is much too novel at the moment to be used for our customer-facing application.

@gulbanana thanks for the tip ! Not sure i will keep compatibility with the silverlight app with this kind of code, but i have to try (even if i prefere a global exception handling solution)

in that case, you could actually use a TaskCompletionSource. let's say that MyFunctionCompletedArgs contains either an exception or a value of type TResult, it'd be something like this:

public Task MyFunction() 
{
    var tcs = new TaskCompletionSource<TResult>();
    wcfClient.MyFunctionCompleted += MyFunctionCompleted(tcs);
    wcfClient.MyFunction();
    return tcs.Task;
}

private EventHandler<TResult> MyFunctionCompleted(TaskCompletionSource<TResult> tcs) => (object sender, MyFunctionCompletedArgs args) =>
{
    if (args.Error != null)
    {
        tcs.SetException(args.Error);
    }
    else
    {
        tcs.SetResult(args.ReturnValue);
    }
};

then on the Blazor side, wait for the promise to be completed:

private async Task ButtonClick()
{
    try
    {
        await myViewModel.MyFunction();
    }
    catch (Exception ex)
    {
    }
}
rono1 commented

A bit confused here... If global exception handling isn't doable, then why is there a product out there
for blazor that claims that "All uncaught exceptions are automatically logged"?:
https://docs.elmah.io/logging-to-elmah-io-from-blazor/

@rono1 You can now hook into the logging pipeline and log exceptions -- that doesn't mean you've "handled" the exception, though, just that you can globally log them. I'm thankful for the logging, though!

We've moved this issue to the Backlog milestone. This means that it is not going to be worked on for the coming release. We will reassess the backlog following the current release and consider this item at that time. To learn more about our issue management process and to have better expectation regarding different types of issues you can read our Triage Process.

One possibility we are considering for the future is having an optional "per component unhandled exception" mechanism. If this mechanism was enabled, then the framework would do its own try/catch logic around all calls into the component's lifecycle methods, event handlers, and rendering logic. If the framework caught an exception at that level, it would consider that individual component dead (and perhaps replace it in the UI with some sort of "error" component) but would not affect execution of the rest of the circuit.

Do you think that kind of approach would meet your needs?

@SteveSandersonMS Is this being considered for implementation? It will result in a better user experience and more robust software.

+1 on this. We're really supposed to wrap every single event in a try/catch? I just went down a rabbit hole of trying to work around this with some base components to manage state because of how painful this is.

+1 also on this. The try/catch approach really isn't an option for larger solutions with multiple developers.

I adressed this in my client side blazor app by using a custom logging provider:
https://github.com/markusrt/NRZMyk/blob/master/NRZMyk.Client/ReloadOnCriticalErrorLogProvider.cs

It seems a bit weird to use a logger as global exception handler but at I did not find any cleaner approach as of now 😞

Thanks for contacting us.
We're moving this issue to the Next sprint planning milestone for future evaluation / consideration. We will evaluate the request when we are planning the work for the next milestone. To learn more about what to expect next and how this issue will be handled you can read more about our triage process here.

For anyone looking for a client-side workaround:

function reportError(error) {
  if (DotNet) {
    DotNet.invokeMethodAsync('MyApp.Client', 'NotifyError', error);
  }
}

var exLog = console.error;
console.error = function (msg) {
  exLog.apply(console, arguments);
  reportError(msg);
}

window.addEventListener("unhandledrejection", function (promiseRejectionEvent) {
  reportError(promiseRejectionEvent.reason.message);
});
  public partial class AppLayout
  {
    [JSInvokable("NotifyError")]
    public static void NotifyError(string error)
    {
      //Handle errors
    }
  }

Browser support

This is a critically important feature for a production application and I wouldn't feel comfortable releasing an application where I couldn't control the application's behavior in the event of an unhandled error, or at least be able to reliably log it. Fortunately, I'm sure there's enough support for this in JavaScript we can leverage, however it's a big enough gap to make me question whether this technology's really mature enough to be adopted in serious applications.

I hope this a global error handler is prioritized in future releases.

Lixan commented

What a pity that a major issue like this one, opened in Aug. 2019, is still delayed
Did most of people give up and surrounded all of their methods with try/catch ?
It's ironic to go on a recent techno if it's to write ugly code

@Lixan For sure. If an essential issue like this isn’t take into account, imagine others...
Would prefer that Microsoft focuses on process these kind of issues that add new features.

It’s already a big deal to choose Blazor as web framework because of its youth but if Microsoft doesn’t listen to developpers !!!

Perhaps it is not technically feasible to fix this issue as we need without some kind of AOP or attribute + compiler feature.

It would be an improvement if components could implement an optional interface which exposes a handle error method. Then components would invoke this method on unhandled error.

@mkArtakMSFT, is at least some solution planned for real for this issue?

I am looking for some global error handling solution to both my Blazor server and WASM projects. Is there any forecast date for when .Net 6 will be out?

Yes, the .NET ship dates are listed at https://github.com/dotnet/core/blob/master/roadmap.md

Please note that you already can catch errors and log them globally. That's what the ILogger interface is for. The work we're planning for .NET 6 is somewhat more specific and described at #26953

Since #26953 reflects the work we plan to do, I'm closing this issue now. If anyone is concerned that #26953 (and the existing ILogger mechanism) doesn't meet their needs, could you please file a new issue and provide specific details about what different thing you are looking for besides #26953 and ILogger? Thanks!

@SteveSandersonMS you are pointing to ILogger implementation. This works for WebAssembly. We've already done it for the ABP Framework (see). We are showing a messagebox to the user.

However, for Blazor Serverside, it doesn't help much because the SignalR connection is broken in anway. I investigated the source code (of aspnetcore), but there is no way to prevent this. CircuitRegistry has no extension point. For ASP.NET Core team, it is pretty easy to provide an extension point for it. So, the end developer checks the exception type/message and decide to break the circuit or just show a message and keep the application continue to work.

So, the end developer checks the exception type/message and decide to break the circuit or just show a message and keep the application continue to work.

I'm afraid that's not a valid solution. Continuing after an arbitrary unhandled exception compromises the server's stability and may open security vulnerabilities. If there's an unhandled exception, the only safe action is to terminate the circuit, since there's no way to know what state things within that circuit have been left in. We already send a notification about the error to JS code running in the browser, so it's possible to display some custom UI at that point (which the project template already does by default). I know this makes things seem less convenient, but helping customers deliver secure applications is a very high priority for us.

@SteveSandersonMS Would the following suggestion work?

ComponentBase is modified to include a virtual method which would be invoked when BuildRenderTree method encounters a problem. This method would provide alternative render. If it too fails, break the circuit.

This may work if RenderTreeBuilder is not immediately committing contents between OpenComponent and CloseComponent statements?

@akovac35 That's interesting. We could do that, but I think the plan we already have in #26953 gives you the same benefits but even better still, since you don't have to repeat your error handling in every single component, but rather can plug in an error renderer at any set of points in your component hierarchy. Errors caught within an error boundary will not be fatal to the circuit, since we know exactly what state is affected and the framework can clear it up so the app can safely continue.