Reputation: 195
In a Blazor application, I want to use a component from two different pages. The component uses a service to maintain its state. But each page needs to have the component use a different state. Here's how I thought I would do it (using the default Counter component to demonstrate).
CounterService.cs
namespace TestTwoInterfaceToOneService.Shared
{
public interface ICounterServiceA
{
int CurrentCount { get; set; }
void IncrementCount();
}
public interface ICounterServiceB
{
int CurrentCount { get; set; }
void IncrementCount();
}
public class CounterService : ICounterServiceA, ICounterServiceB
{
public int CurrentCount { get; set; }
public void IncrementCount()
{
CurrentCount++;
}
}
}
In Program.cs add:
builder.Services.AddScoped<ICounterServiceA, CounterService>();
builder.Services.AddScoped<ICounterServiceB, CounterService>();
Counter.razor
<h1>Counter</h1>
<p>Current count: @CounterService.CurrentCount</p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
@code {
[Parameter]
public CounterService CounterService { get; set; }
private void IncrementCount()
{
CounterService.CurrentCount++;
}
}
PageA.razor
@page "/pagea"
@inject ICounterServiceA CounterServiceA
<h3>Page A</h3>
<Counter CounterService="CounterServiceA" /> @*<-- Error: Cannot convert from ICounterServiceB to CounterService*@
PageB.razor
@page "/pageb"
@inject ICounterServiceB CounterServiceB
<h3>Page B</h3>
<Counter CounterService="CounterServiceB" /> @*<-- Error: Cannot convert from ICounterServiceB to CounterService*@
I get the error 'Cannot convert from ICounterServiceB to CounterService' when I try to pass the service to the component. I've discovered that using 2 identical interfaces pointing to the same concrete implementation does indeed give me 2 scoped instances. However, I cannot figure out how to pass those instances into the component.
Is there a piece that I'm missing? Or, should this be done some other way?
SOLUTION
Using a combination of the answer from Henk and the comments from Brian, the solution I came up with is:
CounterService.cs
namespace TestTwoInterfaceToOneService.Shared
{
public class CounterService
{
public int CurrentCount { get; set; }
public void IncrementCount()
{
CurrentCount++;
}
public void ResetCount()
{
CurrentCount = 0;
}
}
}
CounterStateService.cs
using System.Collections.Generic;
namespace TestTwoInterfaceToOneService.Shared
{
public interface ICounterStateService
{
CounterService this[string key] { get; }
}
public class CounterStateService : ICounterStateService
{
private Dictionary<string, CounterService> counterServices = new();
public CounterService this[string key]
{
get
{
if (!counterServices.ContainsKey(key))
{
counterServices.Add(key, new CounterService());
}
return counterServices[key];
}
}
}
}
In Program.cs, add
builder.Services.AddScoped<ICounterStateService, CounterStateService>();
Counter.razor
<p>Current count: @CounterService.CurrentCount</p>
<button class="btn btn-primary" @onclick="IncrementCount">Increment</button>
@code {
[Parameter]
public CounterService CounterService { get; set; }
private void IncrementCount()
{
CounterService.CurrentCount++;
}
}
PageA.razor
@page "/pagea"
@inject ICounterStateService CounterStateService
<h3>Page A</h3>
<Counter CounterService='CounterStateService["A"]' />
<button class="btn btn-primary" @onclick='CounterStateService["A"].ResetCount'>Reset Count</button>
PageB.razor
@page "/pageb"
@inject ICounterStateService CounterStateService
<h3>Page B</h3>
<Counter CounterService='CounterStateService["B"]' />
<button class="btn btn-primary" @onclick='CounterStateService["B"].ResetCount'>Reset Count</button>
Upvotes: 0
Views: 1112
Reputation: 273494
Or, should this be done some other way?
Yes. Baking this usage pattern into the Type structure isn't going to adapt or scale well. What if you need a third page, or want to make it dynamic?
You could use a wrapper service and a scheme with a key to bind a State to a Component:
public class CounterService { ... } // no need to register
public class CounterStateService // register for injection
{
private Dictionary <string, CounterService> stateLookup = new();
public CounterService this[string key]
{
if (! stateLookup.tryGetValue (key, out CounterService service))
{
service = new CounterService();
stateLookup.Add(key, service);
}
return service;
}
}
and use it like
@page "/pagea"
@inject CounterStateService CounterStateService
<h3>Page A</h3>
<Counter CounterService="CounterStateService["A"]" />
Upvotes: 1