Как отделить код от пользовательского интерфейса в Blazor.Net

Ссылка на эту статью VisualStudioMagazine, я пытаюсь иметь код в отдельном файле вместо вида бритвы.

Я старался:

@page "/Item"
@using WebApplication1.Shared
@using WebApplication1.Client.Services;
@inject HttpClient Http
@inherits ItemComponent

@if (ItemList != null)
{
    <table class="table">
        <thead>
            <tr>
                <th>ID</th>
                <th>Name</th>
                <th>Category</th>
                <th>Metal</th>
                <th>Price</th>
                <th>Quantity</th>
            </tr>
        </thead>
        <tbody>
            @foreach (var item in ItemList)
            {
                <tr>
                    <td>@item.ID</td>
                    <td>@item.Name</td>
                    <td>@item.Category</td>
                    <td>@item.Metal</td>
                    <td>@item.Price</td>
                    <td>@item.Quantity</td>
                </tr>
            }
        </tbody>
    </table>
}

@functions{
    public ItemModel[] ItemList;
    ItemComponent IC = new ItemComponent();

    protected override async Task OnInitAsync()
    {
        ItemList = IC.GetItems().Result;
        //ItemList = await Http.GetJsonAsync<ItemModel[]>("api/Item/GetItems");
        StateHasChanged();
    }
}

И ItemComponent:

using System.Threading.Tasks;
using WebApplication1.Shared;
using System.Net.Http;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Blazor;

namespace WebApplication1.Client.Services
{
    public class ItemComponent
    {
        public async Task<ItemModel[]> GetItems()
        {
            ItemModel[] ItemList;
            HttpClient Http = new HttpClient();
            ItemList = await Http.GetJsonAsync<ItemModel[]>("api/Item/GetItems");
            return ItemList;
        }

    }
}

Но это не работает, это показывает, что:

Код серьезности Описание Ошибка состояния подавления строки файла проекта CS0115 'Item.BuildRenderTree(RenderTreeBuilder)': не найден подходящий метод для переопределения WebApplication1.Client D:\Other\blazor\WebApplication1.Client\obj\Debug\netstandard2.0\RazorDeclaration\Pages\ItemModule\Item.razor.g.cs 30 Active

Также согласно странице учебника не может наследовать BlazorComponent в ItemComponent потому что не удалось получить ссылку.

Есть ли способ отделить большую часть кода от представления Blazor в отдельный файл кода?

Обновление 1

После внесения изменений в соответствии с Крисом Ответ, он показывает исключение

System.Net.Http.HttpRequestException: Невозможно установить соединение, поскольку целевая машина активно от него отказалась. ---> System.Net.Sockets.SocketException: не может быть установлено соединение, потому что целевая машина активно отказала ему. в System.Net.Http.ConnectHelper.ConnectAsync(Строковый хост, порт Int32, CancellationToken cancellationToken) --- Конец внутренней трассировки стека исключений --- в System.Net.Http.ConnectHelper.ConnectAsync(Строковый хост, порт Int32, CancellationToken cancellationToken) в System.Threading.Tasks.ValueTask1.get_Result() at System.Net.Http.HttpConnectionPool.ConnectAsync(HttpRequestMessage request, Boolean allowHttp2, CancellationToken cancellationToken)
at System.Threading.Tasks.ValueTask
1.get_Result () в System.Net.Http.HttpConnectionPool.CreateHttp11ConnectionAsync(запрос HttpRequestMessage, CancellationToken cancellationToken) в System.Threading.Tasks.ValueTask1.get_Result() at System.Net.Http.HttpConnectionPool.GetHttpConnectionAsync(HttpRequestMessage request, CancellationToken cancellationToken) at System.Threading.Tasks.ValueTask1.get_Result () в System.Net.Http.HttpConnectionPool.SendWithRetryAsync(запрос HttpRequestMessage, Boolean doRequestAuth, CancellationToken cancellationToken)
в System.Net.Http.RedirectHandler.SendAsync(запрос HttpRequestMessage, CancellationToken cancellationToken) в System.Net.Http.HttpClient.FinishSendAsyncUnbuffered(задача1 sendTask, HttpRequestMessage request, CancellationTokenSource cts, Boolean disposeCts) at System.Net.Http.HttpClient.GetStringAsyncCore(Task1 getTask) в Microsoft.AspNetCore.Builder.BlazorMonoDebugProxyAppBuilderExtensions.GetOpenedBrowserTabs(String debuggerHost) в Microsoft.AspNetCore.Builder.BlazorMonoDebugProxyAppBuilderExtensions (Hug) Hug

