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

跨WinUI3与Blazor的ViewModel库:Blazor端IUIThreadService的DI注入方案问询

Blazor中为跨平台ViewModel注入IUIThreadService的解决方案

你要的核心目标是让ViewModel通过DI拿到适配Blazor的IUIThreadService,同时不用在每个组件里手动给ViewModel赋值服务,这里有两种可行方案,其中第二种更适合规模化项目:

方案一:Scoped服务+组件初始化注入上下文

这个方案步骤简单,适合小体量项目:

  1. 实现Blazor版IUIThreadService
    这个实现需要临时存储组件的InvokeAsync和StateHasChanged方法引用,用于调度UI线程操作:

    public class BlazorUIThreadService : IUIThreadService
    {
        private Func<Func<Task>, Task> _invokeAsync;
        private Func<Task> _stateHasChanged;
    
        // 初始化方法,用来接收组件的上下文方法
        public void Initialize(Func<Func<Task>, Task> invokeAsync, Func<Task> stateHasChanged)
        {
            _invokeAsync = invokeAsync;
            _stateHasChanged = stateHasChanged;
        }
    
        public Task ExecuteOnUIThreadAsync(Func<Task> action)
        {
            if (_invokeAsync == null)
                throw new InvalidOperationException("BlazorUIThreadService未完成初始化");
            return _invokeAsync(action);
        }
    
        public void ExecuteOnUIThread(Action action)
        {
            _ = ExecuteOnUIThreadAsync(() => { action(); return Task.CompletedTask; });
            _stateHasChanged?.Invoke();
        }
    }
    
  2. 注册Scoped服务
    在Blazor的Program.cs里,把这个实现注册为Scoped服务(和Blazor组件的生命周期匹配):

    builder.Services.AddScoped<IUIThreadService, BlazorUIThreadService>();
    
  3. 组件中初始化服务
    在需要使用ViewModel的组件里,注入IUIThreadService并完成初始化:

    @inject IUIThreadService UIThreadService
    @inject MyViewModel ViewModel
    
    @code {
        protected override void OnInitialized()
        {
            if (UIThreadService is BlazorUIThreadService blazorService)
            {
                blazorService.Initialize(InvokeAsync, StateHasChanged);
            }
            base.OnInitialized();
        }
    }
    
  4. ViewModel中直接使用
    ViewModel通过构造函数注入IUIThreadService,不用关心平台细节:

    public class MyViewModel : INotifyPropertyChanged
    {
        private readonly IUIThreadService _uiThreadService;
        private string _loadStatus;
    
        public string LoadStatus
        {
            get => _loadStatus;
            set { _loadStatus = value; OnPropertyChanged(); }
        }
    
        public MyViewModel(IUIThreadService uiThreadService)
        {
            _uiThreadService = uiThreadService;
        }
    
        public async Task FetchDataAsync()
        {
            // 模拟异步操作
            var data = await RemoteDataService.GetDataAsync();
            // 调度回UI线程更新属性
            await _uiThreadService.ExecuteOnUIThreadAsync(() =>
            {
                LoadStatus = $"共加载{data.Count}条数据";
            });
        }
    
        // INotifyPropertyChanged实现代码省略...
    }
    

方案二:基类统一处理+组件上下文服务(更优)

如果项目组件较多,不想重复写初始化代码,可以用这个方案,通过组件基类统一完成上下文注入:

  1. 定义组件上下文接口和实现
    先封装组件的UI线程相关方法:

    public interface IComponentContext
    {
        Func<Func<Task>, Task> InvokeAsync { get; set; }
        Func<Task> StateHasChanged { get; set; }
    }
    
    public class ComponentContext : IComponentContext
    {
        public Func<Func<Task>, Task> InvokeAsync { get; set; }
        public Func<Task> StateHasChanged { get; set; }
    }
    
  2. 修改BlazorUIThreadService依赖上下文
    让服务直接依赖IComponentContext,不用单独写初始化方法:

    public class BlazorUIThreadService : IUIThreadService
    {
        private readonly IComponentContext _componentContext;
    
        public BlazorUIThreadService(IComponentContext componentContext)
        {
            _componentContext = componentContext;
        }
    
        public Task ExecuteOnUIThreadAsync(Func<Task> action)
        {
            if (_componentContext.InvokeAsync == null)
                throw new InvalidOperationException("组件上下文未初始化");
            return _componentContext.InvokeAsync(action);
        }
    
        public void ExecuteOnUIThread(Action action)
        {
            _ = ExecuteOnUIThreadAsync(() => { action(); return Task.CompletedTask; });
            _componentContext.StateHasChanged?.Invoke();
        }
    }
    
  3. 注册服务
    在Program.cs里注册这两个Scoped服务:

    builder.Services.AddScoped<IComponentContext, ComponentContext>();
    builder.Services.AddScoped<IUIThreadService, BlazorUIThreadService>();
    
  4. 创建组件基类
    所有Blazor组件都继承这个基类,自动完成上下文初始化:

    public class BaseBlazorComponent : ComponentBase
    {
        [Inject] protected IComponentContext ComponentContext { get; set; }
    
        protected override void OnInitialized()
        {
            ComponentContext.InvokeAsync = InvokeAsync;
            ComponentContext.StateHasChanged = StateHasChanged;
            base.OnInitialized();
        }
    }
    
  5. 组件和ViewModel使用
    组件只需继承基类,ViewModel还是通过构造函数注入IUIThreadService,全程不用手动处理平台适配逻辑:

    // 组件代码
    @inherits BaseBlazorComponent
    @inject MyViewModel ViewModel
    
    <p>@ViewModel.LoadStatus</p>
    <button @onclick="ViewModel.FetchDataAsync">加载数据</button>
    

方案对比

  • 方案一:实现简单,适合小型项目或少量组件的场景,但每个使用ViewModel的组件都要写初始化代码
  • 方案二:通过基类统一处理,减少重复代码,适合中大型项目,维护性更好

两种方案都能保证ViewModel完全不依赖Blazor的特定API,和WinUI端的ViewModel实现保持一致,实现真正的跨平台复用。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.12 22:57:45