0
votes

I use to have a common service in MVC Application , register it as Transient service and access its value across whole application without any issue .

I tried to implement same mechanism inside my client side blazor app

First created a class AppState

public class AppState
{
    public string BaseUrl { get; set; }
}

registered as a service

services.AddSingleton<AppState, AppState>();

Used in Blazor Component Index Component

public class IndexComponent : ComponentBase
{
    [Inject]
    HttpClient Http { get; set; }

    [Inject]
    public AppState AppState { get; set; }

    protected override async Task OnInitializedAsync()
    {
        AppState = await Http.GetJsonAsync<AppState>(ConfigFiles.GetPath("appsettings.json"));
        await Task.FromResult(0);
    }
}

Tried to print base url in index.razor file

@page "/"
@inherits IndexComponent

<p>@AppState.BaseUrl</p>

Till Here its fine , now as it contain base url so i wanted to access it in another component

public class MiniCartComponent : ComponentBase
{
    [Inject]
    public AppState AppState { get; set; }

    protected override async Task OnInitializedAsync()
    {
        await Task.FromResult(0);
    }
}

Here it is empty , i dont know why

I tried to print it in razor file

@inherits MiniCartComponent
<p>@AppState.BaseUrl</p>

Here it is empty , it is registered as a service to share data across components , should not it have value across the app once it is set ??

1
Change AppState = await Http.GetJsonAsync<AppState>(ConfigFiles.GetPath("appsettings.json")); to var appState = await Http.GetJsonAsync<AppState>(ConfigFiles.GetPath("appsettings.json")); AppState.BaseUrl = appState.BaseUrl; - Phat Huynh
Ok , it worked , what is the concept behind it . In future there will be many keys in json files , and i have to set them one by one in this way - Saurabh
Just modify AppState, don't set it by other value. If has many field, just wrap it to other class. - Phat Huynh
Ok , but how it is working after AppState.BaseUrl = appState.BaseUrl; , Can you clarify it - Saurabh
You register AppState as singleton, I called it instance A, when you inject to your component it still instance A and you set AppState in you component with result from API it will be instance B, no effect to instance A. - Phat Huynh

1 Answers

4
votes

1st you register a singleton instance

services.AddSingleton<AppState, AppState>();

this look a bit weird to me. IMHO, it should be

services.AddSingleton<IAppState, AppState>();

or

services.AddSingleton<AppState>();

But that's not the probleme.

You inject this service in a component whicht is great

[Inject]
public AppState AppState { get; set; }

And you erase this component instance with a new one get from a web api

protected override async Task OnInitializedAsync()
{
    AppState = await Http.GetJsonAsync<AppState>(ConfigFiles.GetPath("appsettings.json"));
    await Task.FromResult(0);
}

Their again the code is weird, it should be

protected override async Task OnInitializedAsync()
{
    AppState = await Http.GetJsonAsync<AppState>(ConfigFiles.GetPath("appsettings.json"));
}

But that doesn't reset the singleton instance you inject in your second component. That just replace the instance in your 1st component.

  • You should register your service with
services.AddSingleton<AppState>(provider =>
{
    var http = provider.GetRequiredService<HttpClient>();
    return http.GetJsonAsync<AppState>(ConfigFiles.GetPath("appsettings.json")).ConfigureAwait(false).GetAwaiter().GetResult();
});

This way you make just one call to appsettings.json during the app life.

  • And remove OnInitializedAsync code.

Async

But actually, the best way to achieve that is to register a Task returning your settings

services.AddSingleton(provider =>
{
    var http = provider.GetRequiredService<HttpClient>();
    return http.GetJsonAsync<AppState>("appsettings.json");
});

You can inject it in your components with

[Inject]
public Task<AppState> AppStateAsync { get; set; }

public AppState AppState { get; set; }

protected override async Task OnInitializedAsync()
{
    AppState = await AppStateAsync;
}

And use it with

@page "/"
@inherits IndexComponent

<p>@AppState.BaseUrl</p>