3 ответа

Решение

Вам просто нужно унаследовать от ComponentBase в вашем ItemComponent класс как это.

public class ItemComponent : ComponentBase
{
    public async Task<ItemModel[]> GetItems()
    {
        ItemModel[] ItemList;
        HttpClient Http = new HttpClient();
        ItemList = await Http.GetJsonAsync<ItemModel[]>("api/Item/GetItems");
        return ItemList;
    }
}

Статья немного устарела, так как BlazorComponent был переименован некоторое время назад.

Просто переместите весь код, который вы имеете в functions Блок вашего взгляда на базовый класс, так как смешение двух подходов может иметь странные побочные эффекты.

У вас есть два варианта. О первом уже упоминал Крис Сейнти. Создайте класс, наследуемый от ComponentBase, и унаследуйте его в представлении Razor.

Ваш класс будет определяться как:public class MyBaseClass : ComponentBase

И в вашем представлении Razor вы используете:@inherits MyBaseClass

Это делает MyBaseClass кодовой страницей для вашего представления Razor и может переопределять все события жизненного цикла для представления.

Второй вариант - создать ViewModel. Вы создаете стандартный класс C# и вставляете его в представление Razor с помощью внедрения свойств.

Вы обычно определяете свой класс:public class MyViewModel

И вставьте его в представление Razor:@inject MyViewModel

Этот класс ViewModel не знает о событиях жизненного цикла страницы и не зависит ни от чего, что связано с Blazor. Если вы просто хотите привязать свое представление Razor к объекту и вам нужно что-то, что можно повторно использовать (или вы хотите поместить его в общий проект), это может быть хорошим выбором.

Вы можете использовать унаследованный код и внедренную модель представления в одном представлении Razor, если вам нужно или если вы хотите сохранить код жизненного цикла страницы отдельно от привязок данных.

Здесь также есть другое решение, которое похоже на точку зрения Луи Хендрика:

Вы можете использовать унаследованный код и внедренную ViewModel в том же Razor View, если у вас есть необходимость или если вы хотите сохранить код жизненного цикла страницы отдельно от привязок данных.

Рассматривать "состояние" как альтернативу моделям представления

В мире React/Redux/Flux часто обсуждается понятие "состояние", которое относится к объекту (или объектам), который содержит весь статус приложения или области приложения) в определенный момент времени. В представлении Flux о мире каждое действие в приложении переводит приложение из его существующего состояния в новое состояние.

Состояние приложения состоит из обоих текущих значений данных (например, скажем, contact.firstname), а также состояние пользовательского интерфейса (например, включена ли эта кнопка).

Это достигается в основном тем же способом, что и подход ViewModel, но подход State часто группирует код по поведению (например, все состояния, связанные с заказом Pizza, так, из чего состоит текущая Pizza, а также какие элементы пользовательского интерфейса должны отображаться, если заказ обрабатывается) и признает, что состояние может отображаться несколькими компонентами, поэтому объекты состояния не обязательно будут отображаться напрямую в один файл бритвы, как это обычно делает ViewModel.

Отличный пример и учебное пособие, предоставлено командой.NET

Это проще на примере, и, к счастью, мастерская Blazour Pizza команды Microsoft Blazor обеспечивает превосходную мастерскую.

