aspnetcore/migration/fx-to-core/inc/remote-app-setup.md
[!IMPORTANT] Framework and Core applications must use identical virtual directory layouts.
The virtual directory setup is used for route generation, authorization, and other services within the system. At this point, no reliable method has been found to enable different virtual directories due to how ASP.NET Framework works.
In some incremental upgrade scenarios, it's useful for the new ASP.NET Core app to be able to communicate with the original ASP.NET app.
Common scenarios this enables:
:::zone pivot="manual"
To enable the ASP.NET Core app to communicate with the ASP.NET app, it's necessary to make a couple small changes to each app.
You need to configure two configuration values in both applications:
RemoteAppApiKey: A key (required to be parseable as a GUID) that is shared between the two applications. This should be a GUID value like 12345678-1234-1234-1234-123456789012.RemoteAppUri: The URI of the remote ASP.NET Framework application (only required in the ASP.NET Core application configuration). This should be the full URL where the ASP.NET Framework app is hosted, such as https://localhost:44300 or https://myapp.example.com.[!IMPORTANT] The ASP.NET Framework application should be hosted with SSL enabled. In the remote app setup for incremental migration, it is not required to have direct access externally. It is recommended to only allow access from the client application via the proxy.
For ASP.NET Framework applications, add these values to your web.config in the <appSettings> section:
[!IMPORTANT] ASP.NET Framework stores its appSettings in
web.config. However, they can be retrieved from other sources (such as environment variables) with the use of configuration Builders. This makes sharing configuration values much easier between the local and remote applications in this setup.
<appSettings>
<add key="RemoteAppApiKey" value="..." />
</appSettings>
To configure the application to be available to handle the requests from the ASP.NET Core client, set up the following:
Install the NuGet package Microsoft.AspNetCore.SystemWebAdapters.FrameworkServices
Add the configuration code to the Application_Start method in your Global.asax.cs file:
protected void Application_Start()
{
HttpApplicationHost.RegisterHost(builder =>
{
builder.Services.AddSystemWebAdapters()
.AddRemoteAppServer(options =>
{
// ApiKey is a string representing a GUID
options.ApiKey = System.Configuration.ConfigurationManager.AppSettings["RemoteAppApiKey"];
});
});
// ...existing code...
}
Add the SystemWebAdapterModule module to the web.config if it wasn't already added by NuGet. This module configuration is required for IIS hosting scenarios. The SystemWebAdapterModule module is not added automatically when using SDK style projects for ASP.NET Core.
<modules>
+ <remove name="SystemWebAdapterModule" />
+ <add name="SystemWebAdapterModule" type="Microsoft.AspNetCore.SystemWebAdapters.SystemWebAdapterModule, Microsoft.AspNetCore.SystemWebAdapters.FrameworkServices" preCondition="managedHandler" />
</modules>
For ASP.NET Core applications, add these values to your appsettings.json:
{
"RemoteAppApiKey": "...",
"RemoteAppUri": "https://localhost:44300"
}
To set up the ASP.NET Core app to be able to send requests to the ASP.NET app, configure the remote app client by calling AddRemoteAppClient after registering System.Web adapter services with AddSystemWebAdapters.
Add this configuration to your Program.cs file:
builder.Services.AddSystemWebAdapters()
.AddRemoteAppClient(options =>
{
options.RemoteAppUrl = new(builder.Configuration["RemoteAppUri"]);
options.ApiKey = builder.Configuration["RemoteAppApiKey"];
});
With both the ASP.NET and ASP.NET Core apps updated, extension methods can now be used to set up remote app authentication or remote session, as needed.
To enable proxying from the ASP.NET Core application to the ASP.NET Framework application, you can set up a fallback route that forwards unmatched requests to the legacy application. This allows for a gradual migration where the ASP.NET Core app handles migrated functionality while falling back to the original app for unmigrated features.
Install the YARP (Yet Another Reverse Proxy) NuGet package following the latest guidance.
Add the required using statements to your Program.cs:
using Microsoft.Extensions.Options;
using Microsoft.AspNetCore.SystemWebAdapters;
Register the reverse proxy services in your Program.cs:
builder.Services.AddReverseProxy();
After building the app and configuring other middleware, add the fallback route mapping:
var app = builder.Build();
// Configure your other middleware here (authentication, routing, etc.)
// Map the fallback route
app.MapForwarder("/{**catch-all}", app.Services.GetRequiredService<IOptions<RemoteAppClientOptions>>().Value.RemoteAppUrl.OriginalString)
// Ensures this route has the lowest priority (runs last)
.WithOrder(int.MaxValue)
// Skips remaining middleware when this route matches
.ShortCircuit();
app.Run();
:::zone-end
:::zone pivot="aspire"
[!IMPORTANT] This integration is currently in preview and is published as a prerelease package on NuGet.org. When adding the package, enable prerelease packages in your tooling (for example, select
Include prereleasein Visual Studio's NuGet Package Manager or use the equivalent prerelease option with the .NET CLI).
Add Aspire orchestration for the ASP.NET Framework application
Add a new ASP.NET Core application to the solution and add it to your Aspire orchestration
Add the Aspire.Hosting.IncrementalMigration package to your AppHost project.
Configure IIS Express to locally host your framework application and configure incremental migration fallback using the incremental migration extensions:
var builder = DistributedApplication.CreateBuilder(args);
// Add ASP.NET Framework app with IIS Express
var frameworkApp = builder.AddIISExpressProject<Projects.FrameworkApplication>("framework");
// Add ASP.NET Core app with fallback to Framework app
var coreApp = builder.AddProject<Projects.CoreApplication>("core")
.WithIncrementalMigrationFallback(frameworkApp, options =>
{
options.RemoteSession = RemoteSession.Enabled;
options.RemoteAuthentication = RemoteAuthentication.DefaultScheme;
});
builder.Build().Run();
Configure the options of the incremental migration fallback for the scenarios you want to support.
Add the package Aspire.Microsoft.AspNetCore.SystemWebAdapters to your application.
Update the ServiceDefaults project to support .NET Framework. This is based off of the default ServiceDefaults and may differ if you have customized anything.
Update the target framework to multitarget:
- <TargetFramework>net10.0</TargetFramework>
+ <TargetFrameworks>net481;net10.0</TargetFrameworks>
Update the PackageReferences to account for the different frameworks:
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Http.Resilience" />
<PackageReference Include="Microsoft.Extensions.ServiceDiscovery" />
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" />
<PackageReference Include="OpenTelemetry.Extensions.Hosting" />
<PackageReference Include="OpenTelemetry.Instrumentation.Http" />
<PackageReference Include="OpenTelemetry.Instrumentation.Runtime" />
<PackageReference Include="OpenTelemetry.Instrumentation.SqlClient" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'net10.0' ">
<FrameworkReference Include="Microsoft.AspNetCore.App" />
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'net481' ">
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks" />
<PackageReference Include="OpenTelemetry.Instrumentation.AspNet" />
</ItemGroup>
To enable telemetry, update the metrics and tracing registrations:
builder.Services.AddOpenTelemetry()
.WithMetrics(metrics =>
{
metrics
+ #if NET
.AddAspNetCoreInstrumentation()
+ #else
+ .AddAspNetInstrumentation()
+ #endif
.AddSqlClientInstrumentation()
.AddHttpClientInstrumentation()
.AddRuntimeInstrumentation();
})
.WithTracing(tracing =>
{
tracing.AddSource(builder.Environment.ApplicationName)
+ #if NET
.AddAspNetCoreInstrumentation()
+ #else
+ .AddAspNetInstrumentation()
+ #endif
.AddSqlClientInstrumentation()
.AddHttpClientInstrumentation();
});
Disable the default endpoints as that only applies for ASP.NET Core:
+ #if NET
public static WebApplication MapDefaultEndpoints(this WebApplication app)
{
// Default endpoint registrations
}
+ #endif
For a complete, working example of a multi-targeted ServiceDefaults configuration that supports both ASP.NET Core and ASP.NET Framework, see the sample Extensions.cs file in the System.Web adapters repository: https://github.com/dotnet/systemweb-adapters/blob/main/samples/ServiceDefaults/Extensions.cs.
Reference the ServiceDefaults project
Add the configuration code to the Application_Start method in your Global.asax.cs file:
protected void Application_Start()
{
HttpApplicationHost.RegisterHost(builder =>
{
builder.AddServiceDefaults();
builder.Services.AddSystemWebAdapters();
});
}
Reference the ServiceDefaults project
Add the System.Web adapters in Programs.cs:
var builder = WebApplication.CreateBuilder();
builder.AddServiceDefaults();
+ builder.AddSystemWebAdapters();
...
var app = builder.Build();
...
+ // Must be placed after routing if manually added
+ app.UseSystemWebAdapters();
...
+ app.MapRemoteAppFallback()
+
+ // Optional, but recommended unless middleware is needed
+ .ShortCircuit();
app.Run();
:::zone-end
With this configuration:
.ShortCircuit() method prevents unnecessary middleware execution when forwarding requestsThis setup enables a seamless user experience during incremental migration, where users can access both migrated and legacy functionality through a single endpoint.