diff --git a/docs/docs/v4/welcome/migrate-quick-steps.md b/docs/docs/v4/welcome/migrate-quick-steps.md
new file mode 100644
index 00000000..b24c3be4
--- /dev/null
+++ b/docs/docs/v4/welcome/migrate-quick-steps.md
@@ -0,0 +1,1329 @@
+# Migrating from Steeltoe 3
+
+This topic provides quick steps to migrate existing applications to Steeltoe 4.
+For non-trivial cases, see the related documentation topic and samples for v4.
+
+> [!TIP]
+> For detailed information on what has changed, see [What's new in Steeltoe 4](./whats-new.md).
+
+## Bootstrap
+
+For additional information, see the updated [Bootstrap documentation](../bootstrap/index.md).
+
+Project file:
+
+```diff
+
+
+-
++
+
+
+```
+
+Program.cs:
+
+```diff
+-using Steeltoe.Bootstrap.Autoconfig;
++using Steeltoe.Bootstrap.AutoConfiguration;
+
+var builder = WebApplication.CreateBuilder(args);
+builder.AddSteeltoe();
+```
+
+## CircuitBreaker
+
+CircuitBreaker (a .NET port of Netflix Hystrix) has been removed from Steeltoe in v4.
+Use [Polly](https://github.com/App-vNext/Polly) instead.
+
+## Configuration
+
+For additional information, see the updated [Configuration documentation](../configuration/index.md) and
+[Configuration samples](https://github.com/SteeltoeOSS/Samples/tree/main/Configuration).
+
+### Cloud Foundry
+
+Project file:
+
+```diff
+
+
+-
++
+
+
+```
+
+#### Load `VCAP_SERVICES`/`VCAP_APPLICATION` into `IConfiguration`
+
+Program.cs:
+
+```diff
+-using Steeltoe.Extensions.Configuration.CloudFoundry;
++using Steeltoe.Configuration.CloudFoundry;
+
+var builder = WebApplication.CreateBuilder(args);
+builder.AddCloudFoundryConfiguration();
+
+Console.WriteLine($"Application name: {builder.Configuration["vcap:application:application_name"]}");
+
+foreach (var section in builder.Configuration.GetRequiredSection("vcap:services").GetChildren())
+{
+ var plans = string.Join(", ", section
+ .GetChildren()
+ .SelectMany(child => child.GetChildren())
+ .Where(child => child.Key == "plan")
+ .Select(child => child.Value));
+ Console.WriteLine($"Service: {section.Key} with plans: {plans}");
+}
+```
+
+#### Load `VCAP_SERVICES`/`VCAP_APPLICATION` into `OptionsMonitor`
+
+Program.cs:
+
+```diff
+using Microsoft.Extensions.Options;
+-using Steeltoe.Extensions.Configuration.CloudFoundry;
++using Steeltoe.Configuration.CloudFoundry;
+
+var builder = WebApplication.CreateBuilder(args);
+builder.AddCloudFoundryConfiguration();
+-builder.Services.ConfigureCloudFoundryOptions(builder.Configuration);
+
+var app = builder.Build();
+
+var appMonitor = app.Services.GetRequiredService>();
+Console.WriteLine($"Application name: {appMonitor.CurrentValue.ApplicationName}");
+
+var servicesMonitor = app.Services.GetRequiredService>();
+foreach (var services in servicesMonitor.CurrentValue.Services)
+{
+ var plans = string.Join(", ", services.Value.Select(service => service.Plan));
+ Console.WriteLine($"Service: {services.Key} with plans: {plans}");
+}
+```
+
+### Config Server
+
+Project file:
+
+```diff
+
+
+-
++
+
+
+```
+
+Program.cs:
+
+```diff
+-using Steeltoe.Extensions.Configuration.ConfigServer;
++using Steeltoe.Configuration.ConfigServer;
+
+var builder = WebApplication.CreateBuilder(args);
+builder.AddConfigServer();
+```
+
+### Kubernetes
+
+Direct interaction with the Kubernetes API has been removed from Steeltoe in v4.
+
+### Placeholder
+
+Project file:
+
+```diff
+
+
+-
++
+
+
+```
+
+Program.cs:
+
+```diff
+-using Steeltoe.Extensions.Configuration.Placeholder;
++using Steeltoe.Configuration.Placeholder;
+
+var builder = WebApplication.CreateBuilder(args);
+-builder.AddPlaceholderResolver();
++builder.Configuration.AddPlaceholderResolver();
+```
+
+### Random Value
+
+Project file:
+
+```diff
+
+
+-
++
+
+
+```
+
+Program.cs:
+
+```diff
+-using Steeltoe.Extensions.Configuration.RandomValue;
++using Steeltoe.Configuration.RandomValue;
+
+var builder = WebApplication.CreateBuilder(args);
+builder.Configuration.AddRandomValueSource();
+```
+
+### Spring Boot
+
+Project file:
+
+```diff
+
+
+-
++
+
+
+```
+
+Program.cs:
+
+```diff
+-using Steeltoe.Extensions.Configuration.SpringBoot;
++using Steeltoe.Configuration.SpringBoot;
+
+var builder = WebApplication.CreateBuilder(args);
+-builder.AddSpringBootConfiguration();
++builder.Configuration.AddSpringBootFromCommandLine(args);
++builder.Configuration.AddSpringBootFromEnvironmentVariable();
+```
+
+## Connectors
+
+For additional information, see the updated [Connectors documentation](../configuration/index.md) and
+[Configuration samples](https://github.com/SteeltoeOSS/Samples/tree/main/Connectors).
+
+> [!IMPORTANT]
+> The configuration structure for Connectors has changed in Steeltoe 4. Always use the `ConnectionString` property instead of `Host`, `Port`, `Username`, `Password`, etc.
+> Replace the key `Default` with the name of the service binding if you have multiple.
+
+### MySQL using ADO.NET
+
+Project file:
+
+```diff
+
+
+
+-
++
+
+
+```
+
+appsettings.json:
+
+```diff
+{
+- "$schema": "https://steeltoe.io/schema/v3/schema.json",
++ "$schema": "https://steeltoe.io/schema/v4/schema.json",
+- "MySql": {
+- "Client": {
+- "ConnectionString": "Server=localhost;Database=steeltoe;Uid=steeltoe;Pwd=steeltoe"
+- }
+- }
++ "Steeltoe": {
++ "Client": {
++ "MySql": {
++ "Default": {
++ "ConnectionString": "Server=localhost;Database=steeltoe;Uid=steeltoe;Pwd=steeltoe"
++ }
++ }
++ }
++ }
+}
+```
+
+Program.cs:
+
+```diff
+using MySql.Data.MySqlClient;
+-using Steeltoe.Connector.MySql;
++using Steeltoe.Connectors;
++using Steeltoe.Connectors.MySql;
+
+var builder = WebApplication.CreateBuilder(args);
+-builder.Services.AddMySqlConnection(builder.Configuration);
++builder.AddMySql();
+
+var app = builder.Build();
+
+-await using var scope = app.Services.CreateAsyncScope();
+-await using var connection = scope.ServiceProvider.GetRequiredService();
++var factory = app.Services.GetRequiredService>();
++var connector = factory.Get();
++Console.WriteLine($"Using connection string: {connector.Options.ConnectionString}");
++await using var connection = connector.GetConnection();
+
+await connection.OpenAsync();
+await using var command = connection.CreateCommand();
+command.CommandText = "SELECT 1";
+var result = await command.ExecuteScalarAsync();
+Console.WriteLine($"Query returned: {result}");
+```
+
+### MySQL using Entity Framework Core
+
+Project file:
+
+```diff
+
+
+
+-
+-
++
+
+
+```
+
+appsettings.json:
+
+```diff
+{
+- "$schema": "https://steeltoe.io/schema/v3/schema.json",
++ "$schema": "https://steeltoe.io/schema/v4/schema.json",
+- "MySql": {
+- "Client": {
+- "ConnectionString": "Server=localhost;Database=steeltoe;Uid=steeltoe;Pwd=steeltoe"
+- }
+- }
++ "Steeltoe": {
++ "Client": {
++ "MySql": {
++ "Default": {
++ "ConnectionString": "Server=localhost;Database=steeltoe;Uid=steeltoe;Pwd=steeltoe"
++ }
++ }
++ }
++ }
+}
+```
+
+Program.cs:
+
+```diff
+using Microsoft.EntityFrameworkCore;
+-using Steeltoe.Connector.MySql;
++using Steeltoe.Connectors.MySql;
+-using Steeltoe.Connector.MySql.EFCore;
++using Steeltoe.Connectors.EntityFrameworkCore.MySql;
+
+var builder = WebApplication.CreateBuilder(args);
+-builder.Services.AddDbContext(options => options.UseMySql(builder.Configuration));
+-builder.Services.AddMySqlHealthContributor(builder.Configuration);
++builder.AddMySql();
++builder.Services.AddDbContext((serviceProvider, options) => options.UseMySql(serviceProvider));
+
+var app = builder.Build();
+
+await using var scope = app.Services.CreateAsyncScope();
+await using var dbContext = scope.ServiceProvider.GetRequiredService();
+var rowCount = await dbContext.ExampleEntities.CountAsync();
+Console.WriteLine($"Found {rowCount} rows.");
+```
+
+### PostgreSQL using ADO.NET
+
+Project file:
+
+```diff
+
+
+
+-
++
+
+
+```
+
+appsettings.json:
+
+```diff
+{
+- "$schema": "https://steeltoe.io/schema/v3/schema.json",
++ "$schema": "https://steeltoe.io/schema/v4/schema.json",
+- "Postgres": {
+- "Client": {
+- "ConnectionString": "Server=localhost;Database=steeltoe;Uid=steeltoe;Pwd=steeltoe"
+- }
+- }
++ "Steeltoe": {
++ "Client": {
++ "PostgreSql": {
++ "Default": {
++ "ConnectionString": "Server=localhost;Database=steeltoe;Uid=steeltoe;Pwd=steeltoe"
++ }
++ }
++ }
++ }
+}
+```
+
+Program.cs:
+
+```diff
+using Npgsql;
+-using Steeltoe.Connector.PostgreSql;
++using Steeltoe.Connectors;
++using Steeltoe.Connectors.PostgreSql;
+
+var builder = WebApplication.CreateBuilder(args);
+-builder.Services.AddPostgresConnection(builder.Configuration);
++builder.AddPostgreSql();
+
+var app = builder.Build();
+
+-await using var scope = app.Services.CreateAsyncScope();
+-await using var connection = scope.ServiceProvider.GetRequiredService();
++var factory = app.Services.GetRequiredService>();
++var connector = factory.Get();
++Console.WriteLine($"Using connection string: {connector.Options.ConnectionString}");
++await using var connection = connector.GetConnection();
+
+await connection.OpenAsync();
+await using var command = connection.CreateCommand();
+command.CommandText = "SELECT 1";
+var result = await command.ExecuteScalarAsync();
+Console.WriteLine($"Query returned: {result}");
+```
+
+### PostgreSQL using Entity Framework Core
+
+Project file:
+
+```diff
+
+
+
+-
+-
++
+
+
+```
+
+appsettings.json:
+
+```diff
+{
+- "$schema": "https://steeltoe.io/schema/v3/schema.json",
++ "$schema": "https://steeltoe.io/schema/v4/schema.json",
+- "Postgres": {
+- "Client": {
+- "ConnectionString": "Server=localhost;Database=steeltoe;Uid=steeltoe;Pwd=steeltoe"
+- }
+- }
++ "Steeltoe": {
++ "Client": {
++ "PostgreSql": {
++ "Default": {
++ "ConnectionString": "Server=localhost;Database=steeltoe;Uid=steeltoe;Pwd=steeltoe"
++ }
++ }
++ }
++ }
+}
+```
+
+Program.cs:
+
+```diff
+using Microsoft.EntityFrameworkCore;
+-using Steeltoe.Connector.PostgreSql;
++using Steeltoe.Connectors.PostgreSql;
+-using Steeltoe.Connector.PostgreSql.EFCore;
++using Steeltoe.Connectors.EntityFrameworkCore.PostgreSql;
+
+var builder = WebApplication.CreateBuilder(args);
+-builder.Services.AddDbContext(options => options.UseNpgsql(builder.Configuration));
+-builder.Services.AddPostgresHealthContributor(builder.Configuration);
++builder.AddPostgreSql();
++builder.Services.AddDbContext((serviceProvider, options) => options.UseNpgsql(serviceProvider));
+
+var app = builder.Build();
+
+await using var scope = app.Services.CreateAsyncScope();
+await using var dbContext = scope.ServiceProvider.GetRequiredService();
+var rowCount = await dbContext.ExampleEntities.CountAsync();
+Console.WriteLine($"Found {rowCount} rows.");
+```
+
+### RabbitMQ
+
+Project file:
+
+```diff
+
+
+
+-
++
+
+
+```
+
+appsettings.json:
+
+```diff
+{
+- "$schema": "https://steeltoe.io/schema/v3/schema.json",
++ "$schema": "https://steeltoe.io/schema/v4/schema.json",
+- "Rabbitmq": {
+- "Client": {
+- "Uri": "amqp://guest:guest@127.0.0.1/"
+- }
+- }
++ "Steeltoe": {
++ "Client": {
++ "RabbitMQ": {
++ "Default": {
++ "ConnectionString": "amqp://localhost:5672"
++ }
++ }
++ }
++ }
+}
+```
+
+> [!TIP]
+> See the RabbitMQ documentation [here](https://www.rabbitmq.com/docs/uri-spec) and [here](https://www.rabbitmq.com/docs/uri-query-parameters) for the `ConnectionString` URI format.
+
+Program.cs:
+
+```diff
+using RabbitMQ.Client;
+-using Steeltoe.Connector.RabbitMQ;
++using Steeltoe.Connectors;
++using Steeltoe.Connectors.RabbitMQ;
+using System.Text;
+
+var builder = WebApplication.CreateBuilder(args);
+-builder.Services.AddRabbitMQConnection(builder.Configuration, ServiceLifetime.Singleton);
++builder.AddRabbitMQ();
+
+var app = builder.Build();
+
+-var connectionFactory = app.Services.GetRequiredService();
+-var connection = await connectionFactory.CreateConnectionAsync(); // long-lived, do not dispose
++var factory = app.Services.GetRequiredService>();
++var connector = factory.Get();
++Console.WriteLine($"Using connection string: {connector.Options.ConnectionString}");
++var connection = connector.GetConnection(); // long-lived, do not dispose
+await using var channel = await connection.CreateChannelAsync();
+const string queueName = "example-queue-name";
+await channel.QueueDeclareAsync(queueName);
+
+byte[] messageToSend = "example-message"u8.ToArray();
+await channel.BasicPublishAsync(exchange: "", queueName, mandatory: true, new BasicProperties(), messageToSend);
+
+var result = await channel.BasicGetAsync(queueName, autoAck: true);
+string messageReceived = result == null ? "(none)" : Encoding.UTF8.GetString(result.Body.ToArray());
+Console.WriteLine($"Received message: {messageReceived}");
+```
+
+### Redis/Valkey
+
+Project file:
+
+```diff
+
+
+
+-
++
+
+
+```
+
+appsettings.json:
+
+```diff
+{
+- "$schema": "https://steeltoe.io/schema/v3/schema.json",
++ "$schema": "https://steeltoe.io/schema/v4/schema.json",
+- "Redis": {
+- "Client": {
+- "ConnectionString": "localhost:6379"
+- }
+- }
++ "Steeltoe": {
++ "Client": {
++ "Redis": {
++ "Default": {
++ "ConnectionString": "localhost"
++ }
++ }
++ }
++ }
+}
+```
+
+Program.cs:
+
+```diff
+using Microsoft.Extensions.Caching.Distributed;
+-using Steeltoe.Connector.Redis;
++using Steeltoe.Connectors;
++using Steeltoe.Connectors.Redis;
+
+var builder = WebApplication.CreateBuilder(args);
+-builder.Services.AddDistributedRedisCache(builder.Configuration);
++builder.AddRedis();
+
+var app = builder.Build();
+
+-var cache = app.Services.GetRequiredService();
++var factory = app.Services.GetRequiredService>();
++var connector = factory.Get();
++Console.WriteLine($"Using connection string: {connector.Options.ConnectionString}");
++var cache = connector.GetConnection();
+await cache.SetAsync("example-key", "example-value"u8.ToArray());
+var value = await cache.GetStringAsync("example-key");
+Console.WriteLine($"Received value: {value}");
+```
+
+## Discovery
+
+For additional information, see the updated [Discovery documentation](../discovery/index.md) and
+[Discovery samples](https://github.com/SteeltoeOSS/Samples/tree/main/Discovery).
+
+### Eureka
+
+#### Register your service
+
+Project file:
+
+```diff
+
+
+-
++
+
+
+```
+
+appsettings.json:
+
+```diff
+{
+- "$schema": "https://steeltoe.io/schema/v3/schema.json",
++ "$schema": "https://steeltoe.io/schema/v4/schema.json",
+ "Spring": {
+ "Application": {
+ "Name": "example-service"
+ }
+ },
+ "Eureka": {
+ "Client": {
+ "ShouldRegisterWithEureka": true,
+ "ShouldFetchRegistry": false
+ }
+ }
+}
+```
+
+launchSettings.json:
+
+```diff
+{
+ "profiles": {
+ "http": {
+ "commandName": "Project",
+ "applicationUrl": "http://+:5005" // bind to all host names and IP addresses
+ }
+ }
+}
+```
+
+Program.cs:
+
+```diff
+-using Steeltoe.Discovery.Client;
++using Steeltoe.Discovery.Eureka;
+
+var builder = WebApplication.CreateBuilder(args);
+-builder.Services.AddDiscoveryClient();
++builder.Services.AddEurekaDiscoveryClient();
+
+var app = builder.Build();
+
+app.MapGet("/ping", async httpContext =>
+{
+ httpContext.Response.StatusCode = 200;
+ httpContext.Response.ContentType = "text/plain";
+ await httpContext.Response.WriteAsync("pong");
+});
+```
+
+#### Lookup other services
+
+Project file:
+
+```diff
+
+
+-
++
++
+
+
+```
+
+appsettings.json:
+
+```diff
+{
+- "$schema": "https://steeltoe.io/schema/v3/schema.json",
++ "$schema": "https://steeltoe.io/schema/v4/schema.json",
+ "Spring": {
+ "Application": {
+ "Name": "example-service"
+ }
+ },
+ "Eureka": {
+ "Client": {
+ "ShouldRegisterWithEureka": false,
+ "ShouldFetchRegistry": true
+ }
+ }
+}
+```
+
+Program.cs:
+
+```diff
+-using Steeltoe.Common.Http.Discovery;
+-using Steeltoe.Discovery.Client;
++using Steeltoe.Discovery.Eureka;
++using Steeltoe.Discovery.HttpClients;
+
+var builder = WebApplication.CreateBuilder(args);
+-builder.Services.AddDiscoveryClient();
++builder.Services.AddEurekaDiscoveryClient();
+builder.Services
+ .AddHttpClient(httpClient => httpClient.BaseAddress = new Uri("http://example-service/"))
+ .AddServiceDiscovery();
+
+var app = builder.Build();
+
+var pingClient = app.Services.GetRequiredService();
+string response = await pingClient.GetPingAsync();
+Console.WriteLine($"Response: {response}");
+
+public class PingClient(HttpClient httpClient)
+{
+ public async Task GetPingAsync()
+ {
+ return await httpClient.GetStringAsync("ping");
+ }
+}
+```
+
+### Consul
+
+#### Register your service
+
+Project file:
+
+```diff
+
+
+-
++
+
+
+```
+
+appsettings.json:
+
+```diff
+{
+- "$schema": "https://steeltoe.io/schema/v3/schema.json",
++ "$schema": "https://steeltoe.io/schema/v4/schema.json",
+ "Spring": {
+ "Application": {
+ "Name": "example-service"
+ }
+ },
+ "Consul": {
+ "Discovery": {
+ "Register": true
+ }
+ }
+}
+```
+
+launchSettings.json:
+
+```diff
+{
+ "profiles": {
+ "http": {
+ "commandName": "Project",
+ "applicationUrl": "http://+:5005" // bind to all host names and IP addresses
+ }
+ }
+}
+```
+
+Program.cs:
+
+```diff
+-using Steeltoe.Discovery.Client;
++using Steeltoe.Discovery.Consul;
+
+var builder = WebApplication.CreateBuilder(args);
+-builder.Services.AddDiscoveryClient();
+builder.Services.AddConsulDiscoveryClient();
+
+var app = builder.Build();
+
+app.MapGet("/ping", async httpContext =>
+{
+ httpContext.Response.StatusCode = 200;
+ httpContext.Response.ContentType = "text/plain";
+ await httpContext.Response.WriteAsync("pong");
+});
+```
+
+#### Lookup other services
+
+Project file:
+
+```diff
+
+
+-
++
++
+
+
+```
+
+appsettings.json:
+
+```diff
+{
+- "$schema": "https://steeltoe.io/schema/v3/schema.json",
++ "$schema": "https://steeltoe.io/schema/v4/schema.json",
+ "Consul": {
+ "Discovery": {
+ "Register": false
+ }
+ }
+}
+```
+
+Program.cs:
+
+```diff
+-using Steeltoe.Common.Http.Discovery;
+-using Steeltoe.Discovery.Client;
++using Steeltoe.Discovery.Consul;
++using Steeltoe.Discovery.HttpClients;
+
+var builder = WebApplication.CreateBuilder(args);
+-builder.Services.AddDiscoveryClient();
++builder.Services.AddConsulDiscoveryClient();
+builder.Services
+ .AddHttpClient(httpClient => httpClient.BaseAddress = new Uri("http://example-service/"))
+ .AddServiceDiscovery();
+
+var app = builder.Build();
+
+var pingClient = app.Services.GetRequiredService();
+string response = await pingClient.GetPingAsync();
+Console.WriteLine($"Response: {response}");
+
+public class PingClient(HttpClient httpClient)
+{
+ public async Task GetPingAsync()
+ {
+ return await httpClient.GetStringAsync("ping");
+ }
+}
+```
+
+## Integration
+
+Integration (lightweight messaging for Spring-based applications) has been removed from Steeltoe in v4.
+
+## Logging
+
+For additional information, see the updated [Logging documentation](../logging/index.md).
+
+### Dynamic Console
+
+Project file:
+
+```diff
+
+
+-
++
+
+
+```
+
+Program.cs:
+
+```diff
+-using Steeltoe.Extensions.Logging;
++using Steeltoe.Logging;
++using Steeltoe.Logging.DynamicConsole;
+
+var builder = WebApplication.CreateBuilder(args);
+builder.Logging.SetMinimumLevel(LogLevel.Debug);
+builder.Configuration["Logging:LogLevel:Default"] = "Warning";
+builder.Logging.AddDynamicConsole();
+
+var app = builder.Build();
+
+var loggerFactory = app.Services.GetRequiredService();
+var exampleLogger = loggerFactory.CreateLogger("Example.Sub.Namespace");
+
+exampleLogger.LogDebug("Example debug message (1) - hidden");
+
+var dynamicLoggerProvider = app.Services.GetRequiredService();
+dynamicLoggerProvider.SetLogLevel("Example", LogLevel.Debug);
+
+exampleLogger.LogDebug("Example debug message (2)");
+
+await Task.Delay(TimeSpan.FromMilliseconds(250)); // wait for logs to flush
+```
+
+## Management
+
+For additional information, see the updated [Management documentation](../management/index.md) and
+[Management samples](https://github.com/SteeltoeOSS/Samples/tree/main/Management).
+
+### Endpoints
+
+Project file:
+
+```diff
+
+
+-
++
+
+
+```
+
+#### All actuators
+
+appsettings.json:
+
+```diff
+{
+- "$schema": "https://steeltoe.io/schema/v3/schema.json",
++ "$schema": "https://steeltoe.io/schema/v4/schema.json",
+ "Management": {
+ "Endpoints": {
+ "Actuator": {
+ "Exposure": {
+ "Include": [ "*" ]
+ }
++ },
++ "Health": {
++ "ShowComponents": "Always",
++ "ShowDetails": "Always",
++ "Readiness": {
++ "Enabled": true
++ },
++ "Liveness": {
++ "Enabled": true
++ }
+ }
+ }
+ }
+}
+```
+
+Program.cs:
+
+```diff
+-using Steeltoe.Management.Endpoint;
++using Steeltoe.Management.Endpoint.Actuators.All;
+
+var builder = WebApplication.CreateBuilder(args);
+-builder.AddAllActuators();
++builder.Services.AddAllActuators();
+```
+
+#### Custom health contributor
+
+appsettings.json:
+
+```diff
+{
+- "$schema": "https://steeltoe.io/schema/v3/schema.json",
++ "$schema": "https://steeltoe.io/schema/v4/schema.json",
++ "Management": {
++ "Endpoints": {
++ "Health": {
++ "ShowComponents": "Always",
++ "ShowDetails": "Always",
++ "Readiness": {
++ "Enabled": true
++ },
++ "Liveness": {
++ "Enabled": true
++ }
++ }
++ }
++ }
+}
+```
+
+Program.cs:
+
+```diff
+using Steeltoe.Common.HealthChecks;
+-using Steeltoe.Management.Endpoint;
++using Steeltoe.Management.Endpoint.Actuators.Health;
+
+var builder = WebApplication.CreateBuilder(args);
+
+-builder.AddHealthActuator();
++builder.Services.AddHealthActuator();
+-builder.Services.AddSingleton();
++builder.Services.AddHealthContributor();
+-builder.Services.AddControllers();
+
+var app = builder.Build();
+-app.MapControllers();
+app.Run();
+
+public class WarningHealthContributor : IHealthContributor
+{
+ public string Id => "exampleContributor";
+
+- public HealthCheckResult Health()
++ public async Task CheckHealthAsync(CancellationToken cancellationToken)
+ {
++ await Task.Yield();
+ return new HealthCheckResult
+ {
+- Status = HealthStatus.WARNING,
++ Status = HealthStatus.Warning,
++ Description = "Example health contributor reports warning.",
+ Details =
+ {
+- ["status"] = HealthStatus.WARNING,
+- ["description"] = "Example health contributor reports warning.",
+ ["currentTime"] = DateTime.UtcNow.ToString("O")
+ }
+ };
+ }
+}
+```
+
+#### Custom info contributor
+
+Program.cs:
+
+```diff
+-using Steeltoe.Management.Endpoint;
+-using Steeltoe.Management.Info;
++using Steeltoe.Management.Endpoint.Actuators.Info;
+
+var builder = WebApplication.CreateBuilder(args);
+-builder.AddInfoActuator();
++builder.Services.AddInfoActuator();
+-builder.Services.AddSingleton();
++builder.Services.AddInfoContributor();
+-builder.Services.AddControllers();
+
+var app = builder.Build();
+-app.MapControllers();
+app.Run();
+
+public class ExampleInfoContributor : IInfoContributor
+{
+- public void Contribute(IInfoBuilder builder)
++ public async Task ContributeAsync(InfoBuilder builder, CancellationToken cancellationToken)
+ {
++ await Task.Yield();
+ builder.WithInfo(".NET version", Environment.Version);
+ }
+}
+```
+
+#### Cloud hosting
+
+The `UseCloudHosting` extension method has been removed from Steeltoe in v4. Use one of the methods described at
+[8 ways to set the URLs for an ASP.NET Core app](https://andrewlock.net/8-ways-to-set-the-urls-for-an-aspnetcore-app/)
+to configure the port number(s) to listen on.
+
+Program.cs:
+
+```diff
+-using Steeltoe.Common.Hosting;
+-using Steeltoe.Management.Endpoint;
+using Steeltoe.Management.Endpoint.Actuators.All;
+
+var builder = WebApplication.CreateBuilder(args);
+-builder.UseCloudHosting(runLocalHttpPort: 8080, runLocalHttpsPort: 9090);
++builder.WebHost.UseUrls("http://+:8080", "https://+:9090");
+-builder.AddAllActuators();
+builder.Services.AddAllActuators();
+```
+
+For deployment to Cloud Foundry, the `builder.WebHost.UseUrls` line should be omitted.
+
+- When using the dotnet_core_buildpack, the `PORT` environment variable is picked up automatically.
+- When using the binary_buildpack, use the `PORT` environment variable in the `manifest.yml` file:
+
+ ```yaml
+ ---
+ applications:
+ - name: example-app
+ stack: windows
+ buildpacks:
+ - binary_buildpack
+ command: cmd /c ./example-app --urls=http://0.0.0.0:%PORT%
+ ```
+
+#### Spring Boot Admin
+
+appsettings.json:
+
+```diff
+{
+- "$schema": "https://steeltoe.io/schema/v3/schema.json",
++ "$schema": "https://steeltoe.io/schema/v4/schema.json",
+ "Spring": {
+ "Application": {
+ "Name": "example-service"
+ },
+ "Boot": {
+ "Admin": {
+ "Client": {
+ "Url": "http://localhost:9099",
+- "BasePath": "http://host.docker.internal:5050"
++ "BaseHost": "host.docker.internal"
+ }
+ }
+ }
+ },
+ "Management": {
+ "Endpoints": {
+ "Actuator": {
+ "Exposure": {
+ "Include": [ "*" ]
+ }
+ }
+ }
+ }
+}
+```
+
+Program.cs:
+
+```diff
+-using Steeltoe.Management.Endpoint;
++using Steeltoe.Management.Endpoint.Actuators.All;
++using Steeltoe.Management.Endpoint.SpringBootAdminClient;
+
+var builder = WebApplication.CreateBuilder(args);
+builder.WebHost.UseUrls("http://host.docker.internal:5050");
+-builder.AddAllActuators();
++builder.Services.AddAllActuators();
+builder.Services.AddSpringBootAdminClient();
+```
+
+### OpenTelemetry
+
+Using OpenTelemetry for collecting logs, metrics and distributed traces now works out of the box without requiring a Steeltoe NuGet package.
+See the instructions [here](../tracing/index.md) to configure OpenTelemetry in your application.
+See [here](../management/prometheus.md) to export metrics to Prometheus using Steeltoe v4.
+
+The sample [here](https://github.com/SteeltoeOSS/Samples/blob/main/Management/src/ActuatorWeb/README.md#viewing-metric-dashboards) demonstrates exporting to Prometheus and Grafana.
+
+### Kubernetes
+
+Direct interaction with the Kubernetes API has been removed from Steeltoe in v4.
+
+### Application tasks
+
+Project file:
+
+```diff
+
+
+-
++
+
+
+```
+
+After the following steps, run your app:
+
+```shell
+dotnet run runtask=example-task
+```
+
+#### Using inline code
+
+Program.cs:
+
+```diff
+-using Steeltoe.Management.TaskCore;
++using Steeltoe.Management.Tasks;
+
+var builder = WebApplication.CreateBuilder(args);
+-builder.Services.AddTask("example-task", serviceProvider =>
++builder.Services.AddTask("example-task", async (serviceProvider, cancellationToken) =>
+{
++ await Task.Yield();
+ var loggerFactory = serviceProvider.GetRequiredService();
+ var logger = loggerFactory.CreateLogger("ExampleTaskLogger");
+ logger.LogInformation("Example task executed.");
+});
+
+var app = builder.Build();
+-app.RunWithTasks();
++await app.RunWithTasksAsync(CancellationToken.None);
+```
+
+#### Implementing `IApplicationTask`
+
+Program.cs:
+
+```diff
+using Steeltoe.Common;
+-using Steeltoe.Management.TaskCore;
++using Steeltoe.Management.Tasks;
+
+var builder = WebApplication.CreateBuilder(args);
+-builder.Services.AddTask();
++builder.Services.AddTask("example-task");
+
+var app = builder.Build();
+-app.RunWithTasks();
++await app.RunWithTasksAsync(CancellationToken.None);
+
+public class ExampleTask(ILogger logger) : IApplicationTask
+{
+- public string Name => "example-task";
+
+- public void Run()
++ public Task RunAsync(CancellationToken cancellationToken)
+ {
+ logger.LogInformation("Example task executed.");
++ return Task.CompletedTask;
+ }
+}
+```
+
+## Messaging
+
+Template-based support for Spring messaging systems has been removed from Steeltoe in v4.
+
+## Stream
+
+Spring Cloud Stream support has been removed from Steeltoe in v4.
+
+## Security
+
+For additional information, see the updated [Security documentation](../security/index.md) and
+[Discovery samples](https://github.com/SteeltoeOSS/Samples/tree/main/Security).
+
+### CredHub client
+
+The CredHub client has been removed from Steeltoe in v4.
+Use [CredHub Service Broker](https://techdocs.broadcom.com/us/en/vmware-tanzu/platform-services/credhub-service-broker/services/credhub-sb/index.html) instead.
+
+### TODO: JWT/OAuth/OpenID Connect/Certificates...
+
+### DataProtection Key Store using Redis/Valkey
+
+```diff
+
+
+-
+-
+-
++
+
+
+```
+
+appsettings.json:
+
+```diff
+{
+- "$schema": "https://steeltoe.io/schema/v3/schema.json",
++ "$schema": "https://steeltoe.io/schema/v4/schema.json",
+- "Redis": {
+- "Client": {
+- "ConnectionString": "localhost:6379"
+- }
+- }
++ "Steeltoe": {
++ "Client": {
++ "Redis": {
++ "Default": {
++ "ConnectionString": "localhost"
++ }
++ }
++ }
++ }
+}
+```
+
+Program.cs:
+
+```diff
+using Microsoft.AspNetCore.DataProtection;
+-using Steeltoe.Connector.Redis;
++using Steeltoe.Connectors.Redis;
+-using Steeltoe.Security.DataProtection;
++using Steeltoe.Security.DataProtection.Redis;
+
+var builder = WebApplication.CreateBuilder(args);
+-builder.Services.AddRedisConnectionMultiplexer(builder.Configuration);
+-builder.Services.AddDistributedRedisCache(builder.Configuration);
++builder.AddRedis();
+builder.Services.AddDataProtection()
+ .PersistKeysToRedis()
+ .SetApplicationName("example-app");
+builder.Services.AddSession();
+
+var app = builder.Build();
+app.UseSession();
+
+app.MapPost("set-session", httpContext =>
+{
+ httpContext.Session.SetString("example-key", $"example-value-{Guid.NewGuid()}");
+ httpContext.Response.StatusCode = 204;
+ return Task.CompletedTask;
+});
+
+app.MapGet("get-session", async httpContext =>
+{
+ var sessionValue = httpContext.Session.GetString("example-key");
+ httpContext.Response.StatusCode = 200;
+ httpContext.Response.ContentType = "text/plain";
+ await httpContext.Response.WriteAsync($"Session value: {sessionValue ?? "(none)"}");
+});
+
+app.Run();
+```
diff --git a/docs/docs/v4/welcome/toc.yml b/docs/docs/v4/welcome/toc.yml
index 54cb274e..a146cbc6 100644
--- a/docs/docs/v4/welcome/toc.yml
+++ b/docs/docs/v4/welcome/toc.yml
@@ -1,5 +1,7 @@
- href: whats-new.md
name: What's new in Steeltoe 4
+- href: migrate-quick-steps.md
+ name: Migrating from Steeltoe 3
- href: prerequisites.md
name: Prerequisites
- href: common-steps.md
diff --git a/docs/docs/v4/welcome/whats-new.md b/docs/docs/v4/welcome/whats-new.md
index 23cb73f0..0c661b2b 100644
--- a/docs/docs/v4/welcome/whats-new.md
+++ b/docs/docs/v4/welcome/whats-new.md
@@ -19,9 +19,13 @@ underscored that this is the time to refocus on Steeltoe's core goals and re-eva
Steeltoe 4 is a major release that brings many improvements and changes to the library.
The goal of this release is to make Steeltoe better integrated in the .NET ecosystem in a more developer-friendly way, compatible
with the latest versions of .NET and third-party libraries/products, and to improve the overall quality of the library.
-This document provides an overview of the changes in Steeltoe 4, the impact on existing applications, and serves as the upgrade guide (with a searchable API diff and replacement notes).
Steeltoe 4 requires .NET 8 or higher.
+This document provides an overview of the changes in Steeltoe 4 and the impact on existing applications (with a searchable API diff and replacement notes).
+
+> [!TIP]
+> For quick steps to upgrade an existing application from Steeltoe 3, see [Migrating from Steeltoe 3](./migrate-quick-steps.md).
+
### Quality of Life improvements
- Annotated for [nullable reference types](https://learn.microsoft.com/dotnet/csharp/nullable-references)
@@ -620,8 +624,6 @@ For more information, see the updated [Configuration documentation](../configura
For more information, see the updated [Connectors documentation](../configuration/index.md) and
[Configuration samples](https://github.com/SteeltoeOSS/Samples/tree/main/Connectors).
----
-
## Discovery
### Behavior changes
@@ -984,7 +986,11 @@ For more information, see the updated [Logging documentation](../logging/index.m
- Actuators can be turned on/off or bound to different verbs at runtime using configuration
- Simplified content negotiation; updated all actuators to support latest Spring media type
- New actuator `/beans` that lists the contents of the .NET dependency container, including support for keyed services
-- Update health checks and actuator to align with latest Spring; hide details by default; contributors can be turned on/off at runtime using configuration
+- Update health checks and actuator to align with latest Spring
+ - Hide components/details by default
+ - Liveness/readiness contributors are turned off by default
+ - Health contributors can be turned on/off at runtime using configuration
+- Contributors (both health and info) must be singletons (you can inject `IHttpContextAccessor`, but `HttpContext` may not always be available)
- Support Windows network shares in disk space health contributor
- Update `/mappings` actuator to include endpoints from Minimal APIs, Razor Pages, and Blazor, with richer metadata and improved compatibility with Spring
- Heap dumps are enabled by default in Cloud Foundry on Linux; all dump types supported on Windows/Linux/macOS
diff --git a/src/Steeltoe.io/Program.cs b/src/Steeltoe.io/Program.cs
index f110845f..bed7d560 100644
--- a/src/Steeltoe.io/Program.cs
+++ b/src/Steeltoe.io/Program.cs
@@ -23,7 +23,7 @@
}
var rewriteOptions = new RewriteOptions()
- .AddRedirect("^docs/v3/obsolete", "docs/v4/welcome/whats-new.html", 301)
+ .AddRedirect("^docs/v3/obsolete", "docs/v4/welcome/migrate-quick-steps.html", 301)
.AddRedirect("^circuit-breakers.*", "attic", 301)
.AddRedirect("^steeltoe-circuitbreaker", "attic", 301)
.AddRedirect("^event-driven", "attic", 301)
diff --git a/src/Steeltoe.io/wwwroot/css/shared.css b/src/Steeltoe.io/wwwroot/css/shared.css
index 991ae390..ec6d0fb7 100644
--- a/src/Steeltoe.io/wwwroot/css/shared.css
+++ b/src/Steeltoe.io/wwwroot/css/shared.css
@@ -83,6 +83,10 @@ footer a:hover {
color: steelblue;
}
+.hljs-deletion {
+ color: red;
+}
+
:not(a):not(pre) > code,
body[data-yaml-mime=ManagedReference] article dl.parameters > dt > code,
body[data-yaml-mime=ApiPage] article dl.parameters > dt > code {
@@ -97,6 +101,14 @@ body[data-yaml-mime=ApiPage] article dl.parameters > dt > code {
color: #006881
}
+ .hljs-deletion, .hljs-addition {
+ display: inline-block;
+ }
+
+ .hljs-addition {
+ color: green;
+ }
+
:not(a):not(pre) > code {
color: var(--bs-primary-text-emphasis);
background-color: #e6e6e6;
@@ -188,6 +200,15 @@ body[data-yaml-mime=ApiPage] article dl.parameters > dt > code {
.hljs-built_in {
color: #569cd6;
}
+
+ .hljs-deletion {
+ background-color: transparent;
+ }
+
+ .hljs-addition {
+ background-color: transparent;
+ color: lightgreen;
+ }
}
}