Skip to content

Review Startup APIs for Blazor WASM #16874

@mkArtakMSFT

Description

@mkArtakMSFT

Goals

  • Reduce complexity/ceremony of startup/main code for Blazor WASM
  • Address gaps in current scenarios (no way to get services before UI is shown)
  • non-goal symmetry or consistency with server-side ASP.NET Core

Sample Code

These are some focused samples with my proposed implementation choice. There's another section with alternatives and why I didn't choose them. I think there are a lot of reasonable small tweaks to this, but I wanted to lead with something.

Adding configuration to the mix opens a few questions which need further discussion, so it's a good thing we're injecting it at this point.

Simple

This is the code that we expect to appear in every template.

async static Task Main(string[] args)
{
    var builder = WebAssemblyHostBuilder.CreateDefault(args);
    builder.RootComponents.Add<App>("app");

    await builder.Build().RunAsync();
}

Adding custom initialization

This adds some custom initialization after components are registered and after services are available, but before the UI is shown.

async static Task Main(string[] args)
{
    var builder = WebAssemblyHostBuilder.CreateDefault(args);
    builder.Services.AddSingleton<WeatherService>();
    builder.RootComponents.Add<App>("app");

    var host = builder.Build();

    var weatherService = host.Services.GetRequiredService<WeatherService>();
    await weatherService.InitializeAsync();

    await host.RunAsync();
}

Adding configuration

This adds configuration, which the user can use in their initialization code.

async static Task MainWithConfig(string[] args)
{
    var builder = WebAssemblyHostBuilder.CreateDefault(args);
    builder.Services.AddSingleton<WeatherService>();
    builder.RootComponents.Add<App>("app");

    var host = builder.Build();

    var weatherService = host.Services.GetRequiredService<WeatherService>();
    await weatherService.InitializeAsync(host.Configuration["WeatherUrl"]);

    await host.RunAsync();
}

Open questions

  • Should we rename one of the two .Services properties for clarity? One is IServiceCollection, the other is IServiceProvider. I like the idea of this in general, but I haven't come up with a pair of names that feel good.
  • What's the cost (in size) of including IConfiguration and IConfigurationBuilder?

Options and inflection points

StartAsync/StopAsync vs RunAsync

The ASP.NET Core hosting stuff has start/stop in addition to run. It's a source of confusion, start/stop are a lot more subtle than they seem, so I didn't plan to add them here.

Configuration as part of WebAssemblyHostBuilder

One of the negatives of putting configuration here is that it won't/can't be linked out without a look of gymnastics. I think it's worth it though from a design point of view.

You could also imagine an alternative where we don't tie ourselves to configuration at all (nowhere on our API surface). The guidance for users could be to add it to DI themselves.

ComponentMappingCollection vs methods on the WebAssemblyHostBuilder

We discussed this idea last time, so I wanted to include it so we can compare. I like the property a little bit better.

Disposing the host

NOTE: host.RunAsync() will dispose the host if/when RunAsync terminates. This is a little bit bizarre, but it's what generic host does. Hosts are single-use. It's not clear in the WASM how that RunAsync will terminate, at least not in this release.

Apis

namespace Microsoft.??????
{
    public sealed class WebAssemblyHostBuilder
    {
        public static WebAssemblyHostBuilder CreateDefault(string[] args) => throw null;

        public WebAssemblyEnvironment Environment { get; }

        public IServiceCollection Services { get; }

        public IConfigurationBuilder Configuration { get; }

        public ComponentMappingCollection RootComponents { get; }

        public WebAssemblyHost Build() => throw null;
    }

    public sealed class WebAssemblyEnvironment
    {
        public string BaseUri { get; }
    }

    public sealed class WebAssemblyHost : IDisposable
    {
        public IConfiguration Configuration { get; }

        public IServiceProvider Services { get; }

        public void Dispose()
        {
            throw new NotImplementedException();
        }

        public Task RunAsync() => throw null;
    }

    public sealed class ComponentMappingCollection : Collection<ComponentMapping>
    {
        public void Add<TComponent>(string selector) where TComponent : IComponent => throw null;
        public void Add(string selector, Type componentType) => throw null;
    }

    public readonly struct ComponentMapping
    {
        public ComponentMapping(string selector, Type componentType)
        {
            Selector = selector;
            ComponentType = componentType;
        }

        public string Selector { get; }

        public Type ComponentType { get; }
    }
}

Feedback to consider

Metadata

Metadata

Assignees

Labels

Components Big RockThis issue tracks a big effort which can span multiple issuesDoneThis issue has been fixedStatus: Resolvedarea-blazorIncludes: Blazor, Razor Componentsfeature-blazor-wasmThis issue is related to and / or impacts Blazor WebAssemblytask

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions