Skip to content
kododo-devPublic

About

Runtime configuration editor for ASP.NET Core. View and modify IOptions<T> values at runtime through a built-in web UI without restarting the application.

Topics

Resources

Stars

4 stars

Watchers

0 watching

Forks

Latest commit

 

History

31 Commits

Folders and files

Repository files navigation

ConfigWay

CI Demo

Runtime configuration editor for ASP.NET Core. View and modify IOptions<T> values at runtime through a built-in web UI without restarting the application.

A live demo is available at kododo.dev/configway/demo.

UI

Overview

Field types

Array editor

Packages

Package NuGet Description
Kododo.ConfigWay NuGet Core DI registration and in-memory store
Kododo.ConfigWay.Core NuGet Abstractions and interfaces (for extension authors)
Kododo.ConfigWay.UI NuGet Embedded web UI
Kododo.ConfigWay.PostgreSQL NuGet PostgreSQL persistence store
Kododo.ConfigWay.SqlServer NuGet SQL Server persistence store

Quick start

dotnet add package Kododo.ConfigWay
dotnet add package Kododo.ConfigWay.UI
dotnet add package Kododo.ConfigWay.PostgreSQL  # or Kododo.ConfigWay.SqlServer — optional, for persistence
builder.AddConfigWay(x =>
{
    x.AddOptions<EmailOptions>();
    x.AddOptions<AppOptions>();
    x.AddUiEditor();
    x.UsePostgreSql(builder.Configuration.GetConnectionString("DefaultConnection")!);
    // or: x.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")!);
});

app.UseConfigWay(); // mounts UI at /config

Registering options

// Section name inferred from type — "Options" suffix stripped
x.AddOptions<EmailOptions>();   // -> "Email"

// Override explicitly
x.AddOptions<EmailOptions>("Mail");

Nested types are supported and rendered as collapsible subsections in the UI.

Supported field types

ConfigWay maps C# property types to dedicated UI controls automatically:

C# type UI control
string Text input
bool Toggle switch
int, long, double, decimal, ... Numeric input
enum Dropdown select
T[], List<T>, IList<T>, ... Collapsible array editor

Nullable variants (bool?, int?, etc.) are handled the same way.

Array types

Collection properties (T[], List<T>, IList<T>, IEnumerable<T>, IReadOnlyList<T>, ICollection<T>, IReadOnlyCollection<T>) are rendered as a collapsible array editor with add and remove buttons.

Simple arrays with scalar element types (string[], int[], Severity[], ...) show one input field per item:

public class WebhooksOptions
{
    [Display(Name = "Allowed Origins")]
    public string[] AllowedOrigins { get; set; } = [];
}

Complex arrays with class element types show a full sub-form per item, supporting nested objects and all scalar field types:

public class WebhookEndpoint
{
    public string Url    { get; set; } = string.Empty;
    public string Secret { get; set; } = string.Empty;
    public WebhookEvent Event { get; set; }
}

public class WebhooksOptions
{
    [Display(Name = "Endpoints")]
    public WebhookEndpoint[] Endpoints { get; set; } = [];
}

Items that come from lower configuration layers (appsettings.json, environment variables) are marked as non-deletable. They can be edited but not removed, because deleting them from the ConfigWay store would not suppress the underlying value.

Reset to default

Every field, section and array exposes a reset button that appears when the current value differs from the value in the underlying configuration layers (appsettings.json, environment variables, etc.). Resetting removes the ConfigWay-stored override so the original value from those lower layers takes effect again, without restarting the application.

Scope Behaviour
Field Removes the single key from the ConfigWay store. The reset button is shown only when the field differs from its base-config value.
Section Resets all fields and arrays inside the section recursively. Items added via ConfigWay are removed; non-deletable items are reset to their base-config values.
Array Items added via ConfigWay are removed. Non-deletable items (those that exist in lower config layers) have their values reset to the base-config value.

Pending resets are batched with any other edits and applied together when Save is pressed. Pressing Discard also discards pending resets.

Sensitive fields

Mark any string property with [DataType(DataType.Password)] from System.ComponentModel.DataAnnotations to treat it as a sensitive field:

using System.ComponentModel.DataAnnotations;

public class SmtpCredentials
{
    public string Username { get; set; } = string.Empty;

    [DataType(DataType.Password)]
    public string Password { get; set; } = string.Empty;
}

Sensitive fields behave as follows:

  • The input renders as a password field (*****). A placeholder indicates whether a value is currently stored.
  • The real value is never returned by the API. When a value is stored, the API returns "***". When nothing is stored, it returns null.
  • Submitting an empty value is treated as "no change". To remove a stored sensitive value, use the reset button.
  • The reset button appears when a sensitive value is stored. Resetting deletes the stored value and lets the underlying configuration layer (appsettings.json, environment variable) take effect again without a restart.
  • Sensitive values are masked in the UI and the API only. The store keeps them as plain text, so restrict access to the database accordingly.

Customizing UI labels and descriptions

Use [Display] from System.ComponentModel.DataAnnotations to control how options appear in the UI.

using System.ComponentModel.DataAnnotations;

[Display(Name = "E-mail", Description = "Outgoing mail server settings")]
public class EmailOptions
{
    [Display(Name = "SMTP Server", Description = "Hostname of the mail server, e.g. smtp.gmail.com")]
    public string SmtpServer { get; set; } = string.Empty;

    [Display(Name = "Sender address")]
    public string SenderEmail { get; set; } = string.Empty;

    [Display(Name = "Credentials")]
    public Credentials Credentials { get; set; } = new();
}

Name overrides the label shown next to the field or in the section header. When omitted, the property name is used as-is.

Description renders a small info icon next to the label. Hovering over it shows the description as a tooltip. Works on both fields and sections.

[Display] on enum members controls the label shown in the dropdown. The underlying member name is still used as the stored value.

public enum StorageProvider
{
    [Display(Name = "Amazon S3")]
    S3,

    [Display(Name = "Azure Blob Storage")]
    AzureBlob,

    [Display(Name = "Google Cloud Storage")]
    Gcs,
}

Validation

// Data annotations
builder.Services.AddOptions<EmailOptions>().ValidateDataAnnotations();

// Custom validator
builder.Services.AddSingleton<IValidateOptions<EmailOptions>, EmailOptionsValidator>();

Errors are shown in the UI and saving is blocked until they are resolved.

Security

The /config route is not protected by default:

app.UseConfigWay()
   .RequireAuthorization(policy => policy.RequireRole("Admin"));

Use IOptionsSnapshot<T> or IOptionsMonitor<T> in your code to always get the latest values after a reload.

Limitations

  • Changes are reloaded only in the instance that saved them. When the app runs as several replicas, the other replicas pick up the new values after a restart.
  • Saves do not check for concurrent edits. If two people edit the same section at the same time, the last save wins.

Custom store

You can replace the built-in in-memory store with any backend by implementing IStore from Kododo.ConfigWay.Core:

builder.AddConfigWay(x =>
{
    x.Store = new MyCustomStore(); // implements IStore
    x.AddOptions<AppOptions>();
});

License

MIT

About

Runtime configuration editor for ASP.NET Core. View and modify IOptions<T> values at runtime through a built-in web UI without restarting the application.

Topics

Resources

Stars

4 stars

Watchers

0 watching

Forks

Contributors

Languages