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

Time:02-25

On Blazor 6.0 WASM Webclient

It appears that the IOC container is returning different instances of my singleton service, NodeService. I have come to this conclusion by generating a random number in the NodeService constructor, and then checking the value of that random number from different classes that use the service.

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 >
    <div >
        <button @onclick="AddNode">Add Node</button>
        <button @onclick="SaveNodes">Save</button>
        <button @onclick="AddConnector">Add Connector</button>
        <svg  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;
        }
    }
}

When I check the value of nodeService.instance from ComponentA, it comes in at 84 When I check the value from ComponentB, it comes in at 12. My understanding of singletons is that a single instance of singleton service should be across the user's instance of the application. Shouldn't the value of nodeService.instance be the same when referenced from either component?

CodePudding user response:

Not sure why you're experiencing that. I do get the same value. If you need me to push the repo to GitHub let me know.

enter image description here

CodePudding user response:

Typed Clients are registered as Transient services (see docs).

You are registering your type twice - once as Singleton and (via AddHttpClient) as Transient.

When you inject your service, you will get the Transient Typed Client, not the Singleton instance.

You can see this by listing out the registrations for your service, which will show one Singleton and one Transient.

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