diff --git a/samples/AspireWithMassTransit/AspireWithMassTransit.ApiService/AspireWithMassTransit.ApiService.csproj b/samples/AspireWithMassTransit/AspireWithMassTransit.ApiService/AspireWithMassTransit.ApiService.csproj new file mode 100644 index 00000000..fbcb3bfc --- /dev/null +++ b/samples/AspireWithMassTransit/AspireWithMassTransit.ApiService/AspireWithMassTransit.ApiService.csproj @@ -0,0 +1,17 @@ + + + + net8.0 + enable + enable + + + + + + + + + + + diff --git a/samples/AspireWithMassTransit/AspireWithMassTransit.ApiService/HelloAspireEventConsumer.cs b/samples/AspireWithMassTransit/AspireWithMassTransit.ApiService/HelloAspireEventConsumer.cs new file mode 100644 index 00000000..261bbafe --- /dev/null +++ b/samples/AspireWithMassTransit/AspireWithMassTransit.ApiService/HelloAspireEventConsumer.cs @@ -0,0 +1,21 @@ +using MassTransit; + +namespace AspireWithMassTransit.ApiService; + +public class HelloAspireEventConsumer : IConsumer +{ + private readonly ILogger _logger; + + public HelloAspireEventConsumer(ILogger logger) + { + _logger = logger; + } + + public Task Consume(ConsumeContext context) + { + _logger.LogInformation("Received: {Message}", context.Message.Message); + return Task.CompletedTask; + } +} + +public record HelloAspireEvent(string Message); diff --git a/samples/AspireWithMassTransit/AspireWithMassTransit.ApiService/Program.cs b/samples/AspireWithMassTransit/AspireWithMassTransit.ApiService/Program.cs new file mode 100644 index 00000000..e484dcf1 --- /dev/null +++ b/samples/AspireWithMassTransit/AspireWithMassTransit.ApiService/Program.cs @@ -0,0 +1,41 @@ +using AspireWithMassTransit.ApiService; +using MassTransit; + +var builder = WebApplication.CreateBuilder(args); + +// Add service defaults & Aspire client integrations. +builder.AddServiceDefaults(); + +// Add services to the container. +builder.Services.AddProblemDetails(); + +builder.Services.AddMassTransit(s => +{ + s.AddConsumers(typeof(Program).Assembly); + s.UsingRabbitMq((context, cfg) => + { + var configuration = context.GetRequiredService(); + var host = configuration.GetConnectionString("messaging"); + + cfg.Host(host); + cfg.ConfigureEndpoints(context); + }); +}); + +var app = builder.Build(); + +// Configure the HTTP request pipeline. +app.UseExceptionHandler(); + +app.MapGet("/", async (IPublishEndpoint publishEndpoint) => +{ + await publishEndpoint.Publish(new HelloAspireEvent("Hello, .NET!")); + await publishEndpoint.Publish(new HelloAspireEvent("Hello, Aspire!")); + + return Results.Ok("ok"); +}); + +app.MapDefaultEndpoints(); + +app.Run(); + diff --git a/samples/AspireWithMassTransit/AspireWithMassTransit.ApiService/Properties/launchSettings.json b/samples/AspireWithMassTransit/AspireWithMassTransit.ApiService/Properties/launchSettings.json new file mode 100644 index 00000000..f3b8939a --- /dev/null +++ b/samples/AspireWithMassTransit/AspireWithMassTransit.ApiService/Properties/launchSettings.json @@ -0,0 +1,25 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "launchUrl": "", + "applicationUrl": "http://localhost:5575", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "launchUrl": "", + "applicationUrl": "https://localhost:7381;http://localhost:5575", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/samples/AspireWithMassTransit/AspireWithMassTransit.ApiService/appsettings.Development.json b/samples/AspireWithMassTransit/AspireWithMassTransit.ApiService/appsettings.Development.json new file mode 100644 index 00000000..0c208ae9 --- /dev/null +++ b/samples/AspireWithMassTransit/AspireWithMassTransit.ApiService/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/samples/AspireWithMassTransit/AspireWithMassTransit.ApiService/appsettings.json b/samples/AspireWithMassTransit/AspireWithMassTransit.ApiService/appsettings.json new file mode 100644 index 00000000..10f68b8c --- /dev/null +++ b/samples/AspireWithMassTransit/AspireWithMassTransit.ApiService/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/samples/AspireWithMassTransit/AspireWithMassTransit.AppHost/AspireWithMassTransit.AppHost.csproj b/samples/AspireWithMassTransit/AspireWithMassTransit.AppHost/AspireWithMassTransit.AppHost.csproj new file mode 100644 index 00000000..3974bb18 --- /dev/null +++ b/samples/AspireWithMassTransit/AspireWithMassTransit.AppHost/AspireWithMassTransit.AppHost.csproj @@ -0,0 +1,23 @@ + + + + + + Exe + net8.0 + enable + enable + true + 7d4e2632-ca72-4db3-8665-001fb368e89b + + + + + + + + + + + + diff --git a/samples/AspireWithMassTransit/AspireWithMassTransit.AppHost/Program.cs b/samples/AspireWithMassTransit/AspireWithMassTransit.AppHost/Program.cs new file mode 100644 index 00000000..90802813 --- /dev/null +++ b/samples/AspireWithMassTransit/AspireWithMassTransit.AppHost/Program.cs @@ -0,0 +1,11 @@ +var builder = DistributedApplication.CreateBuilder(args); + +var messaging = builder.AddRabbitMQ("messaging") + .WithLifetime(ContainerLifetime.Persistent) + .WithManagementPlugin() + .WithDataVolume(); + +var apiService = builder.AddProject("apiservice") + .WithReference(messaging); + +builder.Build().Run(); diff --git a/samples/AspireWithMassTransit/AspireWithMassTransit.AppHost/Properties/launchSettings.json b/samples/AspireWithMassTransit/AspireWithMassTransit.AppHost/Properties/launchSettings.json new file mode 100644 index 00000000..ef796939 --- /dev/null +++ b/samples/AspireWithMassTransit/AspireWithMassTransit.AppHost/Properties/launchSettings.json @@ -0,0 +1,29 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "https://localhost:17131;http://localhost:15194", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development", + "DOTNET_ENVIRONMENT": "Development", + "DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "https://localhost:21047", + "DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "https://localhost:22126" + } + }, + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "http://localhost:15194", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development", + "DOTNET_ENVIRONMENT": "Development", + "DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:19101", + "DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "http://localhost:20047" + } + } + } +} diff --git a/samples/AspireWithMassTransit/AspireWithMassTransit.AppHost/appsettings.Development.json b/samples/AspireWithMassTransit/AspireWithMassTransit.AppHost/appsettings.Development.json new file mode 100644 index 00000000..0c208ae9 --- /dev/null +++ b/samples/AspireWithMassTransit/AspireWithMassTransit.AppHost/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/samples/AspireWithMassTransit/AspireWithMassTransit.AppHost/appsettings.json b/samples/AspireWithMassTransit/AspireWithMassTransit.AppHost/appsettings.json new file mode 100644 index 00000000..31c092aa --- /dev/null +++ b/samples/AspireWithMassTransit/AspireWithMassTransit.AppHost/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning", + "Aspire.Hosting.Dcp": "Warning" + } + } +} diff --git a/samples/AspireWithMassTransit/AspireWithMassTransit.ServiceDefaults/AspireWithMassTransit.ServiceDefaults.csproj b/samples/AspireWithMassTransit/AspireWithMassTransit.ServiceDefaults/AspireWithMassTransit.ServiceDefaults.csproj new file mode 100644 index 00000000..4c0f8a21 --- /dev/null +++ b/samples/AspireWithMassTransit/AspireWithMassTransit.ServiceDefaults/AspireWithMassTransit.ServiceDefaults.csproj @@ -0,0 +1,22 @@ + + + + net8.0 + enable + enable + true + + + + + + + + + + + + + + + diff --git a/samples/AspireWithMassTransit/AspireWithMassTransit.ServiceDefaults/Extensions.cs b/samples/AspireWithMassTransit/AspireWithMassTransit.ServiceDefaults/Extensions.cs new file mode 100644 index 00000000..5f8a36a1 --- /dev/null +++ b/samples/AspireWithMassTransit/AspireWithMassTransit.ServiceDefaults/Extensions.cs @@ -0,0 +1,111 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Diagnostics.HealthChecks; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Diagnostics.HealthChecks; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.ServiceDiscovery; +using OpenTelemetry; +using OpenTelemetry.Metrics; +using OpenTelemetry.Trace; + +namespace Microsoft.Extensions.Hosting; + +// Adds common .NET Aspire services: service discovery, resilience, health checks, and OpenTelemetry. +// This project should be referenced by each service project in your solution. +// To learn more about using this project, see https://aka.ms/dotnet/aspire/service-defaults +public static class Extensions +{ + public static TBuilder AddServiceDefaults(this TBuilder builder) where TBuilder : IHostApplicationBuilder + { + builder.ConfigureOpenTelemetry(); + + builder.AddDefaultHealthChecks(); + + builder.Services.AddServiceDiscovery(); + + builder.Services.ConfigureHttpClientDefaults(http => + { + // Turn on resilience by default + http.AddStandardResilienceHandler(); + + // Turn on service discovery by default + http.AddServiceDiscovery(); + }); + + // Uncomment the following to restrict the allowed schemes for service discovery. + // builder.Services.Configure(options => + // { + // options.AllowedSchemes = ["https"]; + // }); + + return builder; + } + + public static TBuilder ConfigureOpenTelemetry(this TBuilder builder) where TBuilder : IHostApplicationBuilder + { + builder.Logging.AddOpenTelemetry(logging => + { + logging.IncludeFormattedMessage = true; + logging.IncludeScopes = true; + }); + + builder.Services.AddOpenTelemetry() + .WithMetrics(metrics => + { + metrics.AddAspNetCoreInstrumentation() + .AddHttpClientInstrumentation() + .AddMeter("MassTransit") + .AddRuntimeInstrumentation(); + }) + .WithTracing(tracing => + { + tracing.AddAspNetCoreInstrumentation() + .AddSource("MassTransit") + .AddHttpClientInstrumentation(); + }); + + builder.AddOpenTelemetryExporters(); + + return builder; + } + + private static TBuilder AddOpenTelemetryExporters(this TBuilder builder) where TBuilder : IHostApplicationBuilder + { + var useOtlpExporter = !string.IsNullOrWhiteSpace(builder.Configuration["OTEL_EXPORTER_OTLP_ENDPOINT"]); + + if (useOtlpExporter) + { + builder.Services.AddOpenTelemetry().UseOtlpExporter(); + } + + return builder; + } + + public static TBuilder AddDefaultHealthChecks(this TBuilder builder) where TBuilder : IHostApplicationBuilder + { + builder.Services.AddHealthChecks() + // Add a default liveness check to ensure app is responsive + .AddCheck("self", () => HealthCheckResult.Healthy(), ["live"]); + + return builder; + } + + public static WebApplication MapDefaultEndpoints(this WebApplication app) + { + // Adding health checks endpoints to applications in non-development environments has security implications. + // See https://aka.ms/dotnet/aspire/healthchecks for details before enabling these endpoints in non-development environments. + if (app.Environment.IsDevelopment()) + { + // All health checks must pass for app to be considered ready to accept traffic after starting + app.MapHealthChecks("/health"); + + // Only health checks tagged with the "live" tag must pass for app to be considered alive + app.MapHealthChecks("/alive", new HealthCheckOptions + { + Predicate = r => r.Tags.Contains("live") + }); + } + + return app; + } +} diff --git a/samples/AspireWithMassTransit/AspireWithMassTransit.sln b/samples/AspireWithMassTransit/AspireWithMassTransit.sln new file mode 100644 index 00000000..2266d3c0 --- /dev/null +++ b/samples/AspireWithMassTransit/AspireWithMassTransit.sln @@ -0,0 +1,28 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AspireWithMassTransit.ApiService", "AspireWithMassTransit.ApiService\AspireWithMassTransit.ApiService.csproj", "{0B88A0EE-6435-4A30-9131-165118F3053C}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AspireWithMassTransit.AppHost", "AspireWithMassTransit.AppHost\AspireWithMassTransit.AppHost.csproj", "{665B7823-0444-4F4D-9C41-D43CD50DAEF3}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AspireWithMassTransit.ServiceDefaults", "AspireWithMassTransit.ServiceDefaults\AspireWithMassTransit.ServiceDefaults.csproj", "{77A8E695-50D9-48D4-8ED3-1F458BB31A00}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {0B88A0EE-6435-4A30-9131-165118F3053C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0B88A0EE-6435-4A30-9131-165118F3053C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0B88A0EE-6435-4A30-9131-165118F3053C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0B88A0EE-6435-4A30-9131-165118F3053C}.Release|Any CPU.Build.0 = Release|Any CPU + {665B7823-0444-4F4D-9C41-D43CD50DAEF3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {665B7823-0444-4F4D-9C41-D43CD50DAEF3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {665B7823-0444-4F4D-9C41-D43CD50DAEF3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {665B7823-0444-4F4D-9C41-D43CD50DAEF3}.Release|Any CPU.Build.0 = Release|Any CPU + {77A8E695-50D9-48D4-8ED3-1F458BB31A00}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {77A8E695-50D9-48D4-8ED3-1F458BB31A00}.Debug|Any CPU.Build.0 = Debug|Any CPU + {77A8E695-50D9-48D4-8ED3-1F458BB31A00}.Release|Any CPU.ActiveCfg = Release|Any CPU + {77A8E695-50D9-48D4-8ED3-1F458BB31A00}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/samples/AspireWithMassTransit/README.md b/samples/AspireWithMassTransit/README.md new file mode 100644 index 00000000..653cafc2 --- /dev/null +++ b/samples/AspireWithMassTransit/README.md @@ -0,0 +1,41 @@ +--- +languages: +- csharp +products: +- dotnet +- dotnet-aspire +page_type: sample +name: ".NET Aspire MassTransit sample" +urlFragment: "aspire-dapr" +description: "An example of how to integrate MassTransit into a .NET Aspire app." +--- + +# Integrating MassTransit into a .NET Aspire application + +This sample demonstrates an approach for integrating [MassTransit](https://MassTransit.io/) into a .NET Aspire application. + +## Pre-requisites + +- [.NET 8 SDK](https://dotnet.microsoft.com/download/dotnet/8.0) +- [Docker Desktop](https://www.docker.com/products/docker-desktop/) +- **Optional** [Visual Studio 2022 17.9 Preview](https://visualstudio.microsoft.com/vs/preview/) + +## Running the app + +If using Visual Studio, open the solution file `AspireWithMassTransit.sln` and launch/debug the `AspireWithMassTransit.AppHost` project. + +If using the .NET CLI, run `dotnet run` from the `AspireWithMassTransit.AppHost` directory. + +## Experiencing the app + +Once the app is running, the .NET Aspire dashboard will launch in your browser: + +Navigate to https://localhost:17131 in the browser to publish some messages. + +After that check the log. Consumer should received and consume the messages. + +``` +2024-04-18T20:37:35.2219340 info: AspireWithMassTransit.ApiService.HelloAspireEventConsumer[0] + Received: Hello, .NET! +2024-04-18T20:37:35.2219350 info: AspireWithMassTransit.ApiService.HelloAspireEventConsumer[0] + Received: Hello, Aspire! \ No newline at end of file