17

参考这篇VisualStudioMagazine文章,我试图将代码放在单独的文件中,而不是 razor 视图中。

我试过:

@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();
    }
}

和项目组件:

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 活动

同样根据教程页面不能继承BlazorComponentItemComponent因为它没有参考。

有什么方法可以将大部分代码从 Blazor 视图分离到单独的代码文件中?

更新 1

根据 Chris Answer 进行更改后,它显示异常

System.Net.Http.HttpRequestException:无法建立连接,因为目标机器主动拒绝了它。---> System.Net.Sockets.SocketException: 无法建立连接,因为目标机器主动拒绝。在 System.Net.Http.ConnectHelper.ConnectAsync(String host, Int32 port, CancellationToken cancelToken) --- 内部异常堆栈跟踪结束 --- 在 System.Net.Http.ConnectHelper.ConnectAsync(String host, Int32 port, CancellationToken cancelToken ) 在 System.Threading.Tasks.ValueTask 1.get_Result() 在 System.Net.Http.HttpConnectionPool.CreateHttp11ConnectionAsync(HttpRequestMessage request, CancellationToken cancelToken) 在 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() at System.Net.Http.HttpConnectionPool.GetHttpConnectionAsync(HttpRequestMessage request, CancellationToken cancellationToken) at System.Threading.Tasks.ValueTask1.get_Result() 在 System.Net.Http.HttpConnectionPool.SendWithRetryAsync(HttpRequestMessage request, Boolean doRequestAuth, CancellationToken cancelToken) 在 System.Net.Http.RedirectHandler.SendAsync(HttpRequestMessage request, CancellationToken cancelToken
) 在 System.Net.Http.HttpClient 1 sendTask, HttpRequestMessage request, CancellationTokenSource cts, Boolean disposeCts) at System.Net.Http.HttpClient.GetStringAsyncCore(TaskMicrosoft.AspNetCore.Builder.BlazorMonoDebugProxyAppBuilderExtensions.GetOpenedBrowserTabs(String debuggerHost) 上的.FinishSendAsyncUnbuffered(Task 1 getTask).AspNetCore.Builder.BlazorMonoDebugProxyAppBuilderExtensions.DebugHome(HttpContext context)

4

4 回答 4

27

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视图块中的所有代码移动到基类中,因为混合这两种方法可能会产生奇怪的副作用。

于 2019-05-20T10:22:34.883 回答
13

你有两个选择。第一个已经被 Chris Sainty 提到过。创建一个继承自 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。

于 2019-05-20T12:04:27.327 回答
12

这里还有另一个解决方案,类似于Louis Hendrick 的观点

如果您有需要或希望将页面生命周期代码与数据绑定分开,您可以在同一 Razor 视图上使用继承的代码和注入的 ViewModel。

将“状态”视为视图模型的替代方案

近年来,关于使用“状态”概念管理应用程序当前状态的讨论很多。自从 Flux 模式(尤其是 Redux 实现)兴起以来,这在 React(以及现在的其他 JS 框架)世界中特别流行。

状态和视图模型有什么区别?

视图模型通常表示特定页面的状态,并且通常包含与该页面的呈现方式相关的属性(例如,选择列表的数据,表示页面的某个部分是否应该可见的额外属性等。 ) 以及一个属性,该属性包含要在该页面上绑定的数据的对象(例如,一个SalesOrder类)。

基于状态的方法做的事情大致相同,但不是按适用于(如视图模型那样)的页面对状态进行分组,基于状态的方法通常按行为对代码进行分组(例如,与订购披萨有关的所有状态,因此当前的 Pizza 包含哪些内容以及如果订单正在处理中应该显示哪些 UI 元素)并识别状态可能由多个组件显示 - 因此 State 对象不一定会直接映射到单个 razor 文件中ViewModel 通常会采用的方式。

为什么要采取国家方法?

基于状态的方法有两个主要好处:

  1. 因为状态类不依赖于 UI 类或框架(因此不引用 Blazor、Razor 等),它可以像任何其他 C# 类一样进行测试。这意味着您可以例如通过测试 true 来检查当数据类上的属性设置为某个值时按钮是否会被禁用MyState.SaveButtonEnabled' property is 。这比尝试通过 UI 自动化等来测试行为要简单得多。
  2. 基于状态的方法考虑到应用程序中功能区域的状态通常跨越多个组件或页面的事实。对于较小的单页应用程序 (SPA),通常使用单个状态对象来表示整个应用程序就足够了。显然,这种方法仅适用于整个应用程序在用户会话期间存在的 SPA。

一个很好的例子和教程,由 .NET 团队提供

举个例子就更容易了,谢天谢地,Microsoft Blazor 团队的Blazing Pizza 的 blazor-workshop提供了一个极好的方法。

作为该教程中的一个简单示例 - 这是保存与进行中订单相关的当前状态的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);
        }
    } ```

请注意,此状态类没有绑定到它的 UI 的概念,但它确实具有控制 UI 行为的属性。

在该示例中,剃刀类仍然具有 @functions 块,但是通过在 State 类中引入在控制 UI 行为中具有明确作用的属性(例如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# 代码......

您仍然可以将@code块中的代码放在基类的文件中,也可以使用状态方法。

人们倾向于不这样做的原因是,如果您的状态文件正在驱动 UI 行为,那么@code接线代码通常会以几行结尾,因此通常看起来不值得放在单独的文件中。

于 2019-05-20T15:08:37.810 回答
5

我通过创建一个从 ComponentBase 继承的类并简单地从组件中的该基类继承来阅读有关父类方法的文章。我不是粉丝,因为它迫使我向类公开应该在内部/私有维护的类结构,并且跟踪受保护的继承我认为是正确的答案。

但是,我可能在这里遗漏了一些东西,所以请不要因为推荐这个而责骂我,但是为什么你不能只使用部分指令,创建一个 ComponentName.razor.cs 的“sidecar”(我的术语)文件并简单地声明类作为部分类。我试过这个,它工作得很好......

使用当前的写作模板项目,在 Counter 组件中,我简单地将所有代码剥离出来,结果如下:

@page "/counter"

<h1>Counter</h1>

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

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

然后我继续创建 Sidecar 文件 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 年先生,但它有效。:)

于 2019-12-11T03:06:37.343 回答