Blazor文档阅读 》 Fundamentals 。

ASP.NET Core Blazor logging

WASM应用可以自定义录记功能,只需设置WebAssemblyHostBuilder.Logging辖属。

在Program.cs中添加下列使用声明:

using Microsoft.AspNetCore.Components.WebAssembly.Hosting;

通过LoggingBuilderExtensions.SetMinimumLevel可以自定义录记级别:

var builder = WebAssemblyHostBuilder.CreateDefault(args);
...
builder.Logging.SetMinimumLevel(LogLevel.Debug);
builder.Logging.AddProvider(new CustomLoggingProvider());

Logging辖属的类型为ILoggingBuilder,ILoggingBuilder中的方法也在Loggin中存在。

Hosted Blazor WebAssembly logging

一个宿寄的WASM应用如果采用预渲染,可以执行初始化代码两次。录记第一次发生在服务端执行初始化代码的时候,第二次发生在客户端初始化代码的时候。

SignalR .NET client logging

Log in Razor components

@using Microsoft.Extensions.Logging;用来支持IntelliSense,这样LogWarning以及LogError等可以自动补全。下面是组件中使用ILogger的一个示例:

@page "/counter"
@using Microsoft.Extensions.Logging;
@inject ILogger<Counter> logger;

<h1>Counter</h1>

<p>Current count: @currentCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

@code {
    private int currentCount = 0;

    private void IncrementCount()
    {
        logger.LogWarning("Someone has clicked me!");

        currentCount++;
    }
}

下面额例子展示组件中对ILoggerFactory的使用:

@page "/counter"
@using Microsoft.Extensions.Logging;
@inject ILoggerFactory LoggerFactory

<h1>Counter</h1>

<p>Current count: @currentCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

@code {
    private int currentCount = 0;

    private void IncrementCount()
    {
        var logger = LoggerFactory.CreateLogger<Counter>();
        logger.LogWarning("Someone has clicked me!");

        currentCount++;
    }
}

参考https://docs.microsoft.com/en-us/aspnet/core/fundamentals/logging/?view=aspnetcore-5.0

Handle errors in ASP.NET Core Blazor apps

Detailed errors during development

当错误发生时,Blazor应用会在底部区域显示一个淡黄色的错误条:

  • 开发环境中,错误条会指向浏览器console,那里有打印出来的异常信息
  • 生产环境中,错误条会提示刷新浏览器

UI由https://docs.microsoft.com/en-us/aspnet/core/blazor/project-structure?view=aspnetcore-5.0提供。

WASM应用可在wwwroot/index.html调整:

<div id="blazor-error-ui">
    An unhandled error has occurred.
    <a href="" class="reload">Reload</a>
    <a class="dismiss">🗙</a>
</div>

blazor-error-ui的CSS类定义在wwwroot/css/app.css

Manage unhandled exceptions in developer code

在生成环境中,不要将错误信息显示在UI上面,这么做有以下问题:

  • 泄露敏感信息给终端用户
  • 帮助恶意用户探索应用的弱点,从而威胁到安全性

Global exception handling

应用可以将一个专门的错误处理组件设置成cascading value来以统一的方式处理错误。

下面是错误处理组件的示例,它将自己作为一个cascading value传递给childcontent:

@using Microsoft.Extensions.Logging
@inject ILogger<Error> Logger

<CascadingValue Value=this>
    @ChildContent
</CascadingValue>

@code {
    [Parameter]
    public RenderFragment ChildContent { get; set; }

    public void ProcessError(Exception ex)
    {
        Logger.LogError("Error:ProcessError - Type: {Type} Message: {Message}", 
            ex.GetType(), ex.Message);
    }
}

使用组件而不是注入的服务、自定义的录记器是因为组件可以显示内容,并且应用CSS样式。

在App组件内,将Router包裹在Error组件之内,这样允许Router组件以CascadingParameter的方式接收Error组件:

<Error>
    <Router ...>
        ...
    </Router>
</Error>

如何在其他组件内使用Error组件呢?

首先定义一个Error类型的CascadingParameter:

[CascadingParameter]
public Error Error { get; set; }

然后这样:

try
{
    ...
}
catch (Exception ex)
{
    Error.ProcessError(ex);
}

