Skip to content
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

[StrawberryShake] AddScopedXClient #7878

Open
wants to merge 3 commits into
base: main
Choose a base branch
from

Conversation

repne
Copy link
Contributor

@repne repne commented Dec 29, 2024

Summary

Adds a new method to the service collection extension to register the client as scoped as opposed to singleton.

Why?
This is so a client generated for a Blazor WebAssembly project can be shared with the server hosting it. Sharing it as opposed to use a newly generated client with NoStore. This way a developer can reuse components and services between client side rendering and server side prerending.
Initially I tried to get a PR for having a client generated with two modes, one for the WebAssembly client (noStore = false) and one for the server side Aspnet (noStore = true). However this proved too difficult because data types were generated differently and there was a lot of mapping involved (entityIds vs entities) so sharing the components was not easy.
With this new approach there's the side effect that entityIds are also generated on the server (unlike the noStore = true approach) and so cache hydration from a prerendered page is easier to accomplish.

A followup PR will implement prerendering, stream rendering, and cache hydration of the Blazor components, I already have a working PoC.

This is what the new method look like with all the namespaces removed:

public static IScopedClientBuilder<FooClientStoreAccessor> AddScopedFooClient(this IServiceCollection services, ExecutionStrategy strategy = ExecutionStrategy.NetworkOnly, global::System.Action<IServiceCollection>? configureClientServices = null)
{
    ServiceCollectionServiceExtensions.AddScoped(services, sp =>
    {
        var serviceCollection = new ServiceCollection();
        ConfigureClientDefault(sp, serviceCollection, strategy);
        if (configureClientServices is not null)
        {
            configureClientServices(serviceCollection);
        }

        return new ClientServiceProvider(ServiceCollectionContainerBuilderExtensions.BuildServiceProvider(serviceCollection));
    });
    ServiceCollectionServiceExtensions.AddScoped(services, sp => new FooClientStoreAccessor(ServiceProviderServiceExtensions.GetRequiredService<IOperationStore>(ServiceProviderServiceExtensions.GetRequiredService<ClientServiceProvider>(sp)), ServiceProviderServiceExtensions.GetRequiredService<IEntityStore>(ServiceProviderServiceExtensions.GetRequiredService<ClientServiceProvider>(sp)), ServiceProviderServiceExtensions.GetRequiredService<IEntityIdSerializer>(ServiceProviderServiceExtensions.GetRequiredService<ClientServiceProvider>(sp)), ServiceProviderServiceExtensions.GetRequiredService<global::System.Collections.Generic.IEnumerable<IOperationRequestFactory>>(ServiceProviderServiceExtensions.GetRequiredService<ClientServiceProvider>(sp)), ServiceProviderServiceExtensions.GetRequiredService<global::System.Collections.Generic.IEnumerable<IOperationResultDataFactory>>(ServiceProviderServiceExtensions.GetRequiredService<ClientServiceProvider>(sp))));
    ServiceCollectionServiceExtensions.AddScoped(services, sp => ServiceProviderServiceExtensions.GetRequiredService<GetPersonQuery>(ServiceProviderServiceExtensions.GetRequiredService<ClientServiceProvider>(sp)));
    ServiceCollectionServiceExtensions.AddScoped(services, sp => ServiceProviderServiceExtensions.GetRequiredService<OnPersonSubscription>(ServiceProviderServiceExtensions.GetRequiredService<ClientServiceProvider>(sp)));
    ServiceCollectionServiceExtensions.AddScoped(services, sp => ServiceProviderServiceExtensions.GetRequiredService<CreatePersonMutation>(ServiceProviderServiceExtensions.GetRequiredService<ClientServiceProvider>(sp)));
    ServiceCollectionServiceExtensions.AddScoped(services, sp => ServiceProviderServiceExtensions.GetRequiredService<FooClient>(ServiceProviderServiceExtensions.GetRequiredService<ClientServiceProvider>(sp)));
    ServiceCollectionServiceExtensions.AddScoped(services, sp => ServiceProviderServiceExtensions.GetRequiredService<IFooClient>(ServiceProviderServiceExtensions.GetRequiredService<ClientServiceProvider>(sp)));
    return new ScopedClientBuilder<FooClientStoreAccessor>("FooClient", services);
}

@repne repne marked this pull request as ready for review December 29, 2024 18:39
@repne
Copy link
Contributor Author

repne commented Dec 30, 2024

This is a proof of concept on how to implement cache hydration, stream rendering, and prerendering:
https://gist.github.com/repne/2646d29fff4c959a91bc7de8986b2882

In order to do this, the same strawberry client needs to be used on both server and client Blazor projects.

This can be tested using the following project:

dotnet new blazor -e -int Auto -ai -o MyProject.Web

@repne
Copy link
Contributor Author

repne commented Dec 31, 2024

I have a draft PR that builds on top of this and adds the hydrate functionality and pre-rendering support: repne#1
Let me know if I'm on the right track and I will get it ready.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant