如何在Blazor.Net中将代码与UI分开

时间:2019-05-20 08:54:45

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

参考这篇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 Answers进行更改后,它会显示异常

  

System.Net.Http.HttpRequestException:无法建立连接,因为目标计算机主动拒绝了它。 ->   System.Net.Sockets.SocketException:无法建立连接   因为目标计算机主动拒绝了它。在   System.Net.Http.ConnectHelper.ConnectAsync(字符串主机,Int32端口,   CancellationToken cancellingToken)-内部异常结束   堆栈跟踪---   System.Net.Http.ConnectHelper.ConnectAsync(字符串主机,Int32端口,   的CancellationToken cancelToken()   System.Threading.Tasks.ValueTask 1.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   的请求,在   System.Threading.Tasks.ValueTask 1.get_Result() at System.Net.Http.HttpConnectionPool.GetHttpConnectionAsync(HttpRequestMessage request, CancellationToken cancellationToken) at System.Threading.Tasks.ValueTask 1.get_Result()位于   System.Net.Http.HttpConnectionPool.SendWithRetryAsync(HttpRequestMessage   request,布尔值doRequestAuth,CancellationToken cancelledToken)
  在System.Net.Http.RedirectHandler.SendAsync(HttpRequestMessage   的请求,在   System.Net.Http.HttpClient.FinishSendAsyncUnbuffered(任务1 sendTask, HttpRequestMessage request, CancellationTokenSource cts, Boolean disposeCts) at System.Net.Http.HttpClient.GetStringAsyncCore(Task 1   getTask)   Microsoft.AspNetCore.Builder.BlazorMonoDebugProxyAppBuilderExtensions.GetOpenedBrowserTabs(String   debuggerHost)在   Microsoft.AspNetCore.Builder.BlazorMonoDebugProxyAppBuilderExtensions.DebugHome(HttpContext   上下文)

4 个答案:

答案 0 :(得分: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块中拥有的所有代码都移到基类中,因为将两种方法混合使用可能会产生奇怪的副作用。

答案 1 :(得分:2)

您有两个选择。克里斯·桑迪(Chris Sainty)已经提到了第一个。创建一个从ComponentBase继承的类,并在Razor视图中继承它。

您的课程将定义为: public class MyBaseClass : ComponentBase

然后在“剃刀”视图中使用: @inherits MyBaseClass

这使MyBaseClass成为Razor视图的页面隐藏代码,并且能够覆盖该视图的所有生命周期事件。

第二个选项是创建一个ViewModel。您创建一个标准的C#类,并使用属性注入将其注入到Razor视图中。

您通常定义自己的班级: public class MyViewModel

并将其注入到Razor视图中: @inject MyViewModel

此ViewModel类不知道页面生命周期事件,并且与Blazor相关的任何内容都不相关。如果您只想将Razor视图绑定到一个对象,并且需要一些可重用的对象(或想将其放置在共享项目中),那么这是一个不错的选择。

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

答案 2 :(得分:2)

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

  

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

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

在React / Redux / Flux世界中,经常讨论“状态”的概念-指在某个时间点持有应用程序整体状态或应用程序区域的一个或多个对象。 。在世界的Flux视图中,应用程序中的每个动作都会将应用程序从现有状态转换为新状态。

应用程序的状态既包含当前数据值(例如contact.firstname),也包含UI的状态(例如,此按钮已启用)。

这基本上是通过与ViewModel方法相同的方法来实现的,但是State方法通常按行为将代码分组(例如,与订购Pizza有关的所有状态,因此,如果当前的Pizza包含什么,以及如果显示,则应该显示哪些UI元素)订单正在处理中),并认识到状态可能由多个组件显示-因此State对象不一定会像ViewModel通常那样直接映射到单个剃刀文件。

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

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

但是我不想在剃刀文件中使用C#代码...

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

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

答案 3 :(得分:1)

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