Как присвоить Asyn c значение свойству Razor Component [Parameter]? - PullRequest
1 голос
/ 20 февраля 2020

Я пытаюсь создать свой первый компонент Razor в проекте на стороне сервера Blazor. Компонент 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] значений для компонентов Razor, являются синхронными, и единственные асинхронные значения, которые я нашел для обратные вызовы и методы (не параметры).

Более того, поиск в Интернете не дал ничего очевидного, поэтому я публикую здесь в надежде найти ответ.

Обратите внимание, что я знаю об использовании protected override async Task OnInitializedAsync и сохранение значения там, но это выглядит как обязательная церемония по сравнению с подходом, описанным выше, особенно при рассмотрении множества сервисов и свойств, которые мне в конечном итоге придется связать.

Итак, как это сделать? присваивать значения из асинхронного вызова свойству Razor Component [Parameter] так, как я бы предпочел?

Ответы [ 3 ]

1 голос
/ 21 февраля 2020

После того, как @ mason-wheeler и @ rich-bryant предоставили свои ответы, я немного подумал об этом и нашел свое решение, которое я разместил здесь:

https://github.com/Mike-EEE/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()" />

(Обратите внимание, что мой опубликованный пример и выше использует отражение, которое является медленным. В реальной версии решения, которое я Я использую, я компилирую членский доступ как лямбда-выражения и кеширую результат. Вы можете найти это , начав здесь , если вы достаточно смелы, чтобы возиться.)

1 голос
/ 20 февраля 2020

Попробуйте это.

@page "/"
@inject ICountingService Counter

<h1>Hello World!</h1>

<MyComponent Count="@CounterValue" />

@code{

     public int CounterValue {get; set;}
     protected override async Task OnInitalizedAsync()
     {
           CounterValue = await Counter.Get();
     }
}
0 голосов
/ 20 февраля 2020

Проблема в том, что Counter.Get() не является значением типа int; это задача, которая будет иметь int в некоторой неопределенной точке либо сейчас, либо в будущем. Таким образом, вы не можете присвоить его значение чему-то, что ожидает int прямо сейчас, потому что это int не обязательно существует еще.

У вас уже есть ответ, и хотя это "кажется большим количеством церемонии ", это действительно единственный способ сделать это:

  • Создать свойство int для хранения значения.
  • Объявить асинхронный c метод
  • В В этом методе присвойте значение await ed Counter.Get() для типа int, содержащего значение
  • Установите свойство Count компонента, равное свойству int

Это может выглядеть так много церемоний, но вы должны быть благодарны. Асинхронность по своей сути очень сложна, и наличие асинхронного / ожидающего уже решает около 95% тяжелой работы за вас. Если вы думаете, что это решение грязное, вы должны увидеть, что нужно сделать, чтобы все было правильно без async / await!

...