注入的单例服务在 Blazor 组件中是不同的 [.NET 6]

Injected singleton service is different across Blazor components [.NET 6]

在 Blazor 6.0 WASM Web 客户端上

IOC 容器似乎正在返回我的单例服务的不同实例,NodeService。我通过在 NodeService 构造函数中生成一个随机数,然后从使用该服务的不同 类 检查该随机数的值得出了这个结论。

Program.cs

using BlazorDraggableDemo;
using BlazorDraggableDemo.Factories;
using BlazorDraggableDemo.Services;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;

var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app");
builder.RootComponents.Add<HeadOutlet>("head::after");



builder.Services.AddSingleton<MouseService>();
builder.Services.AddSingleton<IMouseService>(ff => ff.GetRequiredService<MouseService>());
builder.Services.AddSingleton<INodeService, NodeService>();
builder.Services.AddSingleton<INodeServiceRequestMessageFactory, NodeServiceRequestMessageFactory>();
builder.Services.AddHttpClient<INodeService, NodeService>(client =>
{
    client.BaseAddress = new Uri("http://localhost:7071");
});



await builder.Build().RunAsync();

Playground.razor

@inject MouseService mouseSrv;
@inject INodeService nodeService;

<div class="row mt-2">
    <div class="col">
        <button @onclick="AddNode">Add Node</button>
        <button @onclick="SaveNodes">Save</button>
        <button @onclick="AddConnector">Add Connector</button>
        <svg class="bg-light" width="100%" height="500" xmlns="http://www.w3.org/2000/svg"
            @onmousemove=@(e => mouseSrv.FireMove(this, e))
            @onmouseup=@(e => mouseSrv.FireUp(this, e))
            @onmouseleave=@(e => mouseSrv.FireLeave(this, e))>

            @foreach(var node in nodes)
            {
                <Draggable Circle=@node>
                <circle r="15" fill="#04dcff" stroke="#fff" />
                </Draggable>
            }
            @foreach(var connector in connectors)
            {
                <ConnectorComponent Line=connector />
            }
        </svg>
    </div>
</div>

@code {
    public List<Node>? nodes;
    public List<Connector>? connectors;
    int serviceIntance = 0;
    protected override async Task OnInitializedAsync()
    {
        nodes = new List<Node>();
        connectors = new List<Connector>();
        
        try
        {
            await nodeService.LoadNodes();
            nodes = nodeService.GetNodes();
            connectors = nodeService.GetConnectors();
            serviceIntance = nodeService.getInstance();
        }
        catch (Exception ex)
        {
            Console.Error.WriteLine(ex.Message);
        }

        Console.WriteLine("Got Stuff?");
    }

    public async Task SaveNodes()
    {
        await nodeService.SaveNodes();
    }

    private async Task AddNode()
    {
        var lastShape = nodes.LastOrDefault();
        double x = lastShape != null ? lastShape.XCoord + 15 : 0;
        double y = lastShape != null ? lastShape.YCoord : 0;
        await nodeService.CreateNode(x, y, "nodes");
    }

    private async Task AddConnector()
    {
        var startnode = nodes[0];
        var endNode = nodes[1];
        await nodeService.AddConnector(startnode, endNode);
        Console.WriteLine("We Here");
    }
}

ConnectorComponent.razor

@inject INodeService nodeService;

<path d="M @startNode.XCoord @startNode.XCoord C @Line.StartBezierXCoord @Line.StartBezierYCoord, @Line.EndBezierXCoord @Line.EndBezierYCoord, @endNode.XCoord @endNode.YCoord" stroke="rgb(108, 117, 125)" stroke-width="1.5" fill="transparent" style="pointer-events:none !important;" />

@code {
    [Parameter] public Connector Line  { get; set; }
    public Node startNode;
    public Node endNode;
    int serviceInstance;

    protected override void OnParametersSet() {
        var nodes = nodeService.GetNodes();
        serviceInstance = nodeService.getInstance();
        startNode = nodes.First(node => node.Id.Equals(Line.StartNodeId));
        endNode = nodes.First(node => node.Id.Equals(Line.EndNodeId));
        base.OnParametersSet();
    }
}

NodeService.cs

using BlazorDraggableDemo.Models;
using Microsoft.AspNetCore.Components.Web;
using System.Net.Http.Json;
using System.Net.Http;
using BlazorDraggableDemo.Factories;
using BlazorDraggableDemo.DTOs;
using System.Text.Json;

namespace BlazorDraggableDemo.Services
{
    public interface INodeService
    {
        public Task LoadNodes();
        public List<Node> GetNodes();
        public Task SaveNodes();
        public Task AddConnector(Node startNode, Node endNode);
        public void SaveConnectors();
        public Task CreateNode(double xCoord, double yCoord, string solutionId);
        public List<Connector> GetConnectors();
        public int getInstance();
    }

