Skip to content

Server-side Blazor Scoped injection is very unhelpful #61174

@masonwheeler

Description

@masonwheeler

Summary

Scoped injection of services should operate per-session rather than per "request."

Motivation and goals

In a classic website, it makes sense to have scoped DI inject a new service per HTTP request. HTTP is a stateless protocol, so the implementation matches the reality of the way it's used.

This is not the case in Blazor. A single-page application is not conceptually a web request; it's conceptually an application that happens to be running in a browser, and applications are not stateless. When using server-side rendering, scoped DI injects new instances of services you've used previously at unexpected times, resulting in errors because your application state does not stay where you left it.

This occurs because scoped DI is still scoped per-request, which may be correct on a very pedantic technical level, but is entirely unhelpful since this isn't the use case it was designed for. For Blazor, there needs to be a way to inject services per-session, that will remain around for the entire usage of the SPA, either by modifying the scoped DI implementation or by adding a new option.

In scope

If Blazor code injects a scoped service, the entire SPA should see that same object instance forever, until the user leaves/reloads the SPA or the server shuts down, no matter how many different "requests" and navigation events occur along the way.

Out of scope

No effort needs to be made to make this application state persist between multiple sessions by the same user. A reload is still a reload.

Risks / unknowns

If existing code depends on the current behavior in some way, it could cause unexpected consequences. So adding an explicit session-scoped option might be the better course of action.

Examples

This all arose out of a Discord discussion of a crash bug in my project. I have a service that's loading some data from the database. It's being called in the layout, and if I'm opening a page that also wants data from the database, I get:

InvalidOperationException: A second operation was started on this context instance before a previous operation completed. This is usually caused by different threads concurrently using the same instance of DbContext.

And due to the design of Blazor, throwing this exception brings down the entire SPA.

This occurs no matter what efforts are made to get this data only once and persist it in memory, because the memory in question keeps getting replaced out from under me. This needs to stop happening. I'm building an application, and application state needs to stay where the developer left it.

Some people in chat suggested various solutions involving caching user data per-user, but this is not a persuasive suggestion.

  • Users can leave, and then you have stale data bloating up the cache.
    • Setting up timed cache eviction policies can bring you right back to where you started, with application state vanishing out from under you, if the user steps away from the SPA for a few minutes. There's a reason this is called one of the Hard Problems Of Computer Science.
  • Even if invalidation wasn't an issue, now every service that deals with application state becomes significantly more complex as it has to interact with the cache for everything, rather than just keeping it inside its own object.

What is needed is proper scoping that actually works with the SPA model.

Metadata

Metadata

Assignees

No one assigned

    Labels

    ✔️ Resolution: AnsweredResolved because the question asked by the original author has been answered.Status: Resolvedarea-blazorIncludes: Blazor, Razor Componentsdesign-proposalThis issue represents a design proposal for a different issue, linked in the descriptionquestion

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions