Skip to content
Shiny.NET
Commercial SupportGuaranteed response times on Shiny bug fixes, straight from the maintainers. Plans from US$1,500 a year.See the plans

Remote Configuration

NuGet package Shiny.Extensions.Configuration

The Remote Configuration provider fetches configuration from an HTTP endpoint and caches it in a local JSON file. This is useful for feature flags, A/B testing, or any scenario where you need server-driven configuration.

  1. The cached file is added as a JSON configuration source, so the last configuration the app received is available straight away - including offline.
  2. The remote endpoint is called. Its response must be a JSON object, and it is written over the cached file.
  3. The cached file is watched for changes, so the new values are reloaded into configuration once it is written.

A load is skipped when the last one finished less than 30 seconds earlier.

On iOS and Android, add the remote source to your IConfigurationBuilder (in MAUI, builder.Configuration):

builder.Configuration.AddRemote("https://myserver.com/config");

This caches the configuration as remotesettings.json in the app’s local data folder and fetches the remote configuration in the background, so app startup never waits on the network. To use a different cache file name:

builder.Configuration.AddRemote(
"https://myserver.com/config",
configurationFileName: "myconfig.json"
);

On every platform, including Windows, macOS, Linux and plain .NET, you can choose where the cache file lives:

builder.Configuration.AddRemoteWithCacheFile(
"https://myserver.com/config",
Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
"remotesettings.json"
),
waitForRemoteLoad: false
);

waitForRemoteLoad defaults to true, which makes building the configuration wait for the remote call to finish. Set it to false to fetch in the background, as the iOS & Android overload does.

If your server requires custom headers, authentication, or returns a non-standard format, you can supply a custom data retrieval function. It returns the configuration as a JsonNode - parse a response body with JsonNode.Parse, or turn a typed object into one with JsonSerializer.SerializeToNode(obj, MyJsonContext.Default.MyType), which stays trim and AOT safe:

builder.Configuration.AddRemote(
"https://myserver.com/config",
getData: async (remoteConfig, cancellationToken) =>
{
using var httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Add("X-Api-Key", "my-key");
var response = await httpClient.GetStringAsync(remoteConfig.Uri, cancellationToken);
return JsonNode.Parse(response)!;
}
);

remoteConfig is a RemoteConfig record with the endpoint (Uri), the configuration loaded so far (CurrentConfiguration, e.g. to read an API key), the cache file path (ConfigurationFilePath) and WaitForLoadOnStartup.

IRemoteConfigurationProvider extends IConfigurationProvider and adds:

public interface IRemoteConfigurationProvider : IConfigurationProvider
{
DateTimeOffset? LastLoaded { get; }
Task LoadAsync(CancellationToken cancellationToken = default);
}

Get it from the built configuration with GetRemoteConfigurationProvider(). It is not registered in the DI container for you; to inject it, register it yourself:

// MAUI: builder.Configuration is already a built configuration
builder.Services.AddSingleton(builder.Configuration.GetRemoteConfigurationProvider());
// anywhere else, from the IConfigurationRoot
var configuration = configurationBuilder.Build();
var remote = configuration.GetRemoteConfigurationProvider();

You can refresh the remote configuration at any time:

public class MyService(IRemoteConfigurationProvider remoteConfig)
{
public async Task RefreshConfig()
{
await remoteConfig.LoadAsync();
// the cache file is rewritten and reloaded; remoteConfig.LastLoaded is updated
}
}

Combine remote configuration with IOptionsMonitor<T> to react when the reloaded file changes your settings:

builder.Services.Configure<FeatureFlags>(builder.Configuration.GetSection("FeatureFlags"));
public class MyViewModel
{
public MyViewModel(IOptionsMonitor<FeatureFlags> options)
{
options.OnChange(flags =>
{
// React to remote config changes
});
}
}