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

C# WPF(MVVM)应用:实现每次API调用时检查用户登录状态

嘿,针对你的WPF MVVM应用场景,我来分享下怎么实现每次调用API时自动检查登录状态的方案,贴合你的现有WebRequest逻辑来:

核心思路

既然你已经用Cookie来维持登录状态,最靠谱的方式不是单纯检查Cookie是否存在(因为Cookie可能存在但已过期/失效),而是每次发起API请求前,先通过一个轻量的接口验证当前Cookie的有效性——比如调用一个登录后才能访问的简单接口(比如获取用户基础信息、或者仅返回状态的接口),根据响应结果判断用户是否还处于登录状态。

具体实现步骤

我把整个逻辑拆成几个清晰的部分,符合MVVM的职责分离原则:

  • 封装HTTP工具类:把Cookie管理、登录状态检查、API请求发送都统一放在这个类里,ViewModel只需要调用工具类的方法,不用关心底层细节。
  • 实现登录状态检查方法:在工具类里写一个异步方法,发送HEAD/GET请求到验证接口,根据响应状态码判断是否有效(比如200=有效,401/403=失效)。
  • API请求前自动检查:在通用的API请求方法里,先执行状态检查;如果失效,就触发事件通知ViewModel弹出登录窗口,重新登录后更新Cookie再继续请求。
  • ViewModel绑定逻辑:ViewModel订阅工具类的登录触发事件,处理登录窗口的显示,登录成功后把新的Cookie传递给工具类。
代码示例

1. HTTP工具类(ApiHttpClient)

这个类负责所有HTTP请求的底层逻辑,包括Cookie管理和登录状态检查:

using System;
using System.IO;
using System.Net;
using System.Text;
using System.Threading.Tasks;

public class ApiHttpClient
{
    // 保存登录后的Cookie容器
    private CookieContainer _cookieContainer = new();

    // 设置登录成功后的Cookie(登录窗口调用)
    public void UpdateLoginCookies(CookieContainer newCookies)
    {
        _cookieContainer = newCookies ?? new CookieContainer();
    }

    // 检查登录状态的核心方法
    private async Task<bool> IsUserLoggedInAsync(string validationUrl)
    {
        try
        {
            var request = WebRequest.CreateHttp(validationUrl);
            request.Method = "HEAD"; // 用HEAD请求更轻量,只获取响应头,不返回正文
            request.CookieContainer = _cookieContainer;

            using var response = (HttpWebResponse)await request.GetResponseAsync();
            // 200表示Cookie有效;401/403表示未授权,登录失效
            return response.StatusCode == HttpStatusCode.OK;
        }
        catch (WebException ex)
        {
            if (ex.Response is HttpWebResponse errorResp)
            {
                if (errorResp.StatusCode is HttpStatusCode.Unauthorized or HttpStatusCode.Forbidden)
                {
                    return false;
                }
            }
            // 其他异常(比如网络问题),根据业务需求处理,这里暂时返回false
            return false;
        }
    }

    // 通用API请求方法,自动检查登录状态
    public async Task<string> SendApiRequestAsync(string apiUrl, string method = "GET", string postData = null)
    {
        // 先检查登录状态
        var isLoggedIn = await IsUserLoggedInAsync("https://your-company-api.com/api/user/validate"); // 替换成你的验证接口
        if (!isLoggedIn)
        {
            // 触发登录事件,通知ViewModel弹出登录窗口
            LoginRequired?.Invoke(this, EventArgs.Empty);
            throw new InvalidOperationException("登录状态已失效,请重新登录");
        }

        // 正常发送API请求
        var request = WebRequest.CreateHttp(apiUrl);
        request.Method = method;
        request.CookieContainer = _cookieContainer;

        if (method.Equals("POST", StringComparison.OrdinalIgnoreCase) && !string.IsNullOrEmpty(postData))
        {
            var dataBytes = Encoding.UTF8.GetBytes(postData);
            request.ContentType = "application/x-www-form-urlencoded";
            request.ContentLength = dataBytes.Length;
            
            using var requestStream = await request.GetRequestStreamAsync();
            await requestStream.WriteAsync(dataBytes, 0, dataBytes.Length);
        }

        using var response = (HttpWebResponse)await request.GetResponseAsync();
        using var reader = new StreamReader(response.GetResponseStream());
        return await reader.ReadToEndAsync();
    }

    // 定义登录需要的事件,让ViewModel订阅
    public event EventHandler LoginRequired;
}

2. ViewModel中的使用示例

这里以MainViewModel为例,处理API调用和登录窗口的触发:

using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows;
using System.Windows.Input;

public class MainViewModel : INotifyPropertyChanged
{
    private readonly ApiHttpClient _apiClient;
    private string _apiResponseContent;

    public string ApiResponseContent
    {
        get => _apiResponseContent;
        set
        {
            _apiResponseContent = value;
            OnPropertyChanged();
        }
    }

    public ICommand LoadDataCommand { get; }

    public MainViewModel()
    {
        _apiClient = new ApiHttpClient();
        // 订阅登录触发事件,弹出登录窗口
        _apiClient.LoginRequired += (sender, e) => ShowLoginWindow();
        LoadDataCommand = new RelayCommand(async () => await LoadDataFromApi());
    }

    private async Task LoadDataFromApi()
    {
        try
        {
            // 调用API时自动检查登录状态
            var response = await _apiClient.SendApiRequestAsync("https://your-company-api.com/api/data/get");
            ApiResponseContent = $"数据加载成功:{response}";
        }
        catch (InvalidOperationException ex)
        {
            ApiResponseContent = ex.Message;
        }
        catch (Exception ex)
        {
            ApiResponseContent = $"加载失败:{ex.Message}";
        }
    }

    private void ShowLoginWindow()
    {
        var loginWindow = new LoginWindow();
        // 假设登录窗口关闭时,若登录成功则返回DialogResult=true,并提供CookieContainer
        if (loginWindow.ShowDialog() == true)
        {
            _apiClient.UpdateLoginCookies(loginWindow.LoginCookies);
            // 登录成功后可以自动重试之前的API请求
            _ = LoadDataFromApi();
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
    protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

// 基础RelayCommand实现(如果你的项目还没有的话)
public class RelayCommand : ICommand
{
    private readonly Func<Task> _execute;
    private readonly Func<bool> _canExecute;

    public event EventHandler CanExecuteChanged;

    public RelayCommand(Func<Task> execute, Func<bool> canExecute = null)
    {
        _execute = execute ?? throw new ArgumentNullException(nameof(execute));
        _canExecute = canExecute;
    }

    public bool CanExecute(object parameter) => _canExecute?.Invoke() ?? true;

    public async void Execute(object parameter) => await _execute();

    public void RaiseCanExecuteChanged() => CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}
关键注意事项
  • 选择合适的验证接口:尽量选一个响应快、数据量小的接口,比如仅返回用户登录状态的接口,用HEAD请求可以减少网络传输开销。
  • 异常区分处理:不要把所有异常都当成登录失效,要区分开401/403(登录失效)和其他异常(比如网络错误、服务器500错误)。
  • Cookie安全(可选):如果需要记住登录状态,可以把Cookie序列化后加密存储到本地(比如AppData文件夹),重启应用后加载,避免重复登录。
  • MVVM解耦:工具类只负责逻辑,不直接操作UI,通过事件通知ViewModel处理登录窗口,保持ViewModel的独立性。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.26 11:14:38