Do API controllers use a different instance of a service than Blazor components?
The answer is definitely yes for scoped services, no for singletons, but it gets complicated. But I suspect that's not your real question, and the real question is how to store user-specific data for both. Or modify a specific user's data from an external service
Why the services are different
A new controller instance is created to handle each HTTP request. The HTTP request defines a scope as far as DI is concerned, so a new service instance is created for each HTTP request and dispose after the request is processed.
This is A Good Thing, as it means expensive scoped services like a DbContext are disposed even if an error occurs. In the case of a DbContext, this give transaction-per-request semantics out of the box, without requiring any extra code.
Things are more complicated with Blazor Server. In this case, Blazor Server defines a scope per "user circuit" which is essentially a single tab. This means that scoped objects remain active as long the tab is active when the navigation happens on the client. The MVC part behaves the same way it did with ASP.NET Core MVC/Web API
This is similar to how a desktop application behaves and can cause nasty surprises when people assume that scoped services like a DbContest will be disposed "automagically". When you call SaveChangesAsync you may end up persisting changes you thought were discarded.
So even if you try to use a scoped service in Blazor Server, you may end up with a long-lived service in the Blazor components and a short-lived service in the Web API controllers.
In fact, since different scopes are used, the two services can't even find each other.
How DI and Scope work in Blazor Server
This is documented in ASP.NET Core Blazor dependency injection. The difference in a scope's lifetime is explained in Service Lifetime :
The Blazor Server hosting model supports the Scoped lifetime across HTTP requests but not across SignalR connection/circuit messages among components that are loaded on the client.
The Razor Pages or MVC portion of the app treats scoped services normally and recreates the services on each HTTP request when navigating among pages or views or from a page or view to a component.
Scoped services aren't reconstructed when navigating among components on the client, where the communication to the server takes place over the SignalR connection of the user's circuit, not via HTTP requests.
In the following component scenarios on the client, scoped services are reconstructed because a new circuit is created for the user:
- The user closes the browser's window. The user opens a new window and navigates back to the app.
- The user closes the last tab of the app in a browser window. The user opens a new tab and navigates back to the app.
- The user selects the browser's reload/refresh button.
Notifying a user's tabs after an external call
From the comments :
I have an Azure queue serverless trigger. Inside that function I parse a file and I want to send the status to my blazor project. Once it gets in the API controller, i want to update the UI.
That's tricky. In this case the Web API is essentially acting as another user. The Blazor Server tabs need to be updated in response to what is essentially an external event.
Since it's Blazor Server though, assuming no load balancing is used, all the user circuits are running in the same process that handles API requests. One could raise an "event" in the API controller and have the Blazor components listen to it.
Luckily, that's exactly what libraries like Blazor.EventAggregator have implemented.
The Event Aggregator service is a singleton, registered with :
public void ConfigureServices(IServiceCollection services)
{
services.AddEventAggregator();
}
Assuming the message only accepts a file name and a status:
public record FileEvent(string file,string status);
The Web API Controller will act as a publisher :
class MyController:ControllerBase
{
private IEventAggregator _eventAggregator { get; set; }
public MyController(private IEventAggregator eventAggregator)
{
_eventAggregator=eventAggregator;
}
...
[HttpPost]
[AllowAnonymous]
public async Task<ActionResult> Post(BlobInfo blobInfo)
{
await _eventAggregator.PublishAsync(new FileEvent(blogInfo.BlobName,"OK");
return Ok();
}
}
The Blazor Server components that care can inject IEventAggregator, listen for events, specify the events they care about and handle them.
In each component's code-behind, the service can be injected with :
[Inject]
private IEventAggregator _eventAggregator { get; set; }
The class also needs to implement the IHandler<T> interface for the events it cares about :
public class MyComponent : ComponentBase, IHandle<FileEvent>
{
...
List<string> _messages=new List<string>;
public async Task HandleAsync(FileEvent message)
{
_messages.Add($"{message.Name} worked");
await InvokeAsync(StateHasChanged());
}
...
}
The Razor code doesn't really need to change :
@foreach(var value in _messages)
{
<p>@value</p>
}