若ProcessError需要渲染UI,比如输出错误信息,那么需要在ProcessErrors结束前调用StateHasChanged

Log errors with a persistent provider

如果发生了未处理的异常,那么异常会被录记到当前服务容器配置的ILogger。默认情况下,Blazor应用录记到控制台输出。可以考虑将异常输出到一个更恒久的位置,比如一个后端web api所提供的录记服务。后端web api可以选择将录记内容交给Application Performance Management(APM)。

开发者必须决定录记的时机,以及内容的严重度。要访问录记功能被恶意滥用,比如不要录记因URL提供的参数(如productid)是未知的。

更多参考:

原生的https://docs.microsoft.com/en-us/azure/azure-monitor/app/app-insights-overview支持Blazor WebAssembly应用。以后Blazor框架或许会支持原生的Google Analytics。参考https://github.com/microsoft/ApplicationInsights-dotnet/issues/2143以及https://github.com/dotnet/aspnetcore/issues/5461

与此同时,客户端的WASM应用可以使用基于JS的https://docs.microsoft.com/en-us/azure/azure-monitor/app/javascript来将录记内容提交到Application Insights。

Places where errors may occur

Component instantiation

创建组件时会调用组件的构造函数;以@inject或者[inject]注入的DI服务若非singleton,其构造函数也会被调用。这时候出现异常会阻止框架例现化此组件。若要录记此异常,则需使用try-catch语句来拦截此异常。

Lifecycle methods

文中展示了一个OnParametersSetAsync()中出现异常如何处理的例子。

Rendering logic

razor文件中的声明性的标签会被编译进C#的BuildRenderTree方法。组件渲染时,此方法会被调用。此过程也可能出现异常。

比如说对于常见的NullReferenceException,要么使用的时候判断其是否为null,要么给其设一个默认值。

Event handlers

事件处理器可能会抛出异常,如果用户代码不处理这些异常,那么框架会将这些异常录记下来。

Component disposal

实现了System.IDisposable的组件在移除的时候若抛出异常,需要拦截住异常并录记。

JavaScript interop

IJSRuntime.InvokeAsync允许.NET代码在浏览器内异步调用JS的运行库。

下列条件适用于InvokeAsync:

  • 如果InvokeAsync失败且调用是同步的,一个.NET异常会抛出。这在传入参数出错的情况下有可能发生。用户必须自行处理此类异常。
  • 如果InvokeAsync失败且调用是异步的,那么对应的.NET任务便失败了。用户必须自定处理此类异常。如果使用await操作,可以将其围绕在try-catch语句中。
  • 默认情况下,InvokeAsync必须在一定时限内完成,否则就会超时出错。默认时限是一分钟。如果超时,那么System.Threading.Tasks所涉及的异常时OperationCanceledException。用户需要拦截并录记此异常。

同样地,若JS代码调用C#中标为[JSInvokable]的方法也可能出错,此时JS测的Promise会被拒绝。

参考:

Advanced scenarios

组件可以嵌套,但是要避免无限递归:

  • 不要渲染带有循环引用的数据结构
  • 不要创建带有循环引用的布局
  • 不要让用户有机会篡改输入数据从而导致无线循环

开发者可以自行撰写RenderTreeBuilder逻辑,参考https://docs.microsoft.com/en-us/aspnet/core/blazor/advanced-scenarios?view=aspnetcore-5.0#manual-rendertreebuilder-logic。但是要注意:

  • OpenElement和CloseElement的使用要搭对
  • 属性必须添加到正确的位置

不正确的逻辑会导致未定义的行为,以及其他各种问题。

要有心理准备,手写RenderTreeBuilder可能跟用https://docs.microsoft.com/en-us/dotnet/standard/managed-code手写.NET代码一样复杂。

ASP.NET Core Blazor SignalR guidance

只考虑WASM。

如何将SignalR添加到宿寄的WASM应用,参考https://docs.microsoft.com/en-us/aspnet/core/tutorials/signalr-blazor?view=aspnetcore-5.0

SignalR cross-origin negotiation for authentication

关于如何配置客户端,以便让其发送凭证给SignalR服务端。

Render mode

宿寄的WASM应用,首次渲染可以发生在服务端。

ASP.NET Core Blazor static files

略。只适用于Server应用。

(本篇完)