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 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;
}
}
}
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?
To add to @Mister Magoo's answer about injecting transient/scoped services into singletons. I just tried injecting HttpClient into a singleton and got a runtime error when I then tried to inject the singleton.
Unhandled exception rendering component: Cannot consume scoped service 'System.Net.Http.HttpClient' from singleton 'IComp'.
builder.Services.AddHttpClient(), actually registers IHttpClientFactory as a service. So you should be injecting that, then create clients when you need them. This may be your issue, but I couldn't actually reproduce your outcome.
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");
}
}