Search code examples
c#blazor-webassembly

Accessing blazor (wasm) injected objects in plain c# code as opposed to razor page


As an example the template generated code has

builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });

and this service is consumed here

@page "/fetchdata"
@inject HttpClient Http
...
protected override async Task OnInitializedAsync()
{
    forecasts = await Http.GetFromJsonAsync<WeatherForecast[]>("sample-data/weather.json");
}

Fine, but can I access that service from a plain c# file?

I tried hand typing what the @inject generates but this doesnt seem to work:-

[Inject]
HttpClient Http {get;set;};

EDIT - answer consolidated out of various comments and answers

When a dependency is instantiated by the DI system if its constructor has arguments that are classes or interfaces already registered then

  • those dependencies are themselves instantiated if not already done
  • the instances are passed to the constructor on the original dep

this chain must be initiated by an @inject / [inject] somewhere on a blazor page.

So to take my example. If I have some code in a class called say, DbIo, that needs the HttpClient. Must do

public class DbIo{
     HttpClient _http;
     // constructor effectively announcing the dependencies
     public DbIo(HttpClient _http){
         _http = http;
     }
}

add

 builder.Services.AddScoped<DbIo>();

to program.cs, and add (this is the not obvious bit)

 @inject DbIo DbService

to a blazor page (say App.razor). This forces the instantiation of a DbIo instance and hence the passing of the other services via its constructor.


Solution

  • This is the nature of Dependency Injection. If the root object is provided by the DI Container, then it can resolve all constructor parameters, assuming that they too are registered in the DI Container.

    Razor Components/Pages are provided by the Blazor framework and the Blazor framework takes care of resolving the Injected properties (assuming they have been registered in the DI container).

    builder.Services.AddScoped(sp => 
        new HttpClient 
        { 
            BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) 
        });
    

    ... adds HttpClient to the DI Container.

    When your FetchData page is provided by the Blazor Framework it will provide services marked with directive @inject. In this case ... HttpClient.

    Using a custom service ...

    If you want to use a service between your pages and HttpClient so that the page is not working directly with an HttpClient, then you'd need to register that service in the DI Container.

    builder.Services.AddScoped<MyCustomService>();
    

    Then, in your razor page, you can inject MyCustomService:

    @inject MyCustomService CustomService
    

    Now that MyCustomService is being provided by the DI Container, it can just request the HttpClient in it's Constructor parameters (as the HttpClient is also registered in the DI Container):

    public class MyCustomService
    {
        readonly HttpClient _httpClient;
        
        public MyCustomService(HttpClient httpClient)
        {
            _httpClient = httpClient;
        }
        
        public async Task ApiMethod()
        {
            await _httpClient.PostAsync( ..... );
        }
    }