[StrawberryShake] AddScopedXClient
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);
}
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
I have a draft PR that builds on top of this and adds the hydrate functionality and pre-rendering support: https://github.com/repne/hotchocolate/pull/1 Let me know if I'm on the right track and I will get it ready.
Hey, we will soon redo StrawberryShake to cater to the newer capabilities in Blazor ... If you want you can join the effort.
@michaelstaib does it make sense that I pick this up again? This is step one to enable server side pre-rendering in Blazor.