You are(were?) on the completely wrong way ;)
....NET seems to take care of it when using AddSingleton...
You seem(ed?) to lack fundamental understanding of dependency injection. It is arguably the next step in object oriented programming. Simplified, the idea is, that the new() keyword is kind of evil. new Car() ??? Cars aren't transformers, they don't build themselves. When thinking in objects to represent reality, this isn't right. A CarFactory would be more accurate. But you don't want to implement a factory for every object. How to solve this dilemma? -> The concept of Dependency Injection. Simplified, it's one big object factory. Or a bit like Amazon, you tell it what you want and then you get it.
...but not sure how to correctly instantiate the StateService manually in the Program.cs file...
That's the neat part - you don't.
"Just" tell it what you want in Program.cs:
builder.Services.AddScoped<IStateService, StateService>();
builder.Services.AddHttpClient<ITestService, TestService>(httpclient =>
{
httpclient.BaseAddress = new Uri("https://www.google.com/");
});
(I made StateService scoped, because it holds no actual state information in a variable, you are just using it to access data)
As Stephen Cleary linked, the .AddHttpClient here is called a Typed HttpClient. Whenever you use NEW HttpClient instead of something like above, you are doing something dangerous. And I mean beyond the object-building-itself thing. For HttpClient specifically the new keyowrd is evil. Let the framework handle HttpClient, so that connections are being handled efficiently and nothing is left open, otherwise you might get those infamous socket exhaustion issues and general performance problems.
There are many way to inject dependencies into a class, here is one.
public class TestService(HttpClient HttpClient, IStateService StateService) : ITestService
{
public async Task<string> DoStuff()
{
HttpResponseMessage response = await HttpClient.GetAsync("search?q=hellogoogle&thisCallDoesntWork=copyYourOwnSearchStuff");
if (response.IsSuccessStatusCode)
{
string content = await response.Content.ReadAsStringAsync();
return content;
}
else
{
return string.Empty;
}
}
}
In your StateService, you are using another kind of dependency injection, but it effectively does the same: Getting a service from the predefined ones in Program.cs
private IJSRuntime jsRuntime;
public StateService(IJSRuntime _jsRuntime)
{
jsRuntime = _jsRuntime;
}
Injection in the UI file looks a bit different with @inject. Here in the Counter.razor of the HelloWorld Blazor:
@page "/counter"
@rendermode InteractiveServer
@inject ITestService TestService // Dependency injection
<PageTitle>Counter</PageTitle>
<h1>Counter</h1>
<p role="status">Current count: @currentCount</p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
@code {
private int currentCount = 0;
private async Task IncrementCount()
{
string apiCallResult = await TestService.DoStuff(); // Using our injected service
currentCount++;
}
}
Please note that I do not want to pass stateService itself as a parameter to test service because Test Service is completely decoupled from the playform it is running on, therefore it is not aware of how or where the token comes from, only that it has a token to work with.
This problem you describe is kind of why dependency injection was invented (improving object orientation is just a nice side effect). In my setup TestService has no StateService-parameter, it gets a IStateService INTERFACE injected:
builder.Services.AddScoped<IStateService, StateService>();
There is no hard coupling. You can completely change the implementation of StateService and simply adjust the Program.cs code, the TestService will never notice the difference, its code does not need to be touched one bit:
builder.Services.AddScoped<IStateService, CompletelyDifferentStateService>();
It is fine that TestService has a dependency on IStateService, the reality is, it DOES access it and that is fine. But it has no dependency on StateService, the concrete implementation is decoupled.
It probably makes more sense, if you think of my made up class "IUserProvider" with a GetUsers() method. What is the implementation?
UsersApi.cs, calling a server?
TestUserJsonReader.cs reading users from a file?
UsersGenerator.cs, making up random new users out of some hardcoded data?
Any class using IUserProvider via Dependency Injection doesn't care, it is well decoupled. You can even change it dynamically, if IsTestEnvironment -> inject a different implementation.
(I know I'm a bit late to the party)