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; + } } }