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
相关产品推荐
相关产品推荐