В качестве быстрого примера из этого урока - это OrderState класс, который содержит текущее состояние, относящееся к текущему порядку:

    public class OrderState
    {
        public event EventHandler StateChanged;

        public bool ShowingConfigureDialog { get; private set; }

        public Pizza ConfiguringPizza { get; private set; }

        public Order Order { get; private set; } = new Order();

        public void ShowConfigurePizzaDialog(PizzaSpecial special)
        {
            ConfiguringPizza = new Pizza()
            {
                Special = special,
                SpecialId = special.Id,
                Size = Pizza.DefaultSize,
                Toppings = new List<PizzaTopping>(),
            };

            ShowingConfigureDialog = true;
        }

        public void CancelConfigurePizzaDialog()
        {
            ConfiguringPizza = null;

            ShowingConfigureDialog = false;
            StateHasChanged();
        }

        public void ConfirmConfigurePizzaDialog()
        {
            Order.Pizzas.Add(ConfiguringPizza);
            ConfiguringPizza = null;

            ShowingConfigureDialog = false;
            StateHasChanged();
        }

        public void RemoveConfiguredPizza(Pizza pizza)
        {
            Order.Pizzas.Remove(pizza);
            StateHasChanged();
        }

        public void ResetOrder()
        {
            Order = new Order();
        }

        private void StateHasChanged()
        {
            StateChanged?.Invoke(this, EventArgs.Empty);
        }
    } ```

Обратите внимание, что у этого класса состояний нет концепции связанного с ним пользовательского интерфейса, но у него есть свойства, которые управляют поведением пользовательского интерфейса.

В этом примере классы-бритвы по-прежнему имеют блоки @functions, но они значительно упрощаются за счет введения в класс State свойств, которые играют явную роль в управлении поведением пользовательского интерфейса (например, ShowingConfigureDialog). Например, из index.razor:

    <ul class="pizza-cards">
        @if (specials != null)
        {
            @foreach (var special in specials)
            {
                <li onclick="@(() => OrderState.ShowConfigurePizzaDialog(special))"
style="background-image: url('@special.ImageUrl')">
                    <div class="pizza-info">
                        <span class="title">@special.Name</span>
                        @special.Description
                        <span class="price">@special.GetFormattedBasePrice()</span>
                    </div>
                </li>
            }
        }
    </ul> </div> ```

Весь этот учебник превосходен, я настоятельно рекомендую поработать над ним.

Но я не хочу код C# в моих файлах бритвы...

Вы все еще можете поместить код из блока @functions в файл базового класса, а также использовать подход состояния.

Причина, по которой люди обычно этого не делают, заключается в том, что если ваш файл состояния управляет поведением пользовательского интерфейса, то @functions код подключения обычно заканчивается в виде нескольких строк, поэтому часто кажется, что его не стоит помещать в отдельный файл.

Я прочитал статью о подходе к родительскому классу, создав класс, который наследуется от ComponentBase, и просто наследовал от этого базового класса в вашем компоненте. Я не фанат, потому что это заставляет меня раскрывать структуру класса, которая должна поддерживаться внутренне / конфиденциально для класса, и отслеживание защиты для наследования, я полагаю, является правильным ответом.

Однако мне может здесь чего-то не хватать, поэтому, пожалуйста, не убивайте меня за то, что я рекомендовал это, но почему вы не можете просто использовать частичную директиву, создать файл "sidecar" (моя терминология) для ComponentName.razor.cs и просто объявить класс как частичный класс. Я пробовал это, и он работал нормально...

используя текущий на момент написания этого проекта шаблона, в компоненте Counter я просто вырезал весь код, чтобы получить следующее:

@page "/counter"

<h1>Counter</h1>

<p>Current count: @currentCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

Затем я приступил к созданию файла сопроводительного файла Counter.razor.cs и заполнил его:

using Microsoft.AspNetCore.Components;

namespace FirstBlazorWasm.Pages //my test namespace
{
    public partial class Counter //<--- note the partial class definition 
    {

        private int currentCount;

        private void IncrementCount()
        {
            currentCount++;
        }
    }
}

Зовите меня г. 2003 год, но он работает.:)

Другие вопросы по тегам