首页 > 解决方案 > Blazor:如何使用来自具有 2 个不同状态的 2 个不同页面的组件

问题描述

在 Blazor 应用程序中,我想使用来自两个不同页面的组件。组件使用服务来维护其状态。但是每个页面都需要让组件使用不同的状态。以下是我认为我会这样做的方式(使用默认的 Counter 组件进行演示)。

CounterService.cs

namespace TestTwoInterfaceToOneService.Shared
{
    public interface ICounterServiceA
    {
        int CurrentCount { get; set; }
        void IncrementCount();
    }

    public interface ICounterServiceB
    {
        int CurrentCount { get; set; }
        void IncrementCount();
    }

    public class CounterService : ICounterServiceA, ICounterServiceB
    {
        public int CurrentCount { get; set; }
        public void IncrementCount()
        {
            CurrentCount++;
        }
    }
}

在 Program.cs 添加:

builder.Services.AddScoped<ICounterServiceA, CounterService>();
builder.Services.AddScoped<ICounterServiceB, CounterService>();

反剃刀

<h1>Counter</h1>
<p>Current count: @CounterService.CurrentCount</p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

@code {
    [Parameter]
    public CounterService CounterService { get; set; }

    private void IncrementCount()
    {
        CounterService.CurrentCount++;
    }
}

PageA.razor

@page "/pagea"
@inject ICounterServiceA CounterServiceA

<h3>Page A</h3>
<Counter CounterService="CounterServiceA" /> @*<-- Error: Cannot convert from ICounterServiceB to CounterService*@

PageB.razor

@page "/pageb"
@inject ICounterServiceB CounterServiceB

<h3>Page B</h3>
<Counter CounterService="CounterServiceB" /> @*<-- Error: Cannot convert from ICounterServiceB to CounterService*@

当我尝试将服务传递给组件时,我收到错误“无法从 ICounterServiceB 转换为 CounterService”。我发现使用 2 个指向相同具体实现的相同接口确实给了我 2 个作用域实例。但是,我无法弄清楚如何将这些实例传递给组件。

有没有我遗漏的部分?或者,这应该以其他方式完成吗?

解决方案

结合 Henk 的回答和 Brian 的评论,我想出的解决方案是:

CounterService.cs

namespace TestTwoInterfaceToOneService.Shared
{
    public class CounterService
    {
        public int CurrentCount { get; set; }
        public void IncrementCount()
        {
            CurrentCount++;
        }
        public void ResetCount()
        {
            CurrentCount = 0;
        }
    }
}

CounterStateService.cs

using System.Collections.Generic;
namespace TestTwoInterfaceToOneService.Shared
{
    public interface ICounterStateService
    {
        CounterService this[string key] { get; }
    }

    public class CounterStateService : ICounterStateService
    {
        private Dictionary<string, CounterService> counterServices = new();
        public CounterService this[string key]
        {
            get
            {
                if (!counterServices.ContainsKey(key))
                {
                    counterServices.Add(key, new CounterService());
                }
                return counterServices[key];
            }
        }
    }
}

在 Program.cs 中,添加

    builder.Services.AddScoped<ICounterStateService, CounterStateService>();

反剃刀

<p>Current count: @CounterService.CurrentCount</p>
<button class="btn btn-primary" @onclick="IncrementCount">Increment</button>

@code {
    [Parameter]
    public CounterService CounterService { get; set; }

    private void IncrementCount()
    {
        CounterService.CurrentCount++;
    }
}

PageA.razor

@page "/pagea"
@inject ICounterStateService CounterStateService

<h3>Page A</h3>
<Counter CounterService='CounterStateService["A"]' />
<button class="btn btn-primary" @onclick='CounterStateService["A"].ResetCount'>Reset Count</button>

PageB.razor

@page "/pageb"
@inject ICounterStateService CounterStateService

<h3>Page B</h3>
<Counter CounterService='CounterStateService["B"]' />
<button class="btn btn-primary" @onclick='CounterStateService["B"].ResetCount'>Reset Count</button>

标签: dependency-injectioncomponentsblazor

解决方案


或者,这应该以其他方式完成吗?

是的。将这种使用模式烘焙到 Type 结构中不会很好地适应或扩展。如果您需要第三页,或者想让它动态化怎么办?

您可以使用包装服务和带有键的方案将状态绑定到组件:

public class CounterService  { ... }    // no need to register

public class CounterStateService  // register for injection
{

    private Dictionary <string, CounterService> stateLookup = new();

    public CounterService  this[string key] 
    {
      if (! stateLookup.tryGetValue (key, out CounterService  service))
      {
         service = new CounterService();
         stateLookup.Add(key, service);
      }
      return service;
    }
}

并像使用它一样

@page "/pagea"
@inject CounterStateService  CounterStateService

<h3>Page A</h3>
<Counter CounterService="CounterStateService["A"]" />

推荐阅读