我正试图在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'
我发现的所有将[Parameter]
值分配给RazorComponents的示例都是同步的,我发现的唯一异步值是回调和方法(而不是参数(。
此外,在网上搜索并没有返回任何明显的结果,所以我在这里发帖,希望能找到答案。
请注意,我知道使用protected override async Task OnInitializedAsync
并在其中存储值,但与上面的方法相比,这似乎需要很多仪式,尤其是在考虑到我最终必须绑定的多个服务和属性时。
那么,如何以我喜欢的方式将异步调用中的值分配给RazorComponent[Parameter]
属性呢?
问题是,Counter.Get()
不是一个int值;这是一个任务,无论是现在还是将来,它都会在某个未定义的点上有一个int。所以你不能把它的值分配给现在需要一个int的东西,因为这个int还不一定存在。
你已经得到了答案,尽管这"看起来很有仪式感",但这确实是唯一的方法:
- 创建一个int属性来保存该值
- 声明一个异步方法
- 在该方法中,将
Counter.Get()
的await
ed值分配给保存该值的int - 将组件的Count属性设置为int属性
这可能感觉像是一种仪式,但你应该心存感激。异步从本质上来说是非常复杂的,拥有可用的异步/等待已经为您完成了大约95%的艰苦工作。如果你认为这个解决方案很混乱,你应该看看如果没有async/await,需要什么才能把它做好!
试试这个。
@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();
}
}
在@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()" />
(请注意,我发布的示例和上面的示例使用了反射,这很慢。在我使用的解决方案的实际版本中,我将成员访问编译为lambda表达式并缓存结果。如果你足够勇敢,可以从这里开始。(
这感觉有点粗糙,但你可以这样做:
<MyComponent Count="@Counter.Get().Result" />