目录

如何在 Maui 中全局处理异常(异步方法)

在“如何在 Maui 中全局处理异常”一文中介绍了通过 Autofac 动态代理,对 ViewModel 进行拦截,全局捕获 ViewModel 中的异常,并使用 Toaster 弹出异常消息提示。然而,Autofac 中的动态代理只支持对同步方法进行拦截。本文将继续介绍如何实现对异步方法的代理拦截。

先决条件

如何在 Maui 中全局处理异常

Castle.Core.AsyncInterceptor

Castle.Core.AsyncInterceptor 是一个简化异步方法拦截的库,通过引入 Castle.Core.AsyncInterceptor ,可以在 Maui 中轻松实现对异步方法的拦截。

更新项目

调整在“如何在 Maui 中全局处理异常”中创建的项目,更改ViewModel中的 RaiseException 方法为异步的 RaiseExceptionAsync 方法,并返回 Task

public virtual Task RaiseExceptionAsync()
{
    throw new Exception("An Exception has been throw!");
}

更改 MainPage.xaml.cs中的 OnCounterClicked 方法,为方法签名加入 async 关键字,将 RaiseException 方法调用改为异步并等待。

private async void OnCounterClicked(object sender, EventArgs e)
{
    if (BindingContext is MainViewModel vm)
    {
        await vm.RaiseExceptionAsync();
    }
}

引入 Castle.Core.AsyncInterceptor

通过 CLI 或 Nuget 包管理器添加对 Castle.Core.AsyncInterceptor 的引用。

实现异步拦截器

Castle.Core.AsyncInterceptor 定义了 IAsyncInterceptor 接口,创建继承 IAsyncInterceptor 接口的类即可实现异步拦截器。

ViewModels/Interceptors 路径下,创建类 ExceptionHandleAsyncInterceptor,继承 IAsyncInterceptor 接口并实现接口中定义的方法。

public class ExceptionHandleAsyncInterceptor : IAsyncInterceptor
{
    public void InterceptSynchronous(IInvocation invocation)
    {
        try
        {
            invocation.Proceed();
        }
        catch (Exception e)
        {
#if ANDROID
            Com.Hjq.Toast.Toaster.Show(e.Message);
#endif
        }
    }

    public void InterceptAsynchronous(IInvocation invocation)
    {
        invocation.ReturnValue = InternalInterceptAsynchronous(invocation);
    }

    private async Task InternalInterceptAsynchronous(IInvocation invocation)
    {

        try
        {
            invocation.Proceed();
            var task = (Task)invocation.ReturnValue;
            await task;
        }
        catch (Exception e)
        {
#if ANDROID
            Com.Hjq.Toast.Toaster.Show(e.Message);
#endif
        }
    }

    public void InterceptAsynchronous<TResult>(IInvocation invocation)
    {
        invocation.ReturnValue = InternalInterceptAsynchronous<TResult>(invocation);
    }

    private async Task<TResult?> InternalInterceptAsynchronous<TResult>(IInvocation invocation)
    {
        try
        {
            invocation.Proceed();
            var task = (Task<TResult?>)invocation.ReturnValue;
            TResult? result = await task;

            return result;
        }
        catch (Exception e)
        {
#if ANDROID
            Com.Hjq.Toast.Toaster.Show(e.Message);
#endif
            return default;
        }
    }
}

IAsyncInterceptor 中定义了 InterceptSynchronousInterceptAsynchronousInterceptAsynchronous<TResult> 三个方法。InterceptSynchronous 用于实现对同步方法的拦截,InterceptAsynchronous 用于实现对返回值为 Task 的异步方法的拦截,InterceptAsynchronous<TResult> 用于实现对返回值为泛型的 Task<TResult> 异步方法的拦截。关于 Castle.Core.AsyncInterceptor 的更多用法,参见其 Github 仓库

注册使用异步拦截器

MainProgram.cs 文件中注册新建的异步拦截器。

containerBuilder.RegisterType<ExceptionHandleAsyncInterceptor>();
技巧
不要将 MainViewModel 服务注册代码中的 InterceptedBy 方法参数更改为 typeof(ExceptionHandleAsyncInterceptor)。 对 MainViewModel 的拦截行为仍将使用基于 IInterceptor 的拦截器。对于异步方法的支持将通过在基于 IInterceptor 的拦截器中来调用异步拦截器来实现。

修改原来同步方法使用的 ExceptionHandleInterceptor 拦截器,将异步拦截器注入其中,并将对方法的拦截操作交给异步拦截器。

public class ExceptionHandleInterceptor : IInterceptor
{
    private readonly ExceptionHandleAsyncInterceptor _asyncInterceptor;

    public ExceptionHandleInterceptor(ExceptionHandleAsyncInterceptor asyncInterceptor)
    {
        _asyncInterceptor = asyncInterceptor;
    }

    public void Intercept(IInvocation invocation)
    {
        _asyncInterceptor.ToInterceptor().Intercept(invocation);
    }
}

效果测试

启动项目并点击页面上的按钮,将会看到异常被捕获并通过 Toaster 展示出来。

../handle-exception-in-maui/exception-handle.gif
异常消息提示

参考内容

源码获取

扫描下方二维码,关注公众号捕获异常,回复 maui 获取源码。

关注微信公众号“捕获异常”,获取最新文章推送,提升你的技能。

捕获异常微信公众号二维码