    public class NodeService : INodeService
    {
        private readonly HttpClient _httpClient;
        private readonly INodeServiceRequestMessageFactory _nodeServiceRequestMessageFactory;
        private readonly int instance;
        public NodeService(HttpClient httpClient, INodeServiceRequestMessageFactory nodeServiceRequestMessageFactory)
        {
            _httpClient = httpClient;
            _nodeServiceRequestMessageFactory = nodeServiceRequestMessageFactory;
            var rand = new Random();
            instance = rand.Next(0, 100);
        }
        public List<Node> Nodes = new List<Node>();
        public List<Connector> Connectors = new List<Connector>();

        public async Task LoadNodes()
        {
            try
            {
                var nodes = await _httpClient.GetFromJsonAsync<List<Node>>("api/getnodes");
                if (nodes != null)
                {
                    Nodes = nodes;
                }
            }
            catch (Exception ex)
            {
                Console.Error.WriteLine(ex.Message);
            }
        }

        public List<Node> GetNodes()
        {
            return Nodes;
        }

        public async Task SaveNodes()
        {
            try
            {
                var response = await _httpClient.PostAsJsonAsync<UpsertNodesRequestMessage>("api/upsertNodes", new UpsertNodesRequestMessage()
                {
                    Nodes = Nodes.ToList()
                });
            }
            catch (Exception ex)
            {
                Console.Error.WriteLine(ex.Message);
            }
        }

        public async Task AddConnector(Node startNode, Node endNode)
        {
            try
            {
                var response = await _httpClient.PostAsJsonAsync("api/AddConnector", new AddConnectorRequestMessage()
                {
                    StartNode = startNode,
                    EndNode = endNode
                });
                var responseMessage = await response.Content.ReadAsStringAsync();
                var connector = JsonSerializer.Deserialize<Connector>(responseMessage);
                Connectors.Add(connector);
            }
            catch (Exception ex)
            {
                Console.Error.WriteLine(ex.Message);
            }
        }
        public void SaveConnectors()
        {

        }

        public List<Connector> GetConnectors()
        {
            return Connectors;
        }
        public async Task CreateNode(double xCoord, double yCoord, string solutionId)
        {
            try
            {
                var response = await _httpClient.PostAsJsonAsync<CreateNodeRequestMessage>("api/CreateNode", new CreateNodeRequestMessage()
                {
                    XCoord = xCoord,
                    YCoord = yCoord,
                    SolutionId = solutionId
                });
                var responseMessage = await response.Content.ReadAsStringAsync();
                var node = JsonSerializer.Deserialize<Node>(responseMessage);
                Nodes.Add(node);
                
            }
            catch (Exception ex)
            {
                Console.Error.WriteLine(ex.Message);
            }
        }

        public int getInstance()
        {
            return instance;
        }
    }
}

当我从 ComponentA 检查 nodeService.instance 的值时,它出现在 84 当我检查 ComponentB 的值时,它出现在 12。我对单例的理解是单例服务的单个实例应该跨越用户的应用程序实例。从任一组件引用时 nodeService.instance 的值不应该相同吗?

不确定您为什么会遇到这种情况。我确实得到了相同的价值。如果您需要我将回购推送到 GitHub 让我知道。

类型化客户端注册为瞬态服务(see docs)。

您正在注册您的类型两次 - 一次作为 Singleton 并且(通过 AddHttpClient)作为 Transient。

当您 inject 您的服务时,您将获得 Transient Typed Client,而不是 Singleton 实例。

您可以通过列出您的服务的注册来看到这一点,其中将显示一个 Singleton 和一个 Transient。

foreach (var item in builder
  .Services
  .Where(
    service => service.ServiceType.Equals( typeof( INodeService ) ) 
  ) 
 )
{
    Console.WriteLine($"Service: {item.ServiceType.Name} as {item.Lifetime}");
}

添加@Mister Magoo 关于将 transient/scoped 服务注入单例的回答。我刚刚尝试将 HttpClient 注入单例,然后在我尝试注入单例时出现运行时错误。

Unhandled exception rendering component: Cannot consume scoped service 'System.Net.Http.HttpClient' from singleton 'IComp'.

builder.Services.AddHttpClient(),实际上是将IHttpClientFactory注册为服务。所以你应该注入它,然后在需要时创建客户端。这可能是您的问题,但我实际上无法重现您的结果。

public interface IComp
{
    int num { get; set; }
}
public class myComp : IComp
{
    public int num { get; set; }

    private readonly IHttpClientFactory _clientFactory;
    public myComp(IHttpClientFactory clientFactory)
    {
        _clientFactory = clientFactory;
        var rand = new Random();
        num = rand.Next(0, 100);
    }

    public async Task<string> GetSomething()
    {
        // edit, removed the using on client
        var client = _clientFactory.CreateClient();
        return await client.GetStringAsync("http://some-url");
    }
}