Blazor组件在Login控制器返回401状态码时抛出未处理异常问题求助
这个问题的核心在于HttpClient的默认行为:当收到非2xx的HTTP状态码(比如401 Unauthorized)时,它会自动抛出HttpRequestException。你的自定义PostNewtonsoftJsonAsync方法应该是内部调用了EnsureSuccessStatusCode()(或者依赖了HttpClient的默认异常抛出逻辑),导致401响应直接触发异常,而你的AuthService和LoginComponent都没有捕获这个异常,最终让Blazor的渲染器捡到了这个未处理异常,输出了你看到的日志。
简单说:你预期401是业务错误,可以通过返回的Result对象处理,但HttpClient把它当成了网络/请求异常直接抛出来了,打断了你的正常业务流程。
最优雅的处理方式是修改你的自定义PostNewtonsoftJsonAsync方法,禁用自动抛出非2xx状态码的异常,手动处理响应内容和状态码。这样401这类业务级别的错误就能被你的代码正常处理,而不会触发未处理异常。
步骤1:修改自定义Post方法
把原来的PostNewtonsoftJsonAsync改成不调用EnsureSuccessStatusCode(),而是直接读取响应内容并反序列化,同时返回状态码方便后续处理:
public static async Task<(HttpStatusCode StatusCode, T Data)> PostNewtonsoftJsonAsync<T>(this HttpClient client, string url, object content) { // 序列化请求内容 var json = JsonConvert.SerializeObject(content); var httpContent = new StringContent(json, Encoding.UTF8, "application/json"); // 发送请求,不自动抛出异常 var response = await client.PostAsync(url, httpContent); // 读取并反序列化响应内容 var responseJson = await response.Content.ReadAsStringAsync(); var data = JsonConvert.DeserializeObject<T>(responseJson); // 返回状态码和反序列化后的数据 return (response.StatusCode, data); }
步骤2:更新AuthService的Login方法
现在你可以在Login方法里同时处理状态码和业务结果,确保流程不会被异常打断:
public async Task<Result> Login(LoginUserCommand command) { var (statusCode, response) = await _httpClient.PostNewtonsoftJsonAsync<(Result Result, string Token)>("api/login", command); // 不管状态码是200还是401,先处理业务结果 if (!response.Result.Succeeded) { return response.Result; } // 成功时处理令牌 if (!string.IsNullOrEmpty(response.Token)) { await SetAuthToken(command.Email, response.Token); } return response.Result; }
备选方案:捕获异常兜底
如果你暂时不想修改自定义Post方法,也可以在AuthService的Login方法里捕获HttpRequestException,并处理401的情况:
public async Task<Result> Login(LoginUserCommand command) { try { var response = await _httpClient.PostNewtonsoftJsonAsync<(Result Result, string Token)>("api/login", command); if (!response.Result.Succeeded) return response.Result; if (!string.IsNullOrEmpty(response.Token)) await SetAuthToken(command.Email, response.Token); return response.Result; } catch (HttpRequestException ex) when (ex.StatusCode == HttpStatusCode.Unauthorized) { // 读取401响应中的错误信息并返回 var errorResponse = await ex.Response.Content.ReadAsStringAsync(); var errorResult = JsonConvert.DeserializeObject<Result>(errorResponse); return errorResult ?? new Result { Succeeded = false, Errors = new List<string> { "登录失败,身份验证未通过" } }; } }
不过这个方案不如第一种优雅,因为你需要额外处理异常的响应内容,而且代码逻辑会被try-catch分散。
修改完成后,当控制器返回401时,你的代码会正常反序列化返回的Result对象,然后把错误信息传递给LoginComponent,显示错误提示,而不会触发Blazor的未处理异常。
内容的提问来源于stack exchange,提问作者ScottKane

