You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在C# WPF客户端处理Web API认证响应并返回异常模型

解决方案:处理WPF客户端认证的错误响应

问题背景

我基于ASP.NET Web API实现了认证端点,客户端发送含邮箱、密码的HTTP请求后,服务器要么返回带访问令牌的成功响应,要么返回异常码和提示消息(如“邮箱或密码错误”)。目前WPF客户端的认证方法仅能返回成功场景的令牌模型,需要解决用户输入错误密码时如何返回异常模型的问题,同时评估是否用out参数返回令牌、返回值返回异常模型的合理性。

现有代码

服务器端

public AuthentificateResponse Authentificate(AuthentificateRequest model, string ipAddress)
{
    //Check if account exists, is verified and password is correct
    Account account = _context.Accounts.SingleOrDefault(a => a.Email == model.Email);
    if (account == null || account.VerifiedDate == null || _hasher.VerifyHashedPassword(account.PasswordHash, model.Password) == Microsoft.AspNet.Identity.PasswordVerificationResult.Failed)
        throw new RequestExeption("Email or password is incorrect");
    // Generate short-live access token and refresh token
    string jwtToken = _jwtUtils.GenerateJwtToken(account);
    RefreshToken refreshToken = _jwtUtils.GenerateRefreshToken(ipAddress);
    //Add refresh token to account and remove all expired tokens
    account.RefreshTokens.Add(refreshToken);

    removeExpiredTokens(account);
    _context.Accounts.Update(account);
    _context.SaveChanges();

    AuthentificateResponse response = _mapper.Map<AuthentificateResponse>(account);
    response.JwtToken = jwtToken;
    response.RefreshToken = refreshToken.Token;
    return response;
}

客户端

public async Task<AuthentificateResponse> Authentificate(AuthentificateRequest model)
{
    HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, new Uri(_baseUri, "Accounts/authentificate"));
    string requestJson = JsonConvert.SerializeObject(model);
    request.Content = new StringContent(requestJson, Encoding.UTF8, "application/json");
    HttpResponseMessage response = await _httpClient.SendAsync(request);
    string responseJson = await response.Content.ReadAsStringAsync();
    AuthentificateResponse authentificateResponse = JsonConvert.DeserializeObject<AuthentificateResponse>(responseJson);
    //Only for success result
    return authentificateResponse;
}

第一步:修正服务器端的异常响应逻辑

当前服务器直接抛出RequestException会被ASP.NET Web API默认转为500状态码,但认证失败属于客户端错误,应返回401 Unauthorized或400 Bad Request,同时返回结构化错误信息:

[HttpPost("authentificate")]
public IHttpActionResult Authentificate(AuthentificateRequest model)
{
    var ipAddress = Request.GetOwinContext().Request.RemoteIpAddress;
    try
    {
        Account account = _context.Accounts.SingleOrDefault(a => a.Email == model.Email);
        if (account == null || account.VerifiedDate == null || _hasher.VerifyHashedPassword(account.PasswordHash, model.Password) == PasswordVerificationResult.Failed)
            throw new RequestException("Email or password is incorrect");

        // 生成令牌逻辑保持不变
        string jwtToken = _jwtUtils.GenerateJwtToken(account);
        RefreshToken refreshToken = _jwtUtils.GenerateRefreshToken(ipAddress);
        account.RefreshTokens.Add(refreshToken);
        removeExpiredTokens(account);
        _context.Accounts.Update(account);
        _context.SaveChanges();

        AuthentificateResponse response = _mapper.Map<AuthentificateResponse>(account);
        response.JwtToken = jwtToken;
        response.RefreshToken = refreshToken.Token;
        return Ok(response);
    }
    catch (RequestException ex)
    {
        // 返回401状态码和结构化错误
        return Content(HttpStatusCode.Unauthorized, new ErrorResponse { Code = "AuthFailed", Message = ex.Message });
    }
    catch (Exception ex)
    {
        return InternalServerError(ex);
    }
}

// 定义统一错误响应模型
public class ErrorResponse
{
    public string Code { get; set; }
    public string Message { get; set; }
}

第二步:客户端处理响应状态,设计合理返回方式

不建议使用out参数,这种方式不符合C#异步方法的设计习惯,可读性差。推荐以下两种方案:

方案1:返回包含结果状态的包装类

定义通用结果类统一封装成功/失败数据:

public class AuthResult
{
    public bool IsSuccess { get; set; }
    public AuthentificateResponse TokenData { get; set; }
    public ErrorResponse ErrorData { get; set; }
}

修改客户端认证方法:

public async Task<AuthResult> Authentificate(AuthentificateRequest model)
{
    var request = new HttpRequestMessage(HttpMethod.Post, new Uri(_baseUri, "Accounts/authentificate"));
    var requestJson = JsonConvert.SerializeObject(model);
    request.Content = new StringContent(requestJson, Encoding.UTF8, "application/json");
    
    var response = await _httpClient.SendAsync(request);
    var responseJson = await response.Content.ReadAsStringAsync();
    
    var result = new AuthResult();
    if (response.IsSuccessStatusCode)
    {
        result.IsSuccess = true;
        result.TokenData = JsonConvert.DeserializeObject<AuthentificateResponse>(responseJson);
    }
    else
    {
        result.IsSuccess = false;
        result.ErrorData = JsonConvert.DeserializeObject<ErrorResponse>(responseJson);
    }
    return result;
}

上层调用示例:

var authResult = await authService.Authentificate(model);
if (authResult.IsSuccess)
{
    // 处理令牌,如保存到本地
    var token = authResult.TokenData.JwtToken;
}
else
{
    // 显示错误提示
    MessageBox.Show(authResult.ErrorData.Message);
}

方案2:客户端抛出自定义异常

若错误场景属于异常流程(认证失败为少数情况),可通过抛出异常处理:

public async Task<AuthentificateResponse> Authentificate(AuthentificateRequest model)
{
    var request = new HttpRequestMessage(HttpMethod.Post, new Uri(_baseUri, "Accounts/authentificate"));
    var requestJson = JsonConvert.SerializeObject(model);
    request.Content = new StringContent(requestJson, Encoding.UTF8, "application/json");
    
    var response = await _httpClient.SendAsync(request);
    var responseJson = await response.Content.ReadAsStringAsync();
    
    if (response.IsSuccessStatusCode)
    {
        return JsonConvert.DeserializeObject<AuthentificateResponse>(responseJson);
    }
    else
    {
        var error = JsonConvert.DeserializeObject<ErrorResponse>(responseJson);
        throw new AuthException(error.Message, error.Code);
    }
}

// 自定义认证异常类
public class AuthException : Exception
{
    public string ErrorCode { get; }
    public AuthException(string message, string errorCode) : base(message)
    {
        ErrorCode = errorCode;
    }
}

上层调用示例:

try
{
    var tokenResponse = await authService.Authentificate(model);
    // 处理令牌逻辑
}
catch (AuthException ex)
{
    MessageBox.Show(ex.Message);
    // 根据ErrorCode做差异化处理
}

方案选择建议

  • 若业务逻辑需要同时处理成功/失败的多维度数据,选方案1的包装类,更贴合结果驱动的开发模式。
  • 若认证失败属于少数异常场景,选方案2的异常处理,代码结构更简洁,符合异常流程的设计习惯。

内容的提问来源于stack exchange,提问作者Alex

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.15 10:10:40