【问题标题】:How to Separate Code From UI In Blazor.Net如何在 Blazor.Net 中将代码与 UI 分离
【发布时间】:2019-05-20 08:54:44
【问题描述】:

参考这篇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 活跃

同样根据教程页面不能继承BlazorComponentItemComponent,因为它没有引用。

有没有办法将大部分代码从 Blazor 视图中分离到单独的代码文件中?

更新 1

按照 Chris Answer 进行更改后,显示异常

System.Net.Http.HttpRequestException: 由于目标机器主动拒绝,无法建立连接。 ---> System.Net.Sockets.SocketException:无法建立连接 因为目标机器主动拒绝了。在 System.Net.Http.ConnectHelper.ConnectAsync(字符串主机,Int32 端口, CancellationToken cancelToken) --- 内部异常结束 堆栈跟踪 --- 在 System.Net.Http.ConnectHelper.ConnectAsync(字符串主机,Int32 端口, 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 取消令牌)在 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 request, Boolean doRequestAuth, CancellationToken cancelToken)
在 System.Net.Http.RedirectHandler.SendAsync(HttpRequestMessage 请求,CancellationToken 取消令牌)在 System.Net.Http.HttpClient.FinishSendAsyncUnbuffered(Task1 sendTask, HttpRequestMessage request, CancellationTokenSource cts, Boolean disposeCts) at System.Net.Http.HttpClient.GetStringAsyncCore(Task1 getTask) 在 Microsoft.AspNetCore.Builder.BlazorMonoDebugProxyAppBuilderExtensions.GetOpenedBrowserTabs(字符串 调试器主机)在 Microsoft.AspNetCore.Builder.BlazorMonoDebugProxyAppBuilderExtensions.DebugHome(HttpContext 上下文)

【问题讨论】:

    标签: c# blazor .net-core-3.0 blazor-server-side asp.net-core-3.0


    【解决方案1】:

    您只需要像这样从ItemComponent 类中的ComponentBase 继承即可。

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

    【讨论】:

    • 谢谢,我试试看,除了github还有什么网站可以找到关于blazor的新内容
    • 官方网站是blazor.net。我也会考虑加入 Blazor Gitter 频道。我们里面有很多人gitter.im/aspnet/Blazor
    • 错误消失了,但它没有在 UI 上显示数据,在可调试的浏览器选项卡中显示异常,请检查我的更新 1
    • 你能把这个作为一个单独的问题提出来吗@Saurabh
    【解决方案2】:

    你有两个选择。第一个已经被 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。

    【讨论】:

    • 唯一需要指定@inherits MyBaseClass 的情况是code-behind 名称或命名空间与.razor 文件的名称或命名空间不同
    【解决方案3】:

    这里还有另一个类似于Louis Hendrick's point 的解决方案:

    您可以在后面使用继承的代码和注入的 ViewModel 如果您有需要或想要保留页面,请使用相同的 Razor View 生命周期代码与您的数据绑定分开。

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

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

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

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

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

    为什么要采用状态方法?

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

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

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

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

    作为该教程中的一个简单示例 - 这是 OrderState class,它保存与进行中订单相关的当前状态:

        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> ```
    

    整个教程非常好,我强烈建议您完成它。

    但我不想在我的 razor 文件中使用 C# 代码...

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

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

    【讨论】:

    • 谢谢,但我想在 razor 文件中保留尽可能少的代码
    • @Saurabh 你仍然可以这样做,我已经相应地更新了我的答案以反映这一点
    【解决方案4】:

    我通过创建一个继承自 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 年先生,但它有效。 :)

    【讨论】:

    • 好吧,Life Cycle Hooks , OnInit 呢,如果你把它放在部分类中,它会工作吗?
    • “为什么你不能只使用部分指令” - 你可以(并且应该)但它只在 Core 3.1 之后可用。这个问题和答案在 Blazor 时代是古老的。
    • @Saurabh - 是的,还有[Parameter] 属性等
    • 伙计们。我刚刚开始从 3.1 开始,所以我可能错过了一些历史背景。感谢您的洞察力。
    • 我们需要单独安装3.1还是更新VS 2019就足够了? , 更新 VS 2019 后,我得到了仅包含 razor 页面的 blazor 服务器应用程序模板,其中没有 api 控制器。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-07-05
    相关资源
    最近更新 更多