razor.net-coreblazorblazor-server-siderazor-components

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


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?


Solution

  • 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.)