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

异步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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.06 20:53:21