smapiot / Piral.Blazor

All .NET things to make Blazor work seamlessly in microfrontends using Piral. :jigsaw:
https://piral.io
MIT License
55 stars 17 forks source link

Support `appsettings.*.json` and pass bound `IConfiguration` to `Module.ConfigureServices()` #98

Closed tillr closed 1 year ago

tillr commented 2 years ago

New Feature Proposal

For more information, see the CONTRIBUTING guide.

Description

Blazor WASM provides a way of providing environment-specific client-side configuration by placing appsettings.*.json files into the wwwroot folder. The active environment can then be set using, for example, a startup configuration as outlined here.

It would be helpful if appsettings.*.json files could be used for client-side app configuration within a Blazor pilet to populate an IConfiguration instance, and if that instance would then be passed to the Module.ConfigureServices method as outlined in the minimal example below.

appsettings.json:

{
  "Api": {
    "BaseUrl": "https://my.api.com/"
  }
}

ApiOptions.cs:

public class ApiOptions
{
  public string? BaseUrl { get; set; }
}

Module.cs:

[...]
  public static void ConfigureServices(IServiceCollection services, IConfiguration configuration)
  {
    services.AddOptions();

    services.Configure<ApiOptions>(options => configuration.Bind("Api", options));
    // or
    services.Configure<ApiOptions>(configuration.GetSection("Api"));
  }
[...]

Background

As of now, IConfiguration is not bound. This leaves the pilet author with hard-coding necessary configuration values within the ConfigureServices method and also neglects the need for environment-specific configuration within pilets.

FlorianRappl commented 2 years ago

Where should the appsettings.json live? Should it be in the app shell or in pilets?

My proposal is to support the IConfiguration via app shell configuration. Now in your app shell you can hand over something to the initialization of piral-blazor, which determines the key / values. It's then up to you to either read some JSON or gather the configuration from different sources. Would that work?

tillr commented 2 years ago

While it probably isn't the most intuitive way for Blazor WASM developers, who are used to employ appsettings*.json files for that purpose, I can think of various use cases which may require the provision of pilet configuration via the app shell anyway. So yes, your proposed way is fine.

FlorianRappl commented 2 years ago

Well, I mean what is "intuitive". If you work in a distributed environment then suddenly such old habits are required to change, so I'd argue not everything that was once established still makes sense in this new context.

The big issue with a local appsettings.json file is that a pilet should be environment independent. As such you just uploaded one static file - i.e., it won't change. Now if you do that the whole point of the appsettings.json is rendered useless, as you usually would want to change / adopt it to the target environment. However, there is already such a mechanism in the standard feed service; its configuration. It can be made per pilet per environment and it is gathered by the app shell. Additionally, the app shell can anyway be environment dependent, making the usual appsettings.json rather an app shell construct than a pilet construct.

Now with the given proposal I think whatever the user wants to go for works. It would even work having an appsettings.json per pilet, which is then consumed by the app shell and distributed to the pilets accordingly. But the important part is that IConfiguration would work.

tillr commented 2 years ago

That's fine, go for it. I am not sure if I can follow you on this though:

However, there is already such a mechanism in the standard feed service; its configuration. It can be made per pilet per environment and it is gathered by the app shell.

In a Piral.Blazor pilet, how would I access that configuration where I need it (in Module.cs)? That bit seems to be missing at the moment. So either way you choose in your implementation, there should be a way to get that configuration into IConfiguration and use it when setting up DI.

FlorianRappl commented 2 years ago

In a Piral.Blazor pilet, how would I access that configuration where I need it (in Module.cs)?

You access it via the IConfiguration instance. The thing is that the app shell would deliver any kind of global config and put it in the initial config passed to createBlazorApi, i.e., when setting up Piral.Blazor. With this information (and the local one provided to the pilet directly) the IConfiguration would be populated appropriately, consisting of global (i.e., available to all Blazor pilets) and local (i.e., only available to the current Blazor pilet) infos.

tillr commented 2 years ago

Sounds good!

FlorianRappl commented 1 year ago

Alright this is working, e.g.,

public class Module
{
    public static void ConfigureServices(IServiceCollection services, IConfiguration configuration)
    {
        Console.WriteLine("Received configuration is: {0} - {1}", configuration.GetValue<string>("foo"), configuration.GetValue<int>("someThingElse:c"));

        services.AddScoped<ExampleJsInterop>();
    }
}

Note that the second argument (of type IConfiguration) is optional. You can just omit it (as beforehand) and it would still work.

prepfarm commented 11 months ago

Hi @FlorianRappl, I'm unable to get anything from appsettings inside of the Blazor Pilet. I tried moving around appsettings from wwwroot to root of the project, specifying Config in .csproj, setting copy to output folder, etc. but IConfiguration is always empty. Any help? Also not sure how to define (global) settings that will come to pilet from app shell? I don't see option for that when adding createBlazorApi

FlorianRappl commented 11 months ago

This is all fully documented @prepfarm, see: https://github.com/smapiot/Piral.Blazor#overwriting-the-debug-meta-data

As the configuration is distributed the IConfiguration is drawn from the given options to the pilet. For local debugging you can (as documented) use the meta.json file.

Hope that helps!