I am attempting to create my first Razor Component in a Blazor Server-side project. The Razor Component is named MyComponent
and has a property configured to retrieve its value from input:
MyComponent.razor
[Parameter]
public int Count {get; set;}
I am pulling the count from an injected service configured via IServiceCollection
, which looks like this:
public interface ICountingService
{
ValueTask<int> Get();
}
The hosting page, Index.razor
looks like the following:
@page "/"
@inject ICountingService Counter
<h1>Hello World!</h1>
<MyComponent Count="@Counter.Get()" />
However, I cannot seem to bind the correct value for the Count
property.
I get the following error:
cannot convert from 'System.Threading.Tasks.ValueTask<int>' to 'int'
All of the examples I have found for assigning [Parameter]
values to Razor Components are synchronous, and the only asynchronous values I have found are for callbacks and methods (not parameters).
Further, searching online did not return anything obvious so I am posting here in hopes of finding an answer.
Note that I am aware of using protected override async Task OnInitializedAsync
and storing a value in there, but that seems like a lot of required ceremony compared to the approach above, especially when considering the multiple services and properties that I will ultimately have to bind.
So, how does one assign values from an asynchronous call to a Razor Component [Parameter]
property in the way that I would prefer?
After @mason-wheeler and @rich-bryant provided their answers, I went to think about this a little more and found my solution, which I have posted here:
https://github.com/Mike-E-angelo/Blazor.ViewProperties
I am calling it a ViewProperty
which looks like the following:
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();
}
You then pair it with a component base type that then iterates through the component's view properties and invokes their respective asynchronous operations:
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);
}
}
}
}
A sample razor component that uses the above:
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; }
}
The resulting use is a clean view with direct bindings and no need for overrides or other additional ceremony:
@page "/"
@inject ICounter Count
<h1>Hello, world!</h1>
Welcome to your new app.
<MyComponent Count="@Count.Count()" />
(NOTE that my 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 if you are brave enough to poke around.)