Skip to content

[Blazor] Simplify writing code that works across different environments #60312

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
javiercn opened this issue Feb 11, 2025 · 10 comments
Open

[Blazor] Simplify writing code that works across different environments #60312

javiercn opened this issue Feb 11, 2025 · 10 comments
Labels
area-blazor Includes: Blazor, Razor Components
Milestone

Comments

@javiercn
Copy link
Member

Currently, when a component/service needs to run on the server and on webassembly, developers are forced to create at least two versions of the service, put each version in a different location depending on whether it runs on the server or on webassembly, and register things in the DI container twice.

This in many cases is hard to get right, as it requires several steps and it's easy to miss something.

As an alternative, we can consider bringing back multi-targeting support. With this approach, it should be possible for the app to define both implementations on the same location and use the same call to register the services in one go. We can update the template to support such pattern when webassembly is involved.

One such example is:

#if !NET10_WASM
public class WeatherForecastService(WeatherContext context)
#else
public class WeatherForecastService(HttpClient ctx)
#endif


public async Task<WeatherForecast []> GetForecastAsync(Date ...) 
{
#if !NET10_WASM
    return _httpClient.Getasync<...>...
#else
    return context.Forecasts.ToListAsync();
}
public static class ServiceRegistrationExtensions
{
    public static IServiceCollection AddCommonServices(this IServiceCollection services)
    {
        services.AddScoped<WeatherForecastService>();
    }
}
@ghost ghost added the area-blazor Includes: Blazor, Razor Components label Feb 11, 2025
@javiercn javiercn added this to the Backlog milestone Feb 11, 2025
@garrettlondon1
Copy link

This is a great initiative. Using pre-rendered WASM components cause a lot of trouble having to register the DI services on the server side.

The solution is to disable pre-rendering so that the DI services are only resolved on the client, but it creates for a really bad user experience not being able to provide loading states or pre-rendered content.

@hades200082
Copy link

Honestly I kinda hate this.

Using conditional compilation feels like something I have to do because Blazor has a flaw rather than being the right approach.

I'd rather see Blazor handle this in a more automated way.

  1. I don't want a wasm and a server project. I just want one project. Why can't Blazor manage the creation of the wasm part based on what I put in that one project?
  2. Let's say I build and register a service with an interface in the "server" project. Blazor has everything it needs to generate code. It could generate an api endpoint and the matching wasm service with httpclient automatically.

Make the "auto" rendering mode actually be auto.

@garrettlondon1
Copy link

garrettlondon1 commented Mar 26, 2025

I like the idea but it won't work in practice..

Blazor Server components can directly reference the data model, or infra projects, or the dbcontext.. giving WASM access to those class libraries would be catastrophic.. WASM projects should only reference DTO project and call endpoints

This issue is mainly for solving DI two times, i think

@Webreaper
Copy link

Webreaper commented Mar 26, 2025

I've done a couple of projects with dual injection classes, one for client side, one for server side. It's fine, but a bit laborious.

Typically you build your serverside services that do the real thing, and then for the Wasm you write very thin clientside services that are nothing but a proxy via controllers or minimal API wrappers, to call back to the server instance of the services and do the actual work. The proxies are pure boilerplate and add almost nothing.

Perhaps the right answer is to have some convention or attribute based automatic creation of all of that. So you'd write your serverside services, mark them up with an attribute like [WasmProxied(Interface=IMyService)] or something, and everything between them would be auto generated. So you'd get a minimal API wrapper, and a client side interface implementation, injected into the Di container for use in razor files, all for free with zero actual code having to be written.

@garrettlondon1
Copy link

garrettlondon1 commented Mar 26, 2025

https://github.com/reactiveui/refit

Refit interfaces already do most of that, I don't think thats Blazor responsibility

@Webreaper
Copy link

What do you mean "refit interface"?

I guess my point was that in @javiercn's OP, the implementation for wasm and server is #ifdef'd to separate them. What I'm saying is, go one step further and have them implicitly generated without needing any written code at all.

@nelak2
Copy link

nelak2 commented Mar 27, 2025

As a more specific example of where this issue is problematic is with Auth related code. If you have an Auth service for the client, it likely depends on browser storage for cookies, tokens etc, and also the http client for headers, tokens etc.

It's not really safe to implement impersonation of the client for the server side service, and a second Auth flow for the server side creates its own issues too. Nevermind handling that handover gracefully and whatever security vulnerabilities that whole mess introduced. So the correct approach seems to be to have a dummy implementation for the server side that does nothing and then the client side service does the actual authentication flow.

Auth is a very common requirement and the current approach makes it easy to write potentially insecure code. The fact that even setting the render mode from "Interactive Auto" to "Interactive Webassembly" which the documentation says is entirely client side doesn't disable the requirement adds to the confusion. Which in turn leads to people blindly registering the services on both sides so the project will run.

I understand that this is a skill or knowledge issue but making this behaviour and the requirements to correctly support it more explicit goes a long ways towards making the framework more approachable. So at the very least I'd suggest the documentation and templates should be updated.

@hades200082
Copy link

I like the idea but it won't work in practice..

Blazor Server components can directly reference the data model, or infra projects, or the dbcontext.. giving WASM access to those class libraries would be catastrophic.. WASM projects should only reference DTO project and call endpoints

This issue is mainly for solving DI two times, i think

@garrettlondon1 Why wouldn't SourceGenerators be a good fit for this?

As I and @Webreaper noted, if you have the interface and maybe some attributes you have everything you need to source-generate a minimal API endpoint that can use my server-side implementation to return the data, and a wasm-side implementation of the interface that will use the generated API endpoint to retrieve the data on the client ... no need for me to manually code that.

@rogihee
Copy link
Contributor

rogihee commented Mar 27, 2025

Perhaps the right answer is to have some convention or attribute based automatic creation of all of that. So you'd write your serverside services, mark them up with an attribute like [WasmProxied(Interface=IMyService)] or something, and everything between them would be auto generated. So you'd get a minimal API wrapper, and a client side interface implementation, injected into the Di container for use in razor files, all for free with zero actual code having to be written.

I have written 2 source generators, 1 for the client and 1 for the server for exactly this use case. The WASM generates Proxy version with http client, the server one a controller implementation. On the server side we then have a handwritten Service that implements the actual logic of the API call. Works like a charm.

For the services bit we ended up with a convention of having "VoidServices" for pre-rendering of client side services. Just empty service implementations.

I'm not really a fan of multi-targeting, unless it is hard to make a mistake and leak server side assembly references to the client. Having different projects (and I use solution folders) makes it clear where an assembly is accessible from, if it's all in one assembly it is more difficult to see this.

@hades200082
Copy link

@rogihee that sounds good.

It would also need to account for some sort of authentication on the api endpoints. Or at least the possibility of them requiring authentication.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-blazor Includes: Blazor, Razor Components
Projects
None yet
Development

No branches or pull requests

6 participants