diff --git a/Microsoft.AspNetCore.SystemWebAdapters.sln b/Microsoft.AspNetCore.SystemWebAdapters.sln index c50328566f..c0ca4fc50b 100644 --- a/Microsoft.AspNetCore.SystemWebAdapters.sln +++ b/Microsoft.AspNetCore.SystemWebAdapters.sln @@ -112,6 +112,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AuthWindowsCore", "samples\ EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Samples.ServiceDefaults", "samples\ServiceDefaults\Samples.ServiceDefaults.csproj", "{D08809BC-D4C3-F7C8-1C00-151CA8C6E504}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Aspire.Hosting.IncrementalMigration", "src\Aspire.Hosting.IncrementalMigration\Aspire.Hosting.IncrementalMigration.csproj", "{0D10989D-058E-5684-7828-C5A12A0DC0C2}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Aspire.Microsoft.AspNetCore.SystemWebAdapters", "src\Aspire.Microsoft.AspNetCore.SystemWebAdapters\Aspire.Microsoft.AspNetCore.SystemWebAdapters.csproj", "{B955861A-DDDE-0D3F-B0AC-3D6CAA95A0A5}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -246,6 +250,14 @@ Global {D08809BC-D4C3-F7C8-1C00-151CA8C6E504}.Debug|Any CPU.Build.0 = Debug|Any CPU {D08809BC-D4C3-F7C8-1C00-151CA8C6E504}.Release|Any CPU.ActiveCfg = Release|Any CPU {D08809BC-D4C3-F7C8-1C00-151CA8C6E504}.Release|Any CPU.Build.0 = Release|Any CPU + {0D10989D-058E-5684-7828-C5A12A0DC0C2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0D10989D-058E-5684-7828-C5A12A0DC0C2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0D10989D-058E-5684-7828-C5A12A0DC0C2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0D10989D-058E-5684-7828-C5A12A0DC0C2}.Release|Any CPU.Build.0 = Release|Any CPU + {B955861A-DDDE-0D3F-B0AC-3D6CAA95A0A5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B955861A-DDDE-0D3F-B0AC-3D6CAA95A0A5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B955861A-DDDE-0D3F-B0AC-3D6CAA95A0A5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B955861A-DDDE-0D3F-B0AC-3D6CAA95A0A5}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -290,6 +302,8 @@ Global {AC29CA1D-A850-4C29-B27D-C23BAF3D664C} = {95915611-30BF-4AFF-AE41-5CDC6F57DCF7} {64B09008-C9CB-BAEC-2AE1-616E6BE9880B} = {AC29CA1D-A850-4C29-B27D-C23BAF3D664C} {D08809BC-D4C3-F7C8-1C00-151CA8C6E504} = {95915611-30BF-4AFF-AE41-5CDC6F57DCF7} + {0D10989D-058E-5684-7828-C5A12A0DC0C2} = {F9DB9323-C919-49E8-8F96-B923D2F42E60} + {B955861A-DDDE-0D3F-B0AC-3D6CAA95A0A5} = {F9DB9323-C919-49E8-8F96-B923D2F42E60} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {DABA3C65-9D74-4EB6-9B1C-730328710EAD} diff --git a/samples/AuthRemoteFormsAuth/AuthRemoteFormsAuthAppHost/AuthRemoteFormsAuthAppHost.csproj b/samples/AuthRemoteFormsAuth/AuthRemoteFormsAuthAppHost/AuthRemoteFormsAuthAppHost.csproj index 108d000bbb..98d8bb2c29 100644 --- a/samples/AuthRemoteFormsAuth/AuthRemoteFormsAuthAppHost/AuthRemoteFormsAuthAppHost.csproj +++ b/samples/AuthRemoteFormsAuth/AuthRemoteFormsAuthAppHost/AuthRemoteFormsAuthAppHost.csproj @@ -1,4 +1,4 @@ - + @@ -16,6 +16,7 @@ + diff --git a/samples/AuthRemoteFormsAuth/AuthRemoteFormsAuthAppHost/Program.cs b/samples/AuthRemoteFormsAuth/AuthRemoteFormsAuthAppHost/Program.cs index 581384ac47..79bc6ba1e3 100644 --- a/samples/AuthRemoteFormsAuth/AuthRemoteFormsAuthAppHost/Program.cs +++ b/samples/AuthRemoteFormsAuth/AuthRemoteFormsAuthAppHost/Program.cs @@ -1,19 +1,14 @@ var builder = DistributedApplication.CreateBuilder(args); -var remoteApiKey = builder.AddParameter("apiKey", Guid.NewGuid().ToString(), secret: true); - var frameworkApp = builder.AddIISExpress("iis") .AddSiteProject("framework") .WithDefaultIISExpressEndpoints() .WithOtlpExporter() - .WithEnvironment("RemoteApp__ApiKey", remoteApiKey) .WithHttpHealthCheck(); var coreApp = builder.AddProject("core") - .WithEnvironment("RemoteApp__ApiKey", remoteApiKey) - .WithReference(frameworkApp) - .WithEnvironment("RemoteApp__Url", frameworkApp.GetEndpoint("https")) .WithHttpHealthCheck() - .WaitFor(frameworkApp); + .WaitFor(frameworkApp) + .WithIncrementalMigrationFallback(frameworkApp, options => options.RemoteAuthentication = RemoteAuthentication.DefaultScheme); builder.Build().Run(); diff --git a/samples/AuthRemoteFormsAuth/AuthRemoteFormsAuthCore/AuthRemoteFormsAuthCore.csproj b/samples/AuthRemoteFormsAuth/AuthRemoteFormsAuthCore/AuthRemoteFormsAuthCore.csproj index b619f5f112..6e3a0b6495 100644 --- a/samples/AuthRemoteFormsAuth/AuthRemoteFormsAuthCore/AuthRemoteFormsAuthCore.csproj +++ b/samples/AuthRemoteFormsAuth/AuthRemoteFormsAuthCore/AuthRemoteFormsAuthCore.csproj @@ -4,9 +4,6 @@ enable enable - - - diff --git a/samples/AuthRemoteFormsAuth/AuthRemoteFormsAuthCore/Program.cs b/samples/AuthRemoteFormsAuth/AuthRemoteFormsAuthCore/Program.cs index 606aa127b9..368b88c578 100644 --- a/samples/AuthRemoteFormsAuth/AuthRemoteFormsAuthCore/Program.cs +++ b/samples/AuthRemoteFormsAuth/AuthRemoteFormsAuthCore/Program.cs @@ -1,21 +1,10 @@ var builder = WebApplication.CreateBuilder(args); builder.AddServiceDefaults(); +builder.AddSystemWebAdapters(); -builder.Services.AddReverseProxy(); - -// Add services to the container. builder.Services.AddControllersWithViews(); -// Add System.Web adapter services, including registering remote app authentication -builder.Services.AddSystemWebAdapters() - .AddRemoteAppClient(options => - { - options.RemoteAppUrl = new(builder.Configuration["RemoteApp:Url"]!); - options.ApiKey = builder.Configuration["RemoteApp:ApiKey"]!; - }) - .AddAuthenticationClient(true); - var app = builder.Build(); // Configure the HTTP request pipeline. @@ -40,13 +29,9 @@ name: "default", pattern: "{controller=Home}/{action=Index}/{id?}"); -// Configure the reverse proxy to forward all unhandled requests to the remote app -app.MapForwarder("/{**catch-all}", app.Configuration["RemoteApp:Url"]!) - - // If there is a route locally, we want to ensure that is used by default, but otherwise we'll forward - .WithOrder(int.MaxValue) +app.MapDefaultEndpoints(); - // If we're going to forward the request, there is no need to run any of the middleware after routing +app.MapRemoteAppFallback() .ShortCircuit(); app.Run(); diff --git a/samples/AuthRemoteFormsAuth/AuthRemoteFormsAuthFramework/Global.asax.cs b/samples/AuthRemoteFormsAuth/AuthRemoteFormsAuthFramework/Global.asax.cs index 0d328c8e15..6c52d99eb0 100644 --- a/samples/AuthRemoteFormsAuth/AuthRemoteFormsAuthFramework/Global.asax.cs +++ b/samples/AuthRemoteFormsAuth/AuthRemoteFormsAuthFramework/Global.asax.cs @@ -19,11 +19,8 @@ protected void Application_Start() builder.AddServiceDefaults(); builder.RegisterWebObjectActivator(); - builder.Services.AddSystemAdapters() - .AddVirtualizedContentDirectories() - .AddProxySupport(options => options.UseForwardedHeaders = true) - .AddRemoteAppServer(builder.Configuration.GetSection("RemoteApp").Bind) - .AddAuthenticationServer(); + builder.AddSystemWebAdapters() + .AddVirtualizedContentDirectories(); }); RouteConfig.RegisterRoutes(RouteTable.Routes); diff --git a/samples/AuthRemoteIdentity/AuthRemoteIdentityAppHost/AuthRemoteIdentityAppHost.csproj b/samples/AuthRemoteIdentity/AuthRemoteIdentityAppHost/AuthRemoteIdentityAppHost.csproj index 01420d59a7..9c8ef35d9e 100644 --- a/samples/AuthRemoteIdentity/AuthRemoteIdentityAppHost/AuthRemoteIdentityAppHost.csproj +++ b/samples/AuthRemoteIdentity/AuthRemoteIdentityAppHost/AuthRemoteIdentityAppHost.csproj @@ -1,4 +1,4 @@ - + @@ -17,6 +17,7 @@ + diff --git a/samples/AuthRemoteIdentity/AuthRemoteIdentityAppHost/Program.cs b/samples/AuthRemoteIdentity/AuthRemoteIdentityAppHost/Program.cs index 6a450af997..c98ab62930 100644 --- a/samples/AuthRemoteIdentity/AuthRemoteIdentityAppHost/Program.cs +++ b/samples/AuthRemoteIdentity/AuthRemoteIdentityAppHost/Program.cs @@ -3,26 +3,21 @@ var builder = DistributedApplication.CreateBuilder(args); -var remoteApiKey = builder.AddParameter("apiKey", Guid.NewGuid().ToString(), secret: true); - -var iisExpress = builder.AddIISExpress("iis"); - var db = builder.AddSqlServer("identityserver") .WithLifetime(ContainerLifetime.Persistent) .AddDatabase("identity"); -var frameworkApp = iisExpress.AddSiteProject("framework") +var frameworkApp = builder.AddIISExpress("iis") + .AddSiteProject("framework") .WithDefaultIISExpressEndpoints() - .WithEnvironment("RemoteApp__ApiKey", remoteApiKey) .WithReference(db, connectionName: "DefaultConnection") .WithOtlpExporter() .WaitFor(db) .WithHttpHealthCheck(); var coreApp = builder.AddProject("core") - .WithEnvironment("RemoteApp__ApiKey", remoteApiKey) - .WithEnvironment("RemoteApp__Url", frameworkApp.GetEndpoint("https")) .WithHttpHealthCheck() - .WaitFor(frameworkApp); + .WaitFor(frameworkApp) + .WithIncrementalMigrationFallback(frameworkApp, options => options.RemoteAuthentication = RemoteAuthentication.DefaultScheme); builder.Build().Run(); diff --git a/samples/AuthRemoteIdentity/AuthRemoteIdentityCore/AuthRemoteIdentityCore.csproj b/samples/AuthRemoteIdentity/AuthRemoteIdentityCore/AuthRemoteIdentityCore.csproj index b619f5f112..6e3a0b6495 100644 --- a/samples/AuthRemoteIdentity/AuthRemoteIdentityCore/AuthRemoteIdentityCore.csproj +++ b/samples/AuthRemoteIdentity/AuthRemoteIdentityCore/AuthRemoteIdentityCore.csproj @@ -4,9 +4,6 @@ enable enable - - - diff --git a/samples/AuthRemoteIdentity/AuthRemoteIdentityCore/Controllers/HomeController.cs b/samples/AuthRemoteIdentity/AuthRemoteIdentityCore/Controllers/HomeController.cs index cb49577e8c..68a95bc42f 100644 --- a/samples/AuthRemoteIdentity/AuthRemoteIdentityCore/Controllers/HomeController.cs +++ b/samples/AuthRemoteIdentity/AuthRemoteIdentityCore/Controllers/HomeController.cs @@ -1,5 +1,7 @@ +using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.SystemWebAdapters.Authentication; using MvcCoreApp.Models; using System.Diagnostics; @@ -24,7 +26,7 @@ public IActionResult Privacy() return View(); } - [Authorize(AuthenticationSchemes = "Remote")] + [Authorize(AuthenticationSchemes = RemoteAppAuthenticationDefaults.AuthenticationScheme)] public IActionResult UserInfo() { return View(); diff --git a/samples/AuthRemoteIdentity/AuthRemoteIdentityCore/Program.cs b/samples/AuthRemoteIdentity/AuthRemoteIdentityCore/Program.cs index 6f8b31f490..8673032d91 100644 --- a/samples/AuthRemoteIdentity/AuthRemoteIdentityCore/Program.cs +++ b/samples/AuthRemoteIdentity/AuthRemoteIdentityCore/Program.cs @@ -17,6 +17,7 @@ var builder = WebApplication.CreateBuilder(); builder.AddServiceDefaults(); +builder.AddSystemWebAdapters(); // These must match the data protection settings in MvcApp Startup.Auth.cs for cookie sharing to work var sharedApplicationName = "CommonMvcAppName"; @@ -27,17 +28,8 @@ builder.Services.AddAuthentication() .AddCookie("SharedCookie", options => options.Cookie.Name = ".AspNet.ApplicationCookie"); -builder.Services.AddReverseProxy(); - // Add services to the container. builder.Services.AddControllersWithViews(); -builder.Services.AddSystemWebAdapters() - .AddRemoteAppClient(options => - { - options.RemoteAppUrl = new(builder.Configuration["RemoteApp:Url"]!); - options.ApiKey = builder.Configuration["RemoteApp:ApiKey"]!; - }) - .AddAuthenticationClient(true); var app = builder.Build(); @@ -61,13 +53,7 @@ app.MapDefaultControllerRoute(); -// Configure the the reverse proxy to forward all unhandled requests to the remote app -app.MapForwarder("/{**catch-all}", app.Configuration["RemoteApp:Url"]!) - - // If there is a route locally, we want to ensure that is used by default, but otherwise we'll forward - .WithOrder(int.MaxValue) - - // If we're going to forward the request, there is no need to run any of the middleware after routing +app.MapRemoteAppFallback() .ShortCircuit(); app.Run(); diff --git a/samples/AuthRemoteIdentity/AuthRemoteIdentityFramework/Global.asax.cs b/samples/AuthRemoteIdentity/AuthRemoteIdentityFramework/Global.asax.cs index 798c803ba8..cc0f188d32 100644 --- a/samples/AuthRemoteIdentity/AuthRemoteIdentityFramework/Global.asax.cs +++ b/samples/AuthRemoteIdentity/AuthRemoteIdentityFramework/Global.asax.cs @@ -19,11 +19,8 @@ protected void Application_Start() builder.AddServiceDefaults(); builder.RegisterWebObjectActivator(); - builder.Services.AddSystemAdapters() - .AddVirtualizedContentDirectories() - .AddProxySupport(options => options.UseForwardedHeaders = true) - .AddRemoteAppServer(builder.Configuration.GetSection("RemoteApp").Bind) - .AddAuthenticationServer(); + builder.AddSystemWebAdapters() + .AddVirtualizedContentDirectories(); }); AreaRegistration.RegisterAllAreas(); diff --git a/samples/Directory.Packages.props b/samples/Directory.Packages.props index 1486540749..6654ed697f 100644 --- a/samples/Directory.Packages.props +++ b/samples/Directory.Packages.props @@ -6,7 +6,7 @@ - + @@ -61,6 +61,5 @@ - - \ No newline at end of file + diff --git a/samples/MachineKey/MachineKeyAppHost/MachineKeyAppHost.csproj b/samples/MachineKey/MachineKeyAppHost/MachineKeyAppHost.csproj index a6658d18ba..8bf580b9f4 100644 --- a/samples/MachineKey/MachineKeyAppHost/MachineKeyAppHost.csproj +++ b/samples/MachineKey/MachineKeyAppHost/MachineKeyAppHost.csproj @@ -17,6 +17,7 @@ + diff --git a/samples/Modules/ModulesAppHost/ModulesAppHost.csproj b/samples/Modules/ModulesAppHost/ModulesAppHost.csproj index b0bfdccf0b..eaf470e359 100644 --- a/samples/Modules/ModulesAppHost/ModulesAppHost.csproj +++ b/samples/Modules/ModulesAppHost/ModulesAppHost.csproj @@ -17,6 +17,7 @@ + diff --git a/samples/ServiceDefaults/Extensions.cs b/samples/ServiceDefaults/Extensions.cs index a732dc5698..3900c13800 100644 --- a/samples/ServiceDefaults/Extensions.cs +++ b/samples/ServiceDefaults/Extensions.cs @@ -7,6 +7,10 @@ using OpenTelemetry; using OpenTelemetry.Metrics; using OpenTelemetry.Trace; +using System.Diagnostics.CodeAnalysis; +using Microsoft.Extensions.Options; + + #if NET using Microsoft.AspNetCore.Builder; @@ -126,13 +130,13 @@ public static WebApplication MapDefaultEndpoints(this WebApplication app) if (app.Environment.IsDevelopment()) { // All health checks must pass for app to be considered ready to accept traffic after starting - app.MapHealthChecks("/health"); + app.MapHealthChecks("/health").ShortCircuit(); // 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") - }); + }).ShortCircuit(); } return app; diff --git a/samples/ServiceDefaults/Samples.ServiceDefaults.csproj b/samples/ServiceDefaults/Samples.ServiceDefaults.csproj index 0cad0e40eb..b420eaf219 100644 --- a/samples/ServiceDefaults/Samples.ServiceDefaults.csproj +++ b/samples/ServiceDefaults/Samples.ServiceDefaults.csproj @@ -22,8 +22,6 @@ - - @@ -33,7 +31,10 @@ - + + + + diff --git a/samples/SessionRemote/SessionRemoteAppHost/Program.cs b/samples/SessionRemote/SessionRemoteAppHost/Program.cs index 30de519a7c..b11ddb4cf0 100644 --- a/samples/SessionRemote/SessionRemoteAppHost/Program.cs +++ b/samples/SessionRemote/SessionRemoteAppHost/Program.cs @@ -1,18 +1,14 @@ var builder = DistributedApplication.CreateBuilder(args); -var remoteApiKey = builder.AddParameter("apiKey", Guid.NewGuid().ToString(), secret: true); - var frameworkApp = builder.AddIISExpress("iis") .AddSiteProject("framework") .WithDefaultIISExpressEndpoints() .WithOtlpExporter() - .WithEnvironment("RemoteApp__ApiKey", remoteApiKey) .WithHttpHealthCheck(path: "/framework"); var coreApp = builder.AddProject("core") - .WithEnvironment("RemoteApp__ApiKey", remoteApiKey) - .WithEnvironment("RemoteApp__Url", frameworkApp.GetEndpoint("https")) .WithHttpHealthCheck() - .WaitFor(frameworkApp); + .WaitFor(frameworkApp) + .WithIncrementalMigrationFallback(frameworkApp, options => options.RemoteSession = RemoteSession.Enabled); builder.Build().Run(); diff --git a/samples/SessionRemote/SessionRemoteAppHost/SessionRemoteAppHost.csproj b/samples/SessionRemote/SessionRemoteAppHost/SessionRemoteAppHost.csproj index 1167927a9b..9b34fd4a3a 100644 --- a/samples/SessionRemote/SessionRemoteAppHost/SessionRemoteAppHost.csproj +++ b/samples/SessionRemote/SessionRemoteAppHost/SessionRemoteAppHost.csproj @@ -16,6 +16,7 @@ + diff --git a/samples/SessionRemote/SessionRemoteCore/Program.cs b/samples/SessionRemote/SessionRemoteCore/Program.cs index 1c72fc5ca1..a2d9b12fea 100644 --- a/samples/SessionRemote/SessionRemoteCore/Program.cs +++ b/samples/SessionRemote/SessionRemoteCore/Program.cs @@ -5,10 +5,7 @@ var builder = WebApplication.CreateBuilder(args); builder.AddServiceDefaults(); - -builder.Services.AddReverseProxy(); - -builder.Services.AddSystemWebAdapters() +builder.AddSystemWebAdapters() .AddSessionSerializer(options => { options.ThrowOnUnknownSessionKey = false; @@ -16,13 +13,7 @@ .AddJsonSessionSerializer(options => { options.RegisterKey("CoreCount"); - }) - .AddRemoteAppClient(options => - { - options.ApiKey = builder.Configuration["RemoteApp:ApiKey"]!; - options.RemoteAppUrl = new(builder.Configuration["RemoteApp:Url"]); - }) - .AddSessionClient(); + }); var app = builder.Build(); @@ -44,13 +35,7 @@ return session.Cast().Select(key => new { Key = key, Value = session[key] }); }).RequireSystemWebAdapterSession(); -// Configure the the reverse proxy to forward all unhandled requests to the remote app -app.MapForwarder("/{**catch-all}", app.Configuration["RemoteApp:Url"]!) - - // If there is a route locally, we want to ensure that is used by default, but otherwise we'll forward - .WithOrder(int.MaxValue) - - // If we're going to forward the request, there is no need to run any of the middleware after routing +app.MapRemoteAppFallback() .ShortCircuit(); app.Run(); diff --git a/samples/SessionRemote/SessionRemoteCore/SessionRemoteCore.csproj b/samples/SessionRemote/SessionRemoteCore/SessionRemoteCore.csproj index 154c818c7c..f4ef547619 100644 --- a/samples/SessionRemote/SessionRemoteCore/SessionRemoteCore.csproj +++ b/samples/SessionRemote/SessionRemoteCore/SessionRemoteCore.csproj @@ -4,9 +4,6 @@ enable enable - - - diff --git a/samples/SessionRemote/SessionRemoteFramework/Global.asax.cs b/samples/SessionRemote/SessionRemoteFramework/Global.asax.cs index d5c65e271c..c212e14099 100644 --- a/samples/SessionRemote/SessionRemoteFramework/Global.asax.cs +++ b/samples/SessionRemote/SessionRemoteFramework/Global.asax.cs @@ -16,17 +16,11 @@ protected void Application_Start() HttpApplicationHost.RegisterHost(builder => { builder.AddServiceDefaults(); - builder.Services.AddSystemAdapters() - .AddProxySupport(options => options.UseForwardedHeaders = true) - .AddSessionSerializer(options => - { - }) + builder.AddSystemWebAdapters() .AddJsonSessionSerializer(options => { options.RegisterKey("CoreCount"); - }) - .AddRemoteAppServer(builder.Configuration.GetSection("RemoteApp").Bind) - .AddSessionServer(); + }); }); } diff --git a/samples/WebFormsToBlazor/WebFormsToBlazorAppHost/Program.cs b/samples/WebFormsToBlazor/WebFormsToBlazorAppHost/Program.cs index e362c7e6fd..02c0b12fff 100644 --- a/samples/WebFormsToBlazor/WebFormsToBlazorAppHost/Program.cs +++ b/samples/WebFormsToBlazor/WebFormsToBlazorAppHost/Program.cs @@ -1,18 +1,14 @@ var builder = DistributedApplication.CreateBuilder(args); -var remoteApiKey = builder.AddParameter("apiKey", Guid.NewGuid().ToString(), secret: true); - var frameworkApp = builder.AddIISExpress("iis") .AddSiteProject("framework") .WithDefaultIISExpressEndpoints() .WithOtlpExporter() - .WithEnvironment("RemoteApp__ApiKey", remoteApiKey) .WithHttpHealthCheck(); var coreApp = builder.AddProject("core") - .WithEnvironment("RemoteApp__ApiKey", remoteApiKey) - .WithEnvironment("RemoteApp__Url", frameworkApp.GetEndpoint("https")) .WithHttpHealthCheck() - .WaitFor(frameworkApp); + .WaitFor(frameworkApp) + .WithIncrementalMigrationFallback(frameworkApp, options => options.RemoteSession = RemoteSession.Enabled); builder.Build().Run(); diff --git a/samples/WebFormsToBlazor/WebFormsToBlazorAppHost/WebFormsToBlazorAppHost.csproj b/samples/WebFormsToBlazor/WebFormsToBlazorAppHost/WebFormsToBlazorAppHost.csproj index ea95ea5691..6161c000d4 100644 --- a/samples/WebFormsToBlazor/WebFormsToBlazorAppHost/WebFormsToBlazorAppHost.csproj +++ b/samples/WebFormsToBlazor/WebFormsToBlazorAppHost/WebFormsToBlazorAppHost.csproj @@ -16,6 +16,7 @@ + diff --git a/samples/WebFormsToBlazor/WebFormsToBlazorCore/Program.cs b/samples/WebFormsToBlazor/WebFormsToBlazorCore/Program.cs index 6adb246796..41913e19d8 100644 --- a/samples/WebFormsToBlazor/WebFormsToBlazorCore/Program.cs +++ b/samples/WebFormsToBlazor/WebFormsToBlazorCore/Program.cs @@ -4,23 +4,19 @@ var builder = WebApplication.CreateBuilder(args); +builder.AddServiceDefaults(); +builder.AddSystemWebAdapters() + .AddJsonSessionSerializer(options => + { + options.RegisterKey("test-value"); + }); + // Add YARP builder.Services.AddHttpForwarder(); // Add constraint to redirect .axd files to WebForms builder.Services.AddRouting(options => options.ConstraintMap.Add("isAxdFile", typeof(AxdConstraint))); // Add System Web Adapters and setup session -builder.Services.AddSystemWebAdapters() - .AddJsonSessionSerializer(options => - { - options.RegisterKey("test-value"); - }) - .AddRemoteAppClient(options => - { - options.RemoteAppUrl = new(builder.Configuration["RemoteApp:Url"]); - options.ApiKey = builder.Configuration["RemoteApp:ApiKey"]!; - }) - .AddSessionClient(); // Add services to the container. builder.Services.AddRazorComponents() @@ -53,10 +49,15 @@ .AddInteractiveServerRenderMode() .RequireSystemWebAdapterSession(); -app.MapForwarder("/Scripts/{**catch-all}", app.Configuration["RemoteApp:Url"]).Add(static builder => ((RouteEndpointBuilder)builder).Order = int.MaxValue); -app.MapForwarder("/Content/{**catch-all}", app.Configuration["RemoteApp:Url"]).Add(static builder => ((RouteEndpointBuilder)builder).Order = int.MaxValue); -app.MapForwarder("/bundles/{**catch-all}", app.Configuration["RemoteApp:Url"]).Add(static builder => ((RouteEndpointBuilder)builder).Order = int.MaxValue); -app.MapForwarder("/About", app.Configuration["RemoteApp:Url"]).Add(static builder => ((RouteEndpointBuilder)builder).Order = int.MaxValue); -app.MapForwarder("/Contact", app.Configuration["RemoteApp:Url"]).Add(static builder => ((RouteEndpointBuilder)builder).Order = int.MaxValue); -app.MapForwarder("/{route:isAxdFile}", app.Configuration["RemoteApp:Url"]).Add(static builder => ((RouteEndpointBuilder)builder).Order = int.MaxValue); +var fallback = app.MapGroup(""); + +fallback.ShortCircuit(); + +fallback.MapRemoteAppFallback("/Scripts/{**catch-all}"); +fallback.MapRemoteAppFallback("/Content/{**catch-all}"); +fallback.MapRemoteAppFallback("/bundles/{**catch-all}"); +fallback.MapRemoteAppFallback("/About"); +fallback.MapRemoteAppFallback("/Contact"); +fallback.MapRemoteAppFallback("/{route:isAxdFile}"); + app.Run(); diff --git a/samples/WebFormsToBlazor/WebFormsToBlazorCore/WebFormsToBlazorCore.csproj b/samples/WebFormsToBlazor/WebFormsToBlazorCore/WebFormsToBlazorCore.csproj index f883bb8a80..e3e18df5a9 100644 --- a/samples/WebFormsToBlazor/WebFormsToBlazorCore/WebFormsToBlazorCore.csproj +++ b/samples/WebFormsToBlazor/WebFormsToBlazorCore/WebFormsToBlazorCore.csproj @@ -6,7 +6,6 @@ - diff --git a/samples/WebFormsToBlazor/WebFormsToBlazorFramework/Global.asax.cs b/samples/WebFormsToBlazor/WebFormsToBlazorFramework/Global.asax.cs index bbcddbd34a..f968286f8d 100644 --- a/samples/WebFormsToBlazor/WebFormsToBlazorFramework/Global.asax.cs +++ b/samples/WebFormsToBlazor/WebFormsToBlazorFramework/Global.asax.cs @@ -17,15 +17,12 @@ protected void Application_Start() { builder.AddServiceDefaults(); - builder.Services.AddSystemAdapters() + builder.AddSystemWebAdapters() .AddVirtualizedContentDirectories() - .AddProxySupport(options => options.UseForwardedHeaders = true) .AddJsonSessionSerializer(options => { options.RegisterKey("test-value"); - }) - .AddRemoteAppServer(builder.Configuration.GetSection("RemoteApp").Bind) - .AddSessionServer(); + }); }); // Code that runs on application startup diff --git a/src/Aspire.Hosting.IncrementalMigration/Aspire.Hosting.IncrementalMigration.csproj b/src/Aspire.Hosting.IncrementalMigration/Aspire.Hosting.IncrementalMigration.csproj new file mode 100644 index 0000000000..75f902ece9 --- /dev/null +++ b/src/Aspire.Hosting.IncrementalMigration/Aspire.Hosting.IncrementalMigration.csproj @@ -0,0 +1,14 @@ + + + + net8.0 + enable + enable + true + true + + + + + + diff --git a/src/Aspire.Hosting.IncrementalMigration/AspireConstants.Shared.cs b/src/Aspire.Hosting.IncrementalMigration/AspireConstants.Shared.cs new file mode 100644 index 0000000000..3d29469887 --- /dev/null +++ b/src/Aspire.Hosting.IncrementalMigration/AspireConstants.Shared.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.AspNetCore.SystemWebAdapters; + +internal static partial class AspireConstants +{ + public const string RootKey = "SystemWebAdapters"; + public const string ProxyKey = RootKey + Separator + "Proxy"; + public const string ProxyKeyIsEnabled = ProxyKey + Separator + "UseForwardedHeaders"; + public const string RemoteKey = RootKey + Separator + "Remote"; + public const string RemoteApiKey = RemoteKey + Separator + "ApiKey"; + public const string RemoteUrl = RemoteKey + Separator + "RemoteAppUrl"; + public const string RemoteSessionKey = RemoteKey + Separator + "Session"; + public const string RemoteAuthKey = RemoteKey + Separator + "Authentication"; + public const string RemoteAuthIsDefaultScheme = RemoteAuthKey + Separator + "IsDefaultScheme"; + public const string IsEnabled = Separator + "IsEnabled"; +} diff --git a/src/Aspire.Hosting.IncrementalMigration/AspireConstants.cs b/src/Aspire.Hosting.IncrementalMigration/AspireConstants.cs new file mode 100644 index 0000000000..1274d9adbd --- /dev/null +++ b/src/Aspire.Hosting.IncrementalMigration/AspireConstants.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.AspNetCore.SystemWebAdapters; + +internal static partial class AspireConstants +{ + const string Separator = "__"; +} diff --git a/src/Aspire.Hosting.IncrementalMigration/IncrementalMigrationOptions.cs b/src/Aspire.Hosting.IncrementalMigration/IncrementalMigrationOptions.cs new file mode 100644 index 0000000000..f43f4e7c13 --- /dev/null +++ b/src/Aspire.Hosting.IncrementalMigration/IncrementalMigrationOptions.cs @@ -0,0 +1,10 @@ +namespace Aspire.Hosting; + +public class IncrementalMigrationOptions +{ + public RemoteAuthentication RemoteAuthentication { get; set; } = RemoteAuthentication.Disabled; + + public RemoteSession RemoteSession { get; set; } = RemoteSession.Disabled; + + public string RemoteAppEndpointName { get; set; } = "https"; +} diff --git a/src/Aspire.Hosting.IncrementalMigration/IncrementalMigrationResourceExtensions.cs b/src/Aspire.Hosting.IncrementalMigration/IncrementalMigrationResourceExtensions.cs new file mode 100644 index 0000000000..7155b4d935 --- /dev/null +++ b/src/Aspire.Hosting.IncrementalMigration/IncrementalMigrationResourceExtensions.cs @@ -0,0 +1,70 @@ +using System.Threading.Tasks.Sources; +using Aspire.Hosting; +using Aspire.Hosting.ApplicationModel; + +using static Microsoft.AspNetCore.SystemWebAdapters.AspireConstants; + +namespace Aspire.Hosting; + +public static class IncrementalMigrationResourceExtensions +{ + public static IResourceBuilder WithIncrementalMigrationFallback( + this IResourceBuilder coreApp, + IResourceBuilder frameworkApp, + Action? configureOptions = null, + IResourceBuilder? apiKey = null + ) + where TCore : IResourceWithEnvironment + where TFramework : IResourceWithEnvironment, IResourceWithEndpoints + { + ArgumentNullException.ThrowIfNull(coreApp); + ArgumentNullException.ThrowIfNull(frameworkApp); + + apiKey ??= coreApp.ApplicationBuilder.AddParameter($"{coreApp.Resource.Name}-{frameworkApp.Resource.Name}-remoteapp-apiKey", () => Guid.NewGuid().ToString(), secret: true); + + var options = new IncrementalMigrationOptions(); + configureOptions?.Invoke(options); + + coreApp.WithReferenceRelationship(frameworkApp.Resource); + + coreApp.WithEnvironment(ctx => + { + ctx.EnvironmentVariables[RemoteUrl] = frameworkApp.Resource.GetEndpoint(options.RemoteAppEndpointName); + ctx.EnvironmentVariables[RemoteApiKey] = apiKey; + + if (options.RemoteSession == RemoteSession.Enabled) + { + ctx.EnvironmentVariables[RemoteSessionKey + IsEnabled] = true; + } + + if (options.RemoteAuthentication != RemoteAuthentication.Disabled) + { + ctx.EnvironmentVariables[RemoteAuthKey + IsEnabled] = true; + + if (options.RemoteAuthentication == RemoteAuthentication.DefaultScheme) + { + ctx.EnvironmentVariables[RemoteAuthIsDefaultScheme] = true; + } + } + }); + + frameworkApp.WithEnvironment(ctx => + { + ctx.EnvironmentVariables[RemoteApiKey] = apiKey; + + ctx.EnvironmentVariables[ProxyKeyIsEnabled] = true; + + if (options.RemoteSession == RemoteSession.Enabled) + { + ctx.EnvironmentVariables[RemoteSessionKey + IsEnabled] = true; + } + + if (options.RemoteAuthentication != RemoteAuthentication.Disabled) + { + ctx.EnvironmentVariables[RemoteAuthKey + IsEnabled] = true; + } + }); + + return coreApp; + } +} diff --git a/src/Aspire.Hosting.IncrementalMigration/RemoteAuthentication.cs b/src/Aspire.Hosting.IncrementalMigration/RemoteAuthentication.cs new file mode 100644 index 0000000000..d3ae038e7c --- /dev/null +++ b/src/Aspire.Hosting.IncrementalMigration/RemoteAuthentication.cs @@ -0,0 +1,8 @@ +namespace Aspire.Hosting; + +public enum RemoteAuthentication +{ + Disabled, + Enabled, + DefaultScheme, +} diff --git a/src/Aspire.Hosting.IncrementalMigration/RemoteSession.cs b/src/Aspire.Hosting.IncrementalMigration/RemoteSession.cs new file mode 100644 index 0000000000..1604238021 --- /dev/null +++ b/src/Aspire.Hosting.IncrementalMigration/RemoteSession.cs @@ -0,0 +1,7 @@ +namespace Aspire.Hosting; + +public enum RemoteSession +{ + Disabled, + Enabled, +} diff --git a/src/Aspire.Microsoft.AspNetCore.SystemWebAdapters/Aspire.Microsoft.AspNetCore.SystemWebAdapters.csproj b/src/Aspire.Microsoft.AspNetCore.SystemWebAdapters/Aspire.Microsoft.AspNetCore.SystemWebAdapters.csproj new file mode 100644 index 0000000000..94c697fc7a --- /dev/null +++ b/src/Aspire.Microsoft.AspNetCore.SystemWebAdapters/Aspire.Microsoft.AspNetCore.SystemWebAdapters.csproj @@ -0,0 +1,22 @@ + + + + net8.0;net472 + enable + enable + true + true + + + + + + + + + + + + + + diff --git a/src/Aspire.Microsoft.AspNetCore.SystemWebAdapters/AspireConstants.cs b/src/Aspire.Microsoft.AspNetCore.SystemWebAdapters/AspireConstants.cs new file mode 100644 index 0000000000..44d9cc06aa --- /dev/null +++ b/src/Aspire.Microsoft.AspNetCore.SystemWebAdapters/AspireConstants.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.AspNetCore.SystemWebAdapters; + +internal static partial class AspireConstants +{ + const string Separator = ":"; +} diff --git a/src/Aspire.Microsoft.AspNetCore.SystemWebAdapters/AspireRemoteAppExtensions.cs b/src/Aspire.Microsoft.AspNetCore.SystemWebAdapters/AspireRemoteAppExtensions.cs new file mode 100644 index 0000000000..a531a097f4 --- /dev/null +++ b/src/Aspire.Microsoft.AspNetCore.SystemWebAdapters/AspireRemoteAppExtensions.cs @@ -0,0 +1,36 @@ +#if NET + +using System.Diagnostics.CodeAnalysis; +using Microsoft.AspNetCore.SystemWebAdapters; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Builder; + +namespace Microsoft.AspNetCore.Routing; + +public static class AspireRemoteAppExtensions +{ + public static IEndpointConventionBuilder MapRemoteAppFallback(this IEndpointRouteBuilder app, [StringSyntax("Route")] string? pattern = "/{**catch-all}") + { + ArgumentNullException.ThrowIfNull(app); + ArgumentNullException.ThrowIfNull(pattern); + + var url = app.ServiceProvider.GetRequiredService>().Value.RemoteAppUrl.OriginalString; + + return app.MapForwarder(pattern, url) + + // If there is a route locally, we want to ensure that is used by default, but otherwise we'll forward + .WithOrder(int.MaxValue) + + // We want to mark this endpoint as a fallback for remote requests in case we need to identify it later + .WithMetadata(FallbackMetadata.Instance); + } + + public static IApplicationBuilder UseWhenLocal(this IApplicationBuilder builder, Action configuration) + => builder.UseWhen(static context => !context.IsHandledRemotely(), configuration); + + public static IApplicationBuilder UseWhenRemote(this IApplicationBuilder builder, Action configuration) + => builder.UseWhen(static context => context.IsHandledRemotely(), configuration); +} +#endif diff --git a/src/Aspire.Microsoft.AspNetCore.SystemWebAdapters/AspireRemoteAppHttpContextExtensions.cs b/src/Aspire.Microsoft.AspNetCore.SystemWebAdapters/AspireRemoteAppHttpContextExtensions.cs new file mode 100644 index 0000000000..60be86aacf --- /dev/null +++ b/src/Aspire.Microsoft.AspNetCore.SystemWebAdapters/AspireRemoteAppHttpContextExtensions.cs @@ -0,0 +1,10 @@ +#if NET + +namespace Microsoft.AspNetCore.Http; + +public static class AspireRemoteAppHttpContextExtensions +{ + public static bool IsHandledRemotely(this HttpContext context) + => context.GetEndpoint()?.Metadata.GetMetadata() is { }; +} +#endif diff --git a/src/Aspire.Microsoft.AspNetCore.SystemWebAdapters/AspireSystemWebAdapterExtensions.cs b/src/Aspire.Microsoft.AspNetCore.SystemWebAdapters/AspireSystemWebAdapterExtensions.cs new file mode 100644 index 0000000000..531217be9a --- /dev/null +++ b/src/Aspire.Microsoft.AspNetCore.SystemWebAdapters/AspireSystemWebAdapterExtensions.cs @@ -0,0 +1,77 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.AspNetCore.SystemWebAdapters; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; + +#if NET +using Microsoft.AspNetCore.Builder; +#else +using System.Web; +#endif + +using static Microsoft.AspNetCore.SystemWebAdapters.AspireConstants; + +namespace Microsoft.Extensions.Hosting; + +public static class AspireSystemWebAdaptersExtensions +{ + public static ISystemWebAdapterBuilder AddSystemWebAdapters(this IHostApplicationBuilder builder) + { +#if NET + ArgumentNullException.ThrowIfNull(builder); +#else + if (builder is null) + { + throw new ArgumentNullException(nameof(builder)); + } +#endif + + var config = builder.Configuration; + var adapters = builder.Services.AddSystemWebAdapters(); + +#if NET + builder.Services.AddReverseProxy(); +#else + if (config.GetValue(ProxyKeyIsEnabled)) + { + adapters.AddProxySupport(config.GetSection(ProxyKey).Bind); + } +#endif + + if (config.GetValue(RemoteApiKey) is { }) + { +#if NET + var remoteConfig = adapters.AddRemoteAppClient(config.GetSection(RemoteKey).Bind); + + if (config.GetValue(RemoteSessionKey + IsEnabled)) + { + remoteConfig.AddSessionClient(config.GetSection(RemoteSessionKey).Bind); + } + + if (config.GetValue(RemoteAuthKey + IsEnabled)) + { + remoteConfig.AddAuthenticationClient(config.GetValue(RemoteAuthIsDefaultScheme), config.GetSection(RemoteAuthKey).Bind); + } +#else + var remoteConfig = adapters.AddRemoteAppServer(config.GetSection(RemoteKey).Bind); + + if (config.GetValue(RemoteSessionKey + IsEnabled)) + { + remoteConfig.AddSessionServer(config.GetSection(RemoteSessionKey).Bind); + } + + if (config.GetValue(RemoteAuthKey + IsEnabled)) + { + remoteConfig.AddAuthenticationServer(config.GetSection(RemoteAuthKey).Bind); + } +#endif + } + + return adapters; + } +} + diff --git a/src/Aspire.Microsoft.AspNetCore.SystemWebAdapters/FallbackMetadata.cs b/src/Aspire.Microsoft.AspNetCore.SystemWebAdapters/FallbackMetadata.cs new file mode 100644 index 0000000000..9fe89c70e8 --- /dev/null +++ b/src/Aspire.Microsoft.AspNetCore.SystemWebAdapters/FallbackMetadata.cs @@ -0,0 +1,9 @@ +#if NET + +namespace Microsoft.AspNetCore.Http; + +internal sealed class FallbackMetadata +{ + public static FallbackMetadata Instance { get; } = new(); +} +#endif diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props index 8641326dcb..3c218d91d0 100644 --- a/src/Directory.Packages.props +++ b/src/Directory.Packages.props @@ -4,6 +4,7 @@ true + @@ -14,5 +15,6 @@ + - + \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.SystemWebAdapters.FrameworkServices/SystemWebAdapterConfiguration.cs b/src/Microsoft.AspNetCore.SystemWebAdapters.FrameworkServices/SystemWebAdapterConfiguration.cs index ad8ae65a67..5d391cd9da 100644 --- a/src/Microsoft.AspNetCore.SystemWebAdapters.FrameworkServices/SystemWebAdapterConfiguration.cs +++ b/src/Microsoft.AspNetCore.SystemWebAdapters.FrameworkServices/SystemWebAdapterConfiguration.cs @@ -13,7 +13,7 @@ public static class SystemWebAdapterConfiguration { private const string Key = "system-web-adapter"; - [Obsolete("Prefer using HostedHttpApplication instead")] + [Obsolete("Prefer using HttpApplicationHost.RegisterHost(...) instead")] public static ISystemWebAdapterBuilder AddSystemWebAdapters(this HttpApplication application) { if (application is null) @@ -43,7 +43,7 @@ public static ISystemWebAdapterBuilder AddSystemWebAdapters(this HttpApplication if (application.Application[Key] is HttpApplicationHostBuilder existing) { - return existing.Services.AddSystemAdapters(); + return existing.Services.AddSystemWebAdapters(); } else { @@ -56,6 +56,9 @@ public static ISystemWebAdapterBuilder AddSystemWebAdapters(this HttpApplication } } + public static ISystemWebAdapterBuilder AddSystemWebAdapters(this IServiceCollection services) + => new SystemWebAdapterBuilder(services); + public static ISystemWebAdapterBuilder AddProxySupport(this ISystemWebAdapterBuilder builder, Action configure) { if (builder is null) diff --git a/src/Microsoft.AspNetCore.SystemWebAdapters.FrameworkServices/SystemWebAdapterExtensions.cs b/src/Microsoft.AspNetCore.SystemWebAdapters.FrameworkServices/WebObjectActivatorExtensions.cs similarity index 86% rename from src/Microsoft.AspNetCore.SystemWebAdapters.FrameworkServices/SystemWebAdapterExtensions.cs rename to src/Microsoft.AspNetCore.SystemWebAdapters.FrameworkServices/WebObjectActivatorExtensions.cs index 2c9c416517..2a82764037 100644 --- a/src/Microsoft.AspNetCore.SystemWebAdapters.FrameworkServices/SystemWebAdapterExtensions.cs +++ b/src/Microsoft.AspNetCore.SystemWebAdapters.FrameworkServices/WebObjectActivatorExtensions.cs @@ -2,8 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Reflection; -using System.Web; -using Microsoft.AspNetCore.SystemWebAdapters; using Microsoft.AspNetCore.SystemWebAdapters.Hosting; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; @@ -11,7 +9,7 @@ namespace System.Web; -public static partial class SystemWebAdapterExtensions +public static partial class WebObjectActivatorExtensions { public static HttpApplicationHostBuilder RegisterWebObjectActivator(this HttpApplicationHostBuilder builder) { @@ -25,9 +23,6 @@ public static HttpApplicationHostBuilder RegisterWebObjectActivator(this HttpApp return builder; } - public static ISystemWebAdapterBuilder AddSystemAdapters(this IServiceCollection services) - => new SystemWebAdapterBuilder(services); - private sealed partial class WebObjectActivatorHostServices : IServiceProvider, IHostedService { private const string ErrorMessage = "WebObjectActivator is already set and will not be overridden"; @@ -36,11 +31,6 @@ private sealed partial class WebObjectActivatorHostServices : IServiceProvider, public WebObjectActivatorHostServices(IServiceProvider services) { - if (HttpRuntime.WebObjectActivator is { }) - { - throw new InvalidOperationException("HttpRuntime.WebObjectActivator is already configured"); - } - _services = services; }