异步ViewModel初始化与依赖注入服务异常处理咨询
ViewModel异步加载数据的实现与优化
一、当前实现的问题与修正
你的代码存在几处语法和逻辑问题,修正后才能正常运行:
1. ViewModel类的语法错误
原代码中构造方法内直接声明公共集合的写法不符合C#语法,且类名拼写错误,修正后的ViewModel应将集合作为类属性,并通过构造参数传入初始化数据:
public class MyViewModel { public ObservableCollection<Country> Countries { get; } public ObservableCollection<VAT> VATs { get; } // 通过构造参数接收预加载的数据 public MyViewModel(List<Country> countries, List<VAT> vats) { Countries = new ObservableCollection<Country>(countries); VATs = new ObservableCollection<VAT>(vats); } }
2. DI注册的逻辑问题
.NET DI容器处理异步委托时,会将服务注册为Task<MyViewModel>而非直接的MyViewModel。如果直接注入MyViewModel会抛出异常,需调整注册方式:
services.AddSingleton<IMyService, MyService>(); // 注册为Task<MyViewModel>,上层注入时需await获取实例 services.AddTransient(async sp => { var myService = sp.GetRequiredService<IMyService>(); var myVats = await myService.GetTypesOfVatAsync(); var myCountries = await myService.GetCountriesAsync(); return new MyViewModel(myCountries, myVats); });
若要直接注入MyViewModel,可在委托内使用GetAwaiter().GetResult()强制同步,但这种方式会阻塞线程,可能引发死锁,不推荐使用。
二、异步方法的异常处理
在异步工厂委托中可通过try-catch捕获数据库异常,常见处理方式包括:
1. 记录日志+返回带错误状态的ViewModel
给ViewModel添加错误状态属性,捕获异常后设置状态,方便UI绑定显示错误信息:
// 扩展ViewModel的错误状态属性 public class MyViewModel { public ObservableCollection<Country> Countries { get; } public ObservableCollection<VAT> VATs { get; } public bool IsLoadingFailed { get; set; } public string ErrorMessage { get; set; } public MyViewModel(List<Country> countries, List<VAT> vats) { Countries = new ObservableCollection<Country>(countries); VATs = new ObservableCollection<VAT>(vats); } } // DI注册时的异常处理 services.AddTransient(async sp => { var logger = sp.GetRequiredService<ILogger<MyViewModel>>(); var myService = sp.GetRequiredService<IMyService>(); try { var myVats = await myService.GetTypesOfVatAsync(); var myCountries = await myService.GetCountriesAsync(); return new MyViewModel(myCountries, myVats) { IsLoadingFailed = false }; } catch (DbException ex) { logger.LogError(ex, "数据库加载数据失败"); return new MyViewModel(new List<Country>(), new List<VAT>()) { IsLoadingFailed = true, ErrorMessage = "加载数据失败,请检查数据库连接" }; } });
2. 抛出自定义异常
若需要上层统一处理异常,可将数据库异常包装为自定义异常抛出:
catch (DbException ex) { throw new DataLoadException("加载省份或VAT数据失败", ex); }
三、更优的ViewModel数据加载方案
1. 异步初始化接口(推荐)
定义异步初始化接口,让ViewModel自己负责数据加载逻辑,符合单一职责原则,也更易测试:
// 定义异步初始化接口 public interface IAsyncInitializable { Task InitializeAsync(); } // 实现接口的ViewModel public class MyViewModel : IAsyncInitializable { private readonly IMyService _myService; public ObservableCollection<Country> Countries { get; } = new ObservableCollection<Country>(); public ObservableCollection<VAT> VATs { get; } = new ObservableCollection<VAT>(); public bool IsLoading { get; set; } public bool IsLoadingFailed { get; set; } public string ErrorMessage { get; set; } public MyViewModel(IMyService myService) { _myService = myService; } public async Task InitializeAsync() { IsLoading = true; IsLoadingFailed = false; try { var countries = await _myService.GetCountriesAsync(); var vats = await _myService.GetTypesOfVatAsync(); foreach (var country in countries) Countries.Add(country); foreach (var vat in vats) VATs.Add(vat); } catch (DbException ex) { IsLoadingFailed = true; ErrorMessage = "加载数据失败,请检查数据库连接"; // 记录日志 } finally { IsLoading = false; } } }
DI注册简化为:
services.AddSingleton<IMyService, MyService>(); services.AddTransient<MyViewModel>();
使用时在View的生命周期事件中调用初始化:
var viewModel = serviceProvider.GetRequiredService<MyViewModel>(); await viewModel.InitializeAsync(); DataContext = viewModel;
2. 结合UI框架生命周期
比如在WPF的Loaded事件、Blazor的OnInitializedAsync方法中触发ViewModel的异步加载,让数据加载与UI生命周期同步。
3. 延迟加载+用户触发
如果数据非必需初始化时加载,可提供加载按钮,用户点击后触发异步加载,同时显示加载中状态(如进度条),提升用户体验。
内容的提问来源于stack exchange,提问作者Álvaro García
相关产品推荐
相关产品推荐

