如何将异步值分配给 Razor 组件 [参数] 属性?

How to Assign Async Value to Razor Component [Parameter] Property?

我正在尝试在 Blazor 服务器端项目中创建我的第一个 Razor 组件。 Razor 组件名为 MyComponent 并配置了一个 属性 以从输入检索其值:

MyComponent.razor

    [Parameter]
    public int Count {get; set;}

我正在从通过 IServiceCollection 配置的注入服务中提取计数,如下所示:

    public interface ICountingService
    {
        ValueTask<int> Get();
    }

托管页面,Index.razor 如下所示:

@page "/"
@inject ICountingService Counter

<h1>Hello World!</h1>

<MyComponent Count="@Counter.Get()" />

但是,我似乎无法为 Count 属性 绑定正确的值。

我收到以下错误:

cannot convert from 'System.Threading.Tasks.ValueTask<int>' to 'int'

我找到的所有为 Razor 组件分配 [Parameter] 值的示例都是同步的,我发现的唯一异步值是用于回调和方法(不是参数)。

此外,在线搜索没有return任何明显的东西,所以我在这里发帖希望找到答案。

请注意,我知道使用 protected override async Task OnInitializedAsync 并在其中存储一个值,但与上述方法相比,这似乎需要很多仪式,尤其是考虑到我将要使用的多种服务和属性时最终还是要绑定。

那么,如何以我喜欢的方式将异步调用的值分配给 Razor 组件 [Parameter] 属性?

试试这个。

@page "/"
@inject ICountingService Counter

<h1>Hello World!</h1>

<MyComponent Count="@CounterValue" />

@code{
    
     public int CounterValue {get; set;}
     protected override async Task OnInitializedAsync()
     {
           CounterValue = await Counter.Get();
     }
}

问题是,Counter.Get() 不是一个整数值;这是一个现在或将来在某个未定义点有一个 int 的任务。所以你不能把它的值赋给现在需要 int 的东西,因为 int 不一定存在。

您已经找到了答案,虽然它 "seems like a lot of ceremony",但它确实是唯一的方法:

  • 创建一个 int 属性 来保存值。
  • 声明一个异步方法
  • 在该方法中,将 Counter.Get()awaited 值分配给保存值
  • 的 int
  • 设置组件的 Count 属性 等于 int 属性

可能感觉有点仪式感,但你应该心存感激。异步本质上是非常复杂的,并且 async/await 可用已经为您解决了大约 95% 的艰苦工作。如果你认为这个解决方案很混乱,你应该看看如何才能把它弄好 without async/await!

在@mason-wheeler 和@rich-bryant 提供了他们的答案后,我进一步考虑了这个问题并找到了我的解决方案,我已将其发布在这里:

https://github.com/Mike-E-angelo/Blazor.ViewProperties

我称它为 ViewProperty,如下所示:

    public interface IViewProperty
    {
        ValueTask Get();
    }

    public sealed class ViewProperty<T> : IViewProperty
    {
        public static implicit operator ViewProperty<T>(ValueTask<T> instance) => new ViewProperty<T>(instance);

        readonly ValueTask<T> _source;

        public ViewProperty(ValueTask<T> source) => _source = source;

        public T Value { get; private set; }

        public bool HasValue { get; private set; }

        public async ValueTask Get()
        {
            Value    = await _source;
            HasValue = true;
        }

        public override string ToString() => Value.ToString();
    }

然后将其与组件基类型配对,然后循环访问组件的视图属性并调用它们各自的异步操作:

    public abstract class ViewPropertyComponentBase : ComponentBase
    {
        protected override async Task OnParametersSetAsync()
        {
            var properties = GetType().GetRuntimeProperties();
            foreach (var metadata in properties.Where(x => x.GetCustomAttributes<ParameterAttribute>().Any() &&
                                                           typeof(IViewProperty).IsAssignableFrom(x.PropertyType)))
            {
                if (metadata.GetValue(this) is IViewProperty property)
                {
                    await property.Get().ConfigureAwait(false);
                }
            }
        }
    }

使用以上内容的示例剃须刀组件:

MyComponent.razor

@inherits ViewPropertyComponentBase

@if (Count.HasValue)
{
    <p>Your magic number is @Count.</p>
}
else
{
    <p>Loading, please wait...</p>
}


@code {

    [Parameter]
    public ViewProperty<int> Count { get; set; }

}

最终使用是具有直接绑定的清晰视图,无需覆盖或其他额外仪式:

@page "/"
@inject ICounter Count

<h1>Hello, world!</h1>

Welcome to your new app.

<MyComponent Count="@Count.Count()" />

(请注意我的 posted example and above uses reflection, which is slow. In the actual version of the solution that I am using, I compile the member access as lambda expressions and cache the result. You can find that by starting here 如果你足够勇敢地四处闲逛。)

感觉有点老套,但你可以这样做:

<MyComponent Count="@Counter.Get().Result" />