diff --git a/.gitignore b/.gitignore index 7724559c..43d837a0 100644 --- a/.gitignore +++ b/.gitignore @@ -431,3 +431,5 @@ FodyWeavers.xsd **/*/azure.yaml **/*/next-steps.md +/samples/OrchardCore/OrchardCore.Cms/App_Data +/samples/OrchardCore/OrchardCore.Cms/Localization diff --git a/README.md b/README.md index 80e3dcbb..e1040cf6 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,7 @@ Sample highlights include: - [Persisting data in composed containers using volume mounts](./samples/VolumeMount) - [Working with database containers](./samples/DatabaseContainers) - [Integrating clients apps like WinForms](./samples/ClientAppsIntegration) +- [Integrating OrchardCore CMS](./samples/OrchardCore) ## eShop diff --git a/samples/OrchardCore/.gitignore b/samples/OrchardCore/.gitignore new file mode 100644 index 00000000..c20455c7 --- /dev/null +++ b/samples/OrchardCore/.gitignore @@ -0,0 +1,12 @@ +# ========================= +# Orchard specifics +# ========================= + +App_Data*/ +.vs/ + +#exclude node modules +node_modules/ + +wwwroot +**/Localization/**/*.po diff --git a/samples/OrchardCore/Aspire/Aspire.AppHost/Aspire.AppHost.csproj b/samples/OrchardCore/Aspire/Aspire.AppHost/Aspire.AppHost.csproj new file mode 100644 index 00000000..5bf1b8c9 --- /dev/null +++ b/samples/OrchardCore/Aspire/Aspire.AppHost/Aspire.AppHost.csproj @@ -0,0 +1,19 @@ + + + + Exe + net8.0 + enable + enable + true + + + + + + + + + + + diff --git a/samples/OrchardCore/Aspire/Aspire.AppHost/Program.cs b/samples/OrchardCore/Aspire/Aspire.AppHost/Program.cs new file mode 100644 index 00000000..7049023e --- /dev/null +++ b/samples/OrchardCore/Aspire/Aspire.AppHost/Program.cs @@ -0,0 +1,18 @@ +var builder = DistributedApplication.CreateBuilder(args); +const int postgresPort = 62262; +const string postgresUsername = "occms"; +const string postgresPassword = "OrchardCorePass"; + +var cmsdb = builder.AddPostgresContainer("Postgres", postgresPort, postgresPassword); + +var redis = builder.AddRedisContainer("Redis", 50963); + +builder.AddProject("OrchardCore CMS") + .WithEnvironment("OrchardCore__Default__State", "Uninitialized") + .WithEnvironment("OrchardCore__Default__TablePrefix", "Default") + .WithEnvironment("OrchardCore__DatabaseProvider", "Postgres") + .WithEnvironment("OrchardCore__ConnectionString", $"host=localhost;port={postgresPort};database={postgresUsername};username={postgresUsername};password={postgresPassword}") + .WithReference(redis) + .WithReference(cmsdb); + +await builder.Build().RunAsync(); diff --git a/samples/OrchardCore/Aspire/Aspire.AppHost/Properties/launchSettings.json b/samples/OrchardCore/Aspire/Aspire.AppHost/Properties/launchSettings.json new file mode 100644 index 00000000..42a35b84 --- /dev/null +++ b/samples/OrchardCore/Aspire/Aspire.AppHost/Properties/launchSettings.json @@ -0,0 +1,16 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "http://localhost:15264", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development", + "DOTNET_ENVIRONMENT": "Development", + "DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:16296" + } + } + } +} diff --git a/samples/OrchardCore/Aspire/Aspire.AppHost/appsettings.Development.json b/samples/OrchardCore/Aspire/Aspire.AppHost/appsettings.Development.json new file mode 100644 index 00000000..0c208ae9 --- /dev/null +++ b/samples/OrchardCore/Aspire/Aspire.AppHost/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/samples/OrchardCore/Aspire/Aspire.AppHost/appsettings.json b/samples/OrchardCore/Aspire/Aspire.AppHost/appsettings.json new file mode 100644 index 00000000..31c092aa --- /dev/null +++ b/samples/OrchardCore/Aspire/Aspire.AppHost/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning", + "Aspire.Hosting.Dcp": "Warning" + } + } +} diff --git a/samples/OrchardCore/Aspire/Aspire.ServiceDefaults/Aspire.ServiceDefaults.csproj b/samples/OrchardCore/Aspire/Aspire.ServiceDefaults/Aspire.ServiceDefaults.csproj new file mode 100644 index 00000000..30d5c1fb --- /dev/null +++ b/samples/OrchardCore/Aspire/Aspire.ServiceDefaults/Aspire.ServiceDefaults.csproj @@ -0,0 +1,24 @@ + + + + Library + net8.0 + enable + enable + true + + + + + + + + + + + + + + + + diff --git a/samples/OrchardCore/Aspire/Aspire.ServiceDefaults/Extensions.cs b/samples/OrchardCore/Aspire/Aspire.ServiceDefaults/Extensions.cs new file mode 100644 index 00000000..c59308d5 --- /dev/null +++ b/samples/OrchardCore/Aspire/Aspire.ServiceDefaults/Extensions.cs @@ -0,0 +1,119 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Diagnostics.HealthChecks; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Diagnostics.HealthChecks; +using Microsoft.Extensions.Logging; +using OpenTelemetry.Logs; +using OpenTelemetry.Metrics; +using OpenTelemetry.Trace; + +namespace Microsoft.Extensions.Hosting; + +public static class Extensions +{ + public static IHostApplicationBuilder AddServiceDefaults(this IHostApplicationBuilder builder) + { + 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.UseServiceDiscovery(); + }); + + return builder; + } + + public static IHostApplicationBuilder ConfigureOpenTelemetry(this IHostApplicationBuilder builder) + { + builder.Logging.AddOpenTelemetry(logging => + { + logging.IncludeFormattedMessage = true; + logging.IncludeScopes = true; + }); + + builder.Services.AddOpenTelemetry() + .WithMetrics(metrics => + { + metrics.AddRuntimeInstrumentation() + .AddBuiltInMeters(); + }) + .WithTracing(tracing => + { + if (builder.Environment.IsDevelopment()) + { + // We want to view all traces in development + tracing.SetSampler(new AlwaysOnSampler()); + } + + tracing.AddAspNetCoreInstrumentation() + .AddGrpcClientInstrumentation() + .AddHttpClientInstrumentation(); + }); + + builder.AddOpenTelemetryExporters(); + + return builder; + } + + private static IHostApplicationBuilder AddOpenTelemetryExporters(this IHostApplicationBuilder builder) + { + var useOtlpExporter = !string.IsNullOrWhiteSpace(builder.Configuration["OTEL_EXPORTER_OTLP_ENDPOINT"]); + + if (useOtlpExporter) + { + builder.Services.Configure(logging => logging.AddOtlpExporter()); + builder.Services.ConfigureOpenTelemetryMeterProvider(metrics => metrics.AddOtlpExporter()); + builder.Services.ConfigureOpenTelemetryTracerProvider(tracing => tracing.AddOtlpExporter()); + } + + // Uncomment the following lines to enable the Prometheus exporter (requires the OpenTelemetry.Exporter.Prometheus.AspNetCore package) + // builder.Services.AddOpenTelemetry() + // .WithMetrics(metrics => metrics.AddPrometheusExporter()); + + // Uncomment the following lines to enable the Azure Monitor exporter (requires the Azure.Monitor.OpenTelemetry.Exporter package) + // builder.Services.AddOpenTelemetry() + // .UseAzureMonitor(); + + return builder; + } + + public static IHostApplicationBuilder AddDefaultHealthChecks(this IHostApplicationBuilder builder) + { + 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) + { + // Uncomment the following line to enable the Prometheus endpoint (requires the OpenTelemetry.Exporter.Prometheus.AspNetCore package) + // app.MapPrometheusScrapingEndpoint(); + + // 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; + } + + private static MeterProviderBuilder AddBuiltInMeters(this MeterProviderBuilder meterProviderBuilder) => + meterProviderBuilder.AddMeter( + "Microsoft.AspNetCore.Hosting", + "Microsoft.AspNetCore.Server.Kestrel", + "System.Net.Http"); +} diff --git a/samples/OrchardCore/OrchardCore.Cms/NLog.config b/samples/OrchardCore/OrchardCore.Cms/NLog.config new file mode 100644 index 00000000..772f22d4 --- /dev/null +++ b/samples/OrchardCore/OrchardCore.Cms/NLog.config @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/samples/OrchardCore/OrchardCore.Cms/OrchardCore.Cms.csproj b/samples/OrchardCore/OrchardCore.Cms/OrchardCore.Cms.csproj new file mode 100644 index 00000000..bd5a1f60 --- /dev/null +++ b/samples/OrchardCore/OrchardCore.Cms/OrchardCore.Cms.csproj @@ -0,0 +1,18 @@ + + + + net8.0 + enable + enable + + + + + + + + + + + + diff --git a/samples/OrchardCore/OrchardCore.Cms/Program.cs b/samples/OrchardCore/OrchardCore.Cms/Program.cs new file mode 100644 index 00000000..71482a2a --- /dev/null +++ b/samples/OrchardCore/OrchardCore.Cms/Program.cs @@ -0,0 +1,21 @@ +using OrchardCore.Logging; + +var builder = WebApplication.CreateBuilder(args); + +builder.Host.UseNLogHost(); +builder.AddServiceDefaults(); + +builder.Services.AddOrchardCms(); + +var app = builder.Build(); + +if (!app.Environment.IsDevelopment()) +{ + app.UseExceptionHandler("/Error"); + app.UseHsts(); +} + +app.UseStaticFiles(); +app.UseOrchardCore(); + +await app.RunAsync(); diff --git a/samples/OrchardCore/OrchardCore.Cms/Properties/launchSettings.json b/samples/OrchardCore/OrchardCore.Cms/Properties/launchSettings.json new file mode 100644 index 00000000..2efeda50 --- /dev/null +++ b/samples/OrchardCore/OrchardCore.Cms/Properties/launchSettings.json @@ -0,0 +1,38 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:54444", + "sslPort": 44359 + } + }, + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "http://localhost:5090", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "https://localhost:7065;http://localhost:5090", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/samples/OrchardCore/OrchardCore.Cms/appsettings.Development.json b/samples/OrchardCore/OrchardCore.Cms/appsettings.Development.json new file mode 100644 index 00000000..0c208ae9 --- /dev/null +++ b/samples/OrchardCore/OrchardCore.Cms/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/samples/OrchardCore/OrchardCore.Cms/appsettings.json b/samples/OrchardCore/OrchardCore.Cms/appsettings.json new file mode 100644 index 00000000..5a26a881 --- /dev/null +++ b/samples/OrchardCore/OrchardCore.Cms/appsettings.json @@ -0,0 +1,12 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*", + "OrchardCore": { + + } +} diff --git a/samples/OrchardCore/OrchardCore.sln b/samples/OrchardCore/OrchardCore.sln new file mode 100644 index 00000000..ab79c0bd --- /dev/null +++ b/samples/OrchardCore/OrchardCore.sln @@ -0,0 +1,43 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.9.34407.89 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Aspire.AppHost", "Aspire\Aspire.AppHost\Aspire.AppHost.csproj", "{402C833F-9FCE-4557-84D4-3AAAF74C8DE8}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Aspire.ServiceDefaults", "Aspire\Aspire.ServiceDefaults\Aspire.ServiceDefaults.csproj", "{DE2BA7D5-D4FE-446E-AED3-771C7BDF5CAD}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OrchardCore.Cms", "OrchardCore.Cms\OrchardCore.Cms.csproj", "{58DC94EA-7B87-4FA9-B3D1-E20AD89E5692}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Aspire", "Aspire", "{BC067F5D-BBC3-4159-9A65-F2B105BC0758}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {402C833F-9FCE-4557-84D4-3AAAF74C8DE8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {402C833F-9FCE-4557-84D4-3AAAF74C8DE8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {402C833F-9FCE-4557-84D4-3AAAF74C8DE8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {402C833F-9FCE-4557-84D4-3AAAF74C8DE8}.Release|Any CPU.Build.0 = Release|Any CPU + {DE2BA7D5-D4FE-446E-AED3-771C7BDF5CAD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DE2BA7D5-D4FE-446E-AED3-771C7BDF5CAD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DE2BA7D5-D4FE-446E-AED3-771C7BDF5CAD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DE2BA7D5-D4FE-446E-AED3-771C7BDF5CAD}.Release|Any CPU.Build.0 = Release|Any CPU + {58DC94EA-7B87-4FA9-B3D1-E20AD89E5692}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {58DC94EA-7B87-4FA9-B3D1-E20AD89E5692}.Debug|Any CPU.Build.0 = Debug|Any CPU + {58DC94EA-7B87-4FA9-B3D1-E20AD89E5692}.Release|Any CPU.ActiveCfg = Release|Any CPU + {58DC94EA-7B87-4FA9-B3D1-E20AD89E5692}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {402C833F-9FCE-4557-84D4-3AAAF74C8DE8} = {BC067F5D-BBC3-4159-9A65-F2B105BC0758} + {DE2BA7D5-D4FE-446E-AED3-771C7BDF5CAD} = {BC067F5D-BBC3-4159-9A65-F2B105BC0758} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {77E10717-33F7-4C6D-94C3-9426C68D966C} + EndGlobalSection +EndGlobal diff --git a/samples/OrchardCore/README.md b/samples/OrchardCore/README.md new file mode 100644 index 00000000..c3dd7eda --- /dev/null +++ b/samples/OrchardCore/README.md @@ -0,0 +1,51 @@ +--- +languages: +- csharp +products: +- dotnet +- dotnet-aspire +page_type: sample +name: ".NET Aspire OrchardCore sample app" +urlFragment: "aspire-orchard-core" +description: "A sample .NET Aspire app that shows how to use OrchardCore" +--- + +# .NET Aspire OrchardCore CMS sample app + +This is a simple .NET app that shows how to use OrchardCore with .NET Aspire orchestration. + +## Demonstrates + +- How to configure a .NET Aspire app to work with OrchardCore + +## Sample prerequisites + +- [.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 sample + +To download and run the sample, follow these steps: + +### Run the project using Visual Studio + +To run the sample project using Visual Studio, open Visual Studio (2022 or later), then: + + 1. On the menu bar, choose **File** > **Open** > **Project/Solution**. + 2. Navigate to the folder that holds the unzipped sample code, and open the solution (.sln) file. + 3. Right click the _Aspire.AppHost_ project in the solution explore and choose it as the startup project. + 4. Choose the F5 key to run with debugging, or Ctrl+F5 keys to run the project without debugging. + +### Run the project using command line + +To run the .NET Aspire app, open a command line console and change directory to the OrchardCore solution folder. Then execute the following command: + +``` bash +dotnet run --project Aspire/Aspire.AppHost +``` + +On the **Projects** page, click on one of the endpoints (OrchardCore CMS) for the listed project. This launches the simple .NET app. + +For more information about using OrchardCore, see the [OrchardCore documentation](https://docs.orchardcore.net/en/latest/).