diff --git a/README.md b/README.md index 5c35c13d3..e424785a5 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# System.Web adapters for ASP.NET Core +# `System.Web` adapters for ASP.NET Core This project provides a collection of adapters that help migrating from `System.Web.dll` based ASP.NET projects to ASP.NET Core projects. The adapters currently include: @@ -7,15 +7,15 @@ This project provides a collection of adapters that help migrating from `System. - `Microsoft.AspNetCore.SystemWebAdapters.FrameworkServices`: Support for adding services to ASP.NET Framework applications to enable migration efforts - `Microsoft.AspNetCore.SystemWebAdapters.Abstractions`: A collection of abstractions shared between the ASP.NET Core and .NET Framework implementations, such as session serialization interfaces. -These adapters help enable large scale, incremental migration from ASP.NET to ASP.NET Core. For more details on incremental migration from ASP.NET to ASP.NET Core, please see the [documentation](docs). +These adapters help enable large scale, incremental migration from ASP.NET to ASP.NET Core. For more details on incremental migration from ASP.NET to ASP.NET Core, see [Incremental ASP.NET to ASP.NET Core Migration](https://learn.microsoft.com/aspnet/core/migration/inc/overview). ## Get started -Use the [Getting Started](docs/getting_started.md) guide in the docs to start using the System.Web adapters as part of an incremental migration from ASP.NET to ASP.NET Core. +Use the [Get started with incremental ASP.NET to ASP.NET Core migration](https://learn.microsoft.com/en-us/aspnet/core/migration/inc/start) guide in the docs to start using the `System.Web` adapters as part of an incremental migration from ASP.NET to ASP.NET Core. ## Set up -Below are the steps needed to start using the System.Web adapters with your ASP.NET project: +The following steps are needed to use the `System.Web` adapters with an ASP.NET project: 1. *Optional for nightly adapter builds*: Set up `NuGet.config` to point to the CI feed: ```xml diff --git a/docs/README.md b/docs/README.md deleted file mode 100644 index 27f1c1a44..000000000 --- a/docs/README.md +++ /dev/null @@ -1,86 +0,0 @@ -# Incremental ASP.NET to ASP.NET Core Migration - -Migrating an application from ASP.NET Framework to ASP.NET Core is a non-trivial action for the majority of large applications. These applications have often grown organically over time, incorporating new technologies as they come and are often composed of many legacy decisions. The aim of this repo is to provide tools and support for migrating these large applications with as little change as possible to ASP.NET Core. - -One of the larger challenges is the pervasive use of `System.Web.HttpContext` throughout a code base to access information about a request or update a response. Until now, in order to move forward, a large scale rewrite was required to remove this dependency. Now, the adapters in this repo provide a set of runtime helpers to access the types your code is expecting but in a way that will work on ASP.NET Core with little change. - -A complete migration may take a while depending on the size of the application. In order to continue deploying an application to production while working on migrating, the best pattern is to follow is the [Strangler Fig pattern](https://docs.microsoft.com/en-us/azure/architecture/patterns/strangler-fig). This pattern allows for continual development on the old system with an incremental approach to moving forward. This document will describe how to apply that pattern to an ASP.NET app migrating towards ASP.NET Core. - -To jump into the process, please see the [Getting Started](getting_started.md) guide. - -## Migration Journey - -When starting a migration journey, the application will be targeting ASP.NET Framework and running on Windows with its supporting libraries: - -```mermaid -flowchart LR; - external[External Traffic] --> framework[.NET Framework Application] - framework --- libraries[[Business logic]] -``` - -The first step is to introduce a new application based on ASP.NET Core that will become the entry point. Traffic will enter the core app and if the core app cannot match a route, it will proxy the request onto the .NET Framework Application via [YARP](https://microsoft.github.io/reverse-proxy/) and serve a response as the application has already been doing. Majority of code will continue to be in the .NET Framework Application, but the core app is now set up to start migrating routes to: - -```mermaid -flowchart LR; - external[External Traffic] --> core[ASP.NET Core Application] - core --- libraries - core -- YARP proxy --> framework[ \n\n.NET Framework Application\n\n\n] - framework --- libraries[[Business logic]] -``` - -In order to start moving over business logic that relies on `HttpContext`, the libraries need to be built against `Microsoft.AspNetCore.SystemWebAdapters`. This allows libraries to be built against .NET Framework, .NET Core, or .NET Standard 2.0. This will ensure that the libraries are using surface area that is available on both ASP.NET and ASP.NET Core: - -```mermaid -flowchart LR; - external[External Traffic] --> core[ASP.NET Core Application] - core -- Adapters --- libraries - core -- YARP proxy --> framework[ \n\n.NET Framework Application\n\n\n] - framework --- libraries[[Business logic]] -``` - -At this point, the journey is to focus on moving routes over one at a time. This could be WebAPI or MVC controllers (or even a single method from a controller), ASPX pages, handlers, or some other implementation of a route. If the route is available in the core app, it will then be matched and served from there. - -During this process, additional service and infrastructure will be identified that must be moved, in some way, to run on .NET Core. Some options include (listed in order of maintainability): - -1. Duplicate the code -2. Link the code in the new project -3. Move the code to shared libraries - -Over time, the core app will start processing more of the routes served than the .NET Framework Application: - -```mermaid -flowchart LR; - external[External Traffic] --> core[ \n\nASP.NET Core Application\n\n\n] - core -- Adapters --- libraries - core -- YARP proxy --> framework[.NET Framework Application] - framework --- libraries[[Business logic]] -``` - -During this process, you may have the route in both the ASP.NET Core and the ASP.NET Framework applications. This could allow you to perform some A/B testing to ensure functionality is as expected. - -Once the .NET Framework Application is no longer needed, it may be removed: - -```mermaid -flowchart LR; - external[External Traffic] --> core[ASP.NET Core Application] - core -- Adapters --- libraries[[Business logic]] -``` - -At this point, the application as a whole is running on the ASP.NET Core application stack, but is still using the adapters from this repo. At this point, the goal will be to remove the use of the adapters until the application is relying solely on the ASP.NET Core application framework: - -```mermaid -flowchart LR; - external[External Traffic] --> core[ASP.NET Core Application] - core --- libraries[[Business logic]] -``` - -## System.Web Adapters - -The `Microsoft.AspNetCore.SystemWebAdapters` is a collection of runtime helpers that will facilitate using old core written against `System.Web` while moving onto ASP.NET Core. - -The heart of the library is support for `System.Web.HttpContext`. This attempts to provide compatible behavior for what is found running on ASP.NET to expedite moving onto ASP.NET Core. There are a number of behaviors that ASP.NET provided that incur a performance cost if enabled on ASP.NET Core so must be opted into. - -For examples of scenarios where this is useful, see [here](adapters.md). - -For guidance around usage, please see [here](usage_guidance.md). - diff --git a/docs/adapters.md b/docs/adapters.md deleted file mode 100644 index 8f95fa6dc..000000000 --- a/docs/adapters.md +++ /dev/null @@ -1,51 +0,0 @@ -# System.Web Adapters - -The main use case of the adapters in this repo is to help developers who have taken a reliance on `System.Web` types within their class libraries as they want to move to ASP.NET Core. - -Let's take a look at an example using the proposed adapters moving from .NET Framework to ASP.NET Core. - -### ASP.NET Framework -Consider a controller that does something such as: - -```cs -public class SomeController : Controller -{ - public ActionResult Index() - { - SomeOtherClass.SomeMethod(HttpContext.Current); - } -} -``` - -which then has logic in a separate assembly passing that `HttpContext` around until finally, some inner method does some logic on it such as: - -```cs -public class Class2 -{ - public bool PerformSomeCheck(HttpContext context) - { - return context.Request.Headers["SomeHeader"] == "ExpectedValue"; - } -} -``` - -### ASP.NET Core - -In order to run the above logic in ASP.NET Core, a developer will need to add the `Microsoft.AspNetCore.SystemWebAdapters` package, that will enable the projects to work on both platforms. - -The libraries would need to be updated to understand the adapters, but it will be as simple as adding the package and recompiling. If these are the only dependencies a system has on `System.Web.dll`, then the libraries will be able to target .NET Standard to facilitate a simpler building process while migrating. - -The controller in ASP.NET Core will now look like this: - -```cs -public class SomeController : Controller -{ - [Route("/")] - public IActionResult Index() - { - SomeOtherClass.SomeMethod(Context); - } -} -``` - -Notice that since there's a `Controller.Context` property, they can pass that through, but it generally looks the same. Using implicit conversions, the `Microsoft.AspNetCore.Http.HttpContext` can be converted into the adapter that could then be passed around through the levels utilizing the code in the same way. diff --git a/docs/core.md b/docs/core.md deleted file mode 100644 index fa54d7463..000000000 --- a/docs/core.md +++ /dev/null @@ -1,45 +0,0 @@ -# ASP.NET Core Configuration - -In order to setup the adapters for usage within an ASP.NET Core app, the services must be registered and middleware must be inserted. The modified `Program.cs` looks like this: - -```cs -using Microsoft.AspNetCore.SystemWebAdapters; - -var builder = WebApplication.CreateBuilder(); - -// Add services to the container. -builder.Services.AddControllersWithViews(); -builder.Services.AddSystemWebAdapters() - .AddJsonSessionSerializer(options => ClassLibrary.SessionUtils.RegisterSessionKeys(options)) - .AddRemoteAppClient(options => - { - options.RemoteAppUrl = new(builder.Configuration["ReverseProxy:Clusters:fallbackCluster:Destinations:fallbackApp:Address"]); - options.ApiKey = builder.Configuration["RemoteAppApiKey"]; - }) - .AddSessionClient(); - -builder.Services.AddReverseProxy().LoadFromConfig(builder.Configuration.GetSection("ReverseProxy")); - -var app = builder.Build(); - -// Configure the HTTP request pipeline. -if (!app.Environment.IsDevelopment()) -{ - app.UseExceptionHandler("/Home/Error"); - app.UseHsts(); -} - -app.UseHttpsRedirection(); -app.UseStaticFiles(); -app.UseRouting(); -app.UseAuthorization(); -app.UseSystemWebAdapters(); -app.MapDefaultControllerRoute() - .BufferResponseStream() - .PreBufferRequestStream() - .RequireSystemWebAdapterSession(); - -app.Run(); -``` - -This opts into all the behavior (described below), as well as sets up a remote session state (see [here](session-state/session.md) for details). diff --git a/docs/framework.md b/docs/framework.md deleted file mode 100644 index 118ea5aea..000000000 --- a/docs/framework.md +++ /dev/null @@ -1,49 +0,0 @@ -# ASP.NET Framework Configuration - -In order to setup the adapters for usage within an ASP.NET framework app, a module is inserted when the package is installed: - -```xml - - ... - - - - - - - ... - -``` - -This module will only run when in the context of a managed application and only if configured to do so. This configuration should take place in `Global.asax.cs` or `Global.asax.vb` and would look like the following: - -```cs -protected void Application_Start() -{ - AreaRegistration.RegisterAllAreas(); - GlobalConfiguration.Configure(WebApiConfig.Register); - FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); - RouteConfig.RegisterRoutes(RouteTable.Routes); - BundleConfig.RegisterBundles(BundleTable.Bundles); - - SystemWebAdapterConfiguration.AddSystemWebAdapters(this) - .AddProxySupport(options => options.UseForwardedHeaders = true) - .AddJsonSessionSerializer(options => ClassLibrary.SessionUtils.RegisterSessionKeys(options.KnownKeys)) - .AddRemoteAppServer(options => options.ApiKey = ConfigurationManager.AppSettings["RemoteAppApiKey"]) - .AddSessionServer(); -} -``` - -Customizing the adapters is done by adding modules to via `Application.AddSystemWebAdapters()` which will be loaded within the `SystemWebAdaptersModule` registered in `web.config`. The available configuration right now are described below. - -## Additional Modules - -### Proxy Support - -During migration, the .NET Framework application will be moved and will be downstream from a reverse proxy. In order for things like port, URL, and scheme to be constructed correctly, we must update the values `HttpContext` use. This is similar to the [process used on ASP.NET Core](https://docs.microsoft.com/en-us/aspnet/core/host-and-deploy/proxy-load-balancer). - -This is done by calling this `ISystemWebAdapterBuilder.AddProxySupport` method and configuring things as desired. - -### Remote App Session Support - -In order to support session, an option is to retrieve it from the framework application. For details on how this works and how to configure it, please see [here](session-state/remote-session.md). diff --git a/docs/getting_started.md b/docs/getting_started.md deleted file mode 100644 index 6e510e33a..000000000 --- a/docs/getting_started.md +++ /dev/null @@ -1,48 +0,0 @@ -# Getting Started - -In order to start a large migration, we recommend setting up a ASP.NET Core application that will proxy to the original .NET Framework Application. This set up will look like this: - -```mermaid -flowchart LR; - external[External Traffic] --> core[ASP.NET Core Application] - core -- Adapters --- libraries - core -- YARP proxy --> framework[.NET Framework Application] - framework --- libraries[[Business logic]] -``` - -To understand how this is helpful in the migration process, please refer to the [introduction](README.md). The rest of this document will provide the steps to set this up and how to proceed with an incremental migration. - -## Set up ASP.NET Core Project - -1. Install this [experimental Visual Studio extension](https://marketplace.visualstudio.com/items?itemName=WebToolsTeam.aspnetprojectmigrations) that will help configure the solution. -2. Right click the ASP.NET Framework application and select "Migrate Project": - ![Migrate Menu](images/migrate_menu.png) -3. This will open a menu that will offter to start a migration. Click the link to begin: - ![Migrate Options](images/migrate_options.png) -4. A wizard will now appear that allows you to create a new project or select an existing project. - ![Migrate Wizard](images/migrate_wizard.png) -5. After completing the wizard, you will now have an ASP.NET Core project that will proxy requests that do not exist there onto the ASP.NET Framework application. - -## Upgrade supporting libraries - -If you have supporting libraries in your solution that you will need to use, they should be upgraded to support .NET 6. [Upgrade Assistant](https://github.com/dotnet/upgrade-assistant) is a great tool for this. - -The adapters in this project can be used in these libraries to enable support for `System.Web.HttpContext` usage that you may have in class libraries. In order to enable this in a library: - -1. Remove reference to `System.Web` in the project file -2. Add the `Microsoft.AspNetCore.SystemWebAdapters` package -3. Enable multi-targeting and add a .NET 6 target, or convert the project to .NET Standard - -This step may require a number of projects to change depending on your solution structure. Upgrade Assistant can help you identify which ones need to change and automate a number of steps in the process. - -## Enable Session Support - -Session is a commonly used feature of ASP.NET that shares a name with a feature in ASP.NET Core but that's where the similarity ends. Please see the documentation on [session support](session-state/session.md) to understand how to use it. - -## Enable shared authentication support - -It is possible to share authentication between the original ASP.NET app and the new ASP.NET Core app by using the System.Web adapters remote authentication feature. This feature allows the ASP.NET Core app to defer authentication to the ASP.NET app. Please see the [remote app connection](remote-app-setup.md) and [remote authentication]((remote-authentication/remote-authentication.md)) docs for more details. - -## General Usage Guidance - -There are a number of differences between ASP.NET and ASP.NET Core that the adapters are able to smooth over. However, there are some features that require an opt-in as it will incur some cost. There are also behavior that cannot be adapted. Please see [usage guidance](usage_guidance.md) to see a list of these. diff --git a/docs/images/migrate_menu.png b/docs/images/migrate_menu.png deleted file mode 100644 index de415e35f..000000000 Binary files a/docs/images/migrate_menu.png and /dev/null differ diff --git a/docs/images/migrate_options.png b/docs/images/migrate_options.png deleted file mode 100644 index 92958ed28..000000000 Binary files a/docs/images/migrate_options.png and /dev/null differ diff --git a/docs/images/migrate_wizard.png b/docs/images/migrate_wizard.png deleted file mode 100644 index aaac87e92..000000000 Binary files a/docs/images/migrate_wizard.png and /dev/null differ diff --git a/docs/remote-app-setup.md b/docs/remote-app-setup.md deleted file mode 100644 index 499b23ed6..000000000 --- a/docs/remote-app-setup.md +++ /dev/null @@ -1,49 +0,0 @@ -# Remote app setup - -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. - -Specifically, this capability is used, currently, for [remote app authentication](remote-authentication/remote-authentication.md) and [remote session](session-state/remote-session.md) features. - -## Configuration - -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. - -### ASP.NET app configuration - -To setup the ASP.NET app to be able to receive requests from the ASP.NET Core app, call the `AddRemoteApp` extension method on the `ISystemWebAdapterBuilder` as shown here. - -```CSharp -SystemWebAdapterConfiguration.AddSystemWebAdapters(this) - .AddRemoteAppServer(options => - { - // ApiKey is a string representing a GUID - options.ApiKey = ConfigurationManager.AppSettings["RemoteAppApiKey"]; - }); -``` - -In the options configuration method passed to the `AddRemoteApp` call, you must specify an API key which is used to secure the endpoint so that only trusted callers can make requests to it (this same API key will be provided to the ASP.NET Core app when it is configured). The API key is a string and must be parsable as a GUID (128-bit hex number). Hyphens in the key are optional. - -### ASP.NET Core app - -To setup the ASP.NET Core app to be able to send requests to the ASP.NET app, you need to make a similar change, calling `AddRemoteApp` after registering System.Web adapter services with `AddSystemWebAdapters`. - -```CSharp -builder.Services.AddSystemWebAdapters() - .AddRemoteAppClient(options => - { - options.RemoteAppUrl = new(builder.Configuration["ReverseProxy:Clusters:fallbackCluster:Destinations:fallbackApp:Address"]); - options.ApiKey = builder.Configuration["RemoteAppApiKey"]; - }); -``` - -The `AddRemoteApp` call is used to configure the remote app's URL and the shared secret API key. - -With both the ASP.NET and ASP.NET Core app updated, extension methods can now be used to setup [remote app authentication](remote-authentication/remote-authentication.md) or [remote session](session-state/remote-session.md), as needed. - -## Securing the remote app connection - -Because remote app features involve serving requests on new endpoints from the ASP.NET app, it's important that communication to and from the ASP.NET app be secure. - -First, make sure that the API key string used to authenticate the ASP.NET Core app with the ASP.NET app is unique and kept secret. It is a best practice to not store the API key in source control. Instead, load it at runtime from a secure source such as Azure Key Vault or other secure runtime configuration. In order to encourage secure API keys, remote app connections require that the keys be non-empty GUIDs (128-bit hex numbers). - -Second, because it's important for the ASP.NET Core app to be able to trust that it is requesting information from the correct ASP.NET app, the ASP.NET app should use HTTPS in any production scenarios so that the ASP.NET Core app can know responses are being served by a trusted source. diff --git a/docs/remote-authentication/remote-authentication.md b/docs/remote-authentication/remote-authentication.md deleted file mode 100644 index 49c2a82c9..000000000 --- a/docs/remote-authentication/remote-authentication.md +++ /dev/null @@ -1,68 +0,0 @@ -# Remote Authentication - -The System.Web adapter's remote authentication feature allows an ASP.NET Core app to determine a user's identity (authenticate an HTTP request) by deferring to an ASP.NET app. Enabling the feature adds an endpoint to the ASP.NET app that returns a serialized `ClaimsPrincipal` representing the authenticated user for any requests made to the endpoint. The ASP.NET Core app, then, registers a custom authentication handler that will (for endpoints with remote authentication enabled) determine a user's identity by calling that endpoint on the ASP.NET app and passing selected headers and cookies from the original request received by the ASP.NET Core app. - -## Configuration - -There are just a few small code changes needed to enable remote authentication in a solution that's already set up according to the [Getting Started](../getting_started.md). - -First, follow the [remote app setup](../remote-app-setup.md) instructions to connect the ASP.NET Core and ASP.NET apps. Then, there are just a couple extra extension methods to call to enable remote app authentication. - -### ASP.NET app configuration - -First, the ASP.NET app needs to be configured to add the authentication endpoint. This is done by calling the `AddRemoteAuthentication` extension method to set up the HTTP module that will watch for requests to the authentication endpoint. Note that remote authentication scenarios typically want to add proxy support, as well, so that any auth-related redirects will correctly route to the ASP.NET Core app rather than the ASP.NET one. - -```CSharp -SystemWebAdapterConfiguration.AddSystemWebAdapters(this) - .AddProxySupport(options => options.UseForwardedHeaders = true) - .AddRemoteApp(options => - { - options.ApiKey = ConfigurationManager.AppSettings["RemoteAppApiKey"]; - }) - .AddRemoteAppAuthentication(); -``` - -### ASP.NET Core app configuration - -Next, the ASP.NET Core app needs to be configured to enable the authentication handler that will authenticate users by making an HTTP request to the ASP.NET app. Again, this is done by calling `AddRemoteAppAuthentication` when registering System.Web adapters services: - -```CSharp -builder.Services.AddSystemWebAdapters() - .AddRemoteApp(options => - { - options.RemoteAppUrl = new(builder.Configuration["ReverseProxy:Clusters:fallbackCluster:Destinations:fallbackApp:Address"]); - options.ApiKey = builder.Configuration("RemoteAppApiKey"); - }) - .AddRemoteAppAuthentication(true); -``` - -The boolean that is passed to the `AddRemoteAuthentication` call specifies whether remote app authentication should be the default authentication scheme. Passing `true` will cause the user to be authenticated via remote app authentication for all requests, whereas passing `false` means that the user will only be authenticated with remote app authentication if the remote app scheme is specifically requested (with `[Authorize(AuthenticationSchemes = RemoteAppAuthenticationDefaults.AuthenticationScheme)]` on a controller or action method, for example). Passing false for this parameter has the advantage of only making HTTP requests to the original ASP.NET app for authentication for endpoints that require remote app authentication but has the disadvantage of requiring annotating all such endpoints to indicate that they will use remote app auth. - -In addition to the require boolean, an optional callback may be passed to `AddRemoteAppAuthentication` to modify some other aspects of the remote authentication process's behavior: - -* `RequestHeadersToForward`: This property contains headers that should be forwarded from a request when calling the authenticate API. By default, the only headers forwarded are `Authorization` and `Cookie`. Additional headers can be forwarded by adding them to this list. Alternatively, if the list is cleared (so that no headers are specified), then all headers will be forwarded. -* `ResponseHeadersToForward`: This property lists response headers that should be propagated back from the authenticate request to the original call that prompted authentication in scenarios where identity is challenged. By default, this includes `Location`, `Set-Cookie`, and `WWW-Authenticate` headers. -* `AuthenticationEndpointPath`: The endpoint on the ASP.NET app where authenticate requests should be made. This defaults to `/systemweb-adapters/authenticate` and must match the endpoint specified in the ASP.NET authentication endpoint configuration. - -Finally, if the ASP.NET Core app didn't previously include authentication middleware, that will need to be enabled (after routing middleware, but before authorization middleware): - -```CSharp -app.UseAuthentication(); -``` - -## Design - -1. When requests are processed by the ASP.NET Core app, if remote app authentication is the default scheme or specified by the request's endpoint, the `RemoteAuthenticationAuthHandler` will attempt to authenticate the user. - 1. The handler will make an HTTP request to the ASP.NET app's authenticate endpoint. It will copy configured headers from the current request onto this new one in order to forward auth-relevant data. As mentioned above, default behavior is to copy the `Authorize` and `Cookie` headers. The API key header is also added for security purposes. -1. The ASP.NET app will serve requests sent to the authenticate endpoint. As long as the API keys match, the ASP.NET app will return either the current user's `ClaimsPrincipal` serialized into the response body or it will return an HTTP status code (like 401 or 302) and response headers indicating failure. -1. When the ASP.NET Core app's `RemoteAuthenticationAuthHandler` receives the response from the ASP.NET app: - 1. If a ClaimsPrincipal was successfully returned, the auth handler will deserialize it and use it as the current user's identity. - 1. If a ClaimsPrincipal was not successfully returned, the handler will store the result and if authentication is challenged (because the user is accessing a protected resource, for example), the request's response will be updated with the status code and selected response headers from the response from the authenticate endpoint. This enables challenge responses (like redirects to a login page) to be propagated to end users. - 1. Because results from the ASP.NET app's authenticate endpoint may include data specific to that endpoint, users can register `IRemoteAuthenticationResultProcessor` implementations with the ASP.NET Core app which will run on any authentication results before they are used. As an example, the one built-in `IRemoteAuthenticationResultProcessor` is `RedirectUrlProcessor` which looks for Location response headers returned from the authenticate endpoint and ensures that they redirect back to the host of the ASP.NET Core app and not the ASP.NET app directly. - -## Known limitations - -This remote authentication approach has a couple known limitations: - -1. Because Windows authentication depends on a handle to a Windows identity, Windows authentication is not supported by this feature. Future work is planned to explore how shared Windows authentication might work. -1. This feature allows the ASP.NET Core app to make use of an identity authenticated by the ASP.NET app but all actions related to users (logging on, logging off, etc.) still need to be routed through the ASP.NET app. diff --git a/docs/session-state/remote-session.md b/docs/session-state/remote-session.md deleted file mode 100644 index 19aebbc2f..000000000 --- a/docs/session-state/remote-session.md +++ /dev/null @@ -1,112 +0,0 @@ -# Remote App Session State - -Remote app session state will enable communication between the ASP.NET Core and ASP.NET app and to retrieve the session state. This is enabled by exposing an endpoint on the ASP.NET app that can be queried to retrieve and set the session state. - -# HttpSessionState serialization - -The `System.Web.SessionState.HttpSessionState` object must be serialized for remote app session state to be enabled. This is accomplished through implementation of the type `Microsoft.AspNetCore.SysteWebAdapters.SessionState.Serialization.ISessionSerializer`, of which a default binary writer implementation is provided. This is added by the following code: - -```csharp -builder.Services.AddSystemWebAdapters() - .AddSessionSerializer(options => - { - // Customize session serialization here - }); -``` - -## Configuration - -First, follow the [remote app setup](../remote-app-setup.md) instructions to connect the ASP.NET Core and ASP.NET apps. Then, there are just a couple extra extension methods to call to enable remote app session state. - -Configuration for ASP.NET Core involves calling `AddRemoteAppSession` and `AddJsonSessionSerializer` to register known session item types. The code should look similar to the following: - -```csharp -builder.Services.AddSystemWebAdapters() - .AddJsonSessionSerializer(options => - { - // Serialization/deserialization requires each session key to be registered to a type - options.RegisterKey("test-value"); - options.RegisterKey("SampleSessionItem"); - }) - .AddRemoteAppClient(options => - { - // Provide the URL for the remote app that has enabled session querying - options.RemoteAppUrl = new(builder.Configuration["ReverseProxy:Clusters:fallbackCluster:Destinations:fallbackApp:Address"]); - - // Provide a strong API key that will be used to authenticate the request on the remote app for querying the session - options.ApiKey = builder.Configuration("RemoteAppApiKey"); - }) - .AddSessionClient(); -``` - -Session support requires additional work for the ASP.NET Core pipeline, and is not turned on by default. It can be configured on a per-route basis via ASP.NET Core metadata. - -For example, session support requires either to annotate a controller: - -```cs -[Session] -public class SomeController : Controller -{ -} -``` - -or to enable for all endpoints by default: - -```cs -app.MapDefaultControllerRoute() - .RequireSystemWebAdapterSession(); -``` - -The framework equivalent would look like the following change in `Global.asax.cs`: - -```csharp -SystemWebAdapterConfiguration.AddSystemWebAdapters(this) - .AddJsonSessionSerializer(options => - { - // Serialization/deserialization requires each session key to be registered to a type - options.RegisterKey("test-value"); - options.RegisterKey("SampleSessionItem"); - }) - // Provide a strong API key that will be used to authenticate the request on the remote app for querying the session - // ApiKey is a string representing a GUID - .AddRemoteAppServer(options => options.ApiKey = ConfigurationManager.AppSettings["RemoteAppApiKey"]) - .AddSessionServer(); -``` - -# Protocol - -## Readonly -Readonly session will retrieve the session state from the framework app without any sort of locking. This consists of a single `GET` request that will return a session state and can be closed immediately. - -```mermaid - sequenceDiagram - participant core as ASP.NET Core - participant framework as ASP.NET - participant session as Session Store - core ->> framework: GET /session - framework ->> session: Request session - session -->> framework: Session - framework -->> core: Session -``` - -## Writeable - -Writeable session state protocol starts with the the same as the readonly, but differs in the following: - -- Requires an additional `PUT` request to update the state -- The initial `GET` request must be kept open until the session is done; if closed, the session will not be able to be updated - -```mermaid - sequenceDiagram - participant core as ASP.NET Core - participant framework as ASP.NET - participant session as Session Store - core ->> framework: GET /session - framework ->> session: Request session - session -->> framework: Session - framework -->> core: Session - core ->> framework: PUT /session - framework ->> framework: Deserialize to HttpSessionState - framework -->> core: Session complete - framework ->> session: Persist -``` diff --git a/docs/session-state/session.md b/docs/session-state/session.md deleted file mode 100644 index 79f612099..000000000 --- a/docs/session-state/session.md +++ /dev/null @@ -1,46 +0,0 @@ -# Session State - -Session state in ASP.NET Framework provided a number of features that ASP.NET Core does not provide. In order to migrate from ASP.NET Framework to Core, the adapters provide mechanisms to enable populating session state with similar behavior as `System.Web` did. Some of the differences between framework and core are: - -- ASP.NET Framework would lock session usage within a session, so subsequent requests in a session are handled in a serial fashion. This is different than ASP.NET Core that does not provide any of these guarantees. -- ASP.NET Framework would serialize and deserialize objects automatically (unless being done in-memory). ASP.NET Core simply provides a mechanism to store a `byte[]` given a key. Any object serialization/deserialization has to be done manually be the user. - -The adapter infrastructure exposes two interfaces that can be used to implement any session storage system. These are: - -- `Microsoft.AspNetCore.SystemWebAdapters.ISessionManager`: This has a single method that gets passed an `HttpContext` and the session metadata and expects an `ISessionState` object to be returned. -- `Microsoft.AspNetCore.SystemWebAdapters.ISessionState`: This describes the state of a session object. It is used as the backing of the `System.Web.SessionState.HttpSessionState` type. - -## Serialization -Since the adapters provide the ability to work with strongly-typed session state, we must be able to serialize and deserialize types. This is customized through the `Microsoft.AspNetCore.SystemWebAdapters.SessionState.Serialization.ISessionKeySerializer`. - -A default JSON implementation is provided that is configured via the `JsonSessionSerializerOptions`: - -- `RegisterKey(string)` - Registers a session key to a known type. This is required in order to serialize/deserialize the session state correctly. If a key is found that there is no registration for, an error will be thrown and session will not be available. - -To use the default JSON backed implementation, add a package reference to [Microsoft.AspNetCore.SystemWebAdapters.SessionState](https://www.nuget.org/packages/Microsoft.AspNetCore.SystemWebAdapters.SessionState) and add the following to the startup: - -> The Microsoft.AspNetCore.SystemWebAdapters.SessionState package is currently in preview. Remember to check the box to 'include prerelease' packages. - -```csharp -builder.Services.AddSystemWebAdapters() - .AddJsonSessionSerializer(options => - { - // Serialization/deserialization requires each session key to be registered to a type - options.RegisterKey("test-value"); - }); -``` - -## Implementations - -There are two available implementations of the session state object that currently ship, each with some trade offs of features. The best choice for an application may depend on which part of the migration it is in, and may change over time. - -- Strongly typed: Provides the ability to access an object and can be cast to the expected type -- Locking: Ensures multiple requests within a single session are queued up and aren't accessing the session at the same time -- Standalone: Use when you're not sharing session between ASP.NET Framework and ASP.NET Core to avoid modifying code in class libraries that references SessionState - -Below are the available implementations: - -| Implementation | Strongly typed | Locking | Standalone | -|-------------------------------------------------------------|----------------|---------|------------| -| [Remote app](remote-session.md) | ✔️ | ✔️ | ⛔ | -| [Wrapped ASP.NET Core](wrapped-aspnetcore-session.md) | ✔️ | ⛔ | ✔️ | diff --git a/docs/session-state/wrapped-aspnetcore-session.md b/docs/session-state/wrapped-aspnetcore-session.md deleted file mode 100644 index 601c83609..000000000 --- a/docs/session-state/wrapped-aspnetcore-session.md +++ /dev/null @@ -1,18 +0,0 @@ -# Wrapped ASP.NET Core Session State - -This implementation wraps the session provided on ASP.NET Core so that it can be used with the adapters. The session will be using the same backing store as `Microsoft.AspNetCore.Http.ISession` but will provide strongly-typed access to its members. - -Configuration for ASP.NET Core would look similar to the following: - -```csharp -builder.Services.AddSystemWebAdapters() - .AddJsonSessionSerializer(options => - { - // Serialization/deserialization requires each session key to be registered to a type - options.RegisterKey("test-value"); - options.RegisterKey("SampleSessionItem"); - }) - .WrapAspNetCoreSession(); -``` - -The framework app would not need any changes to enable this behavior. \ No newline at end of file diff --git a/docs/usage_guidance.md b/docs/usage_guidance.md deleted file mode 100644 index 8722f1ff8..000000000 --- a/docs/usage_guidance.md +++ /dev/null @@ -1,102 +0,0 @@ -# Usage Guidance - -`Microsoft.AspNetCore.SystemWebAdapters` provides an emulation layer to mimic behavior from ASP.NET framework on ASP.NET Core. Below are some guidelines for some of the considerations when using them: - -## `HttpContext` lifetime - -The adapters are backed by `Microsoft.AspNetCore.Http.HttpContext` which cannot be used past the lifetime of a request. Thus, `System.Web.HttpContext` when run on ASP.NET Core cannot be used past a request as well, while on ASP.NET Framework it would work at times. An `ObjectDisposedException` will be thrown in cases where it is used past a request end. - -**Recommendation**: Store the values needed into a POCO and hold onto that. - -## Conversion to `System.Web.HttpContext` - -There are two ways to convert an `Microsoft.AspNetCore.Http.HttpContext` to a `System.Web.HttpContext`: - -- Implicit casting -- Constructor usage - -**Recommendation**: For the most cases, implicit casting should be preferred as this will cache the created instance and ensure only a single `System.Web.HttpContext` per request. - -## `CultureInfo.CurrentCulture` is not set by default - -In ASP.NET Framework, `CultureInfo.Current` was set for a request, but this is not done automatically in ASP.NET Core. Instead, you must add the appropriate middleware to your pipeline. - -**Recommendation**: See [ASP.NET Core Localization](https://docs.microsoft.com/aspnet/core/fundamentals/localization#localization-middleware) for details on how to enable this. - -Simplest way to enable this with similar behavior as ASP.NET Framework would be to add the following to your pipeline: - -```csharp -app.UseRequestLocalization(); -``` - -## `System.Threading.Thread.CurrentPrincipal` - -In ASP.NET Framework, `System.Threading.Thread.CurrentPrincipal` and `System.Security.Claims.ClaimsPrincipal.Current` would be set to the current user. This is not available on ASP.NET Core out of the box. Support for this is available with these adapters by adding the `ISetThreadCurrentPrincipal` to the endpoint (available to controllers via the `SetThreadCurrentPrincipalAttribute`). However, it should only be used if the code cannot be refactored to remove usage. - -**Recommendation**: If possible, use the property `HttpContext.User` instead by passing it through to the call site. If not possible, enable setting the current user and also consider setting the request to be a logical single thread (see below for details). - -## Request thread does not exist in ASP.NET Core - -In ASP.NET Framework, a request had thread-affinity and `HttpContext.Current` would only be available if on that thread. ASP.NET Core does not have this guarantee so `HttpContext.Current` will be available within the same async context, but no guarantees about threads are made. - -**Recommendation**: If reading/writing to the `HttpContext`, you must ensure you are doing so in a single-threaded way. You can force a request to never run concurrently on any async context by setting the `ISingleThreadedRequestMetadata`. This will have performance implications and should only be used if you can't refactor usage to ensure non-concurrent access. There is an implementation available to add to controllers with `SingleThreadedRequestAttribute`: - - -```csharp -[SingleThreadedRequest] -public class SomeController : Controller -{ - ... -} -``` - -## `HttpContext.Request` may need to be prebuffered - -By default, the incoming request is not always seekable nor fully available. In order to get behavior seen in .NET Framework, you can opt into prebuffering the input stream. This will fully read the incoming stream and buffer it to memory or disk (depending on settings). - -**Recommendation**: This can be enabled by applying endpoint metadata that implements the `IPreBufferRequestStreamMetadata` interface. This is available as an attribute `PreBufferRequestStreamAttribute` that can be applied to controllers or methods. - -To enable this on all MVC endpoints, there is an extension method that can be used as follows: - -```cs -app.MapDefaultControllerRoute() - .PreBufferRequestStream(); -``` - -## `HttpContext.Response` may require buffering - -Some APIs on `HttpContext.Response` require that the output stream is buffered, such as `HttpResponse.Output`, `HttpResponse.End()`, `HttpResponse.Clear()`, and `HttpResponse.SuppressContent`. - -**Recommendation**: In order to support behavior for `HttpContext.Response` that requires buffering the response before sending, endpoints must opt-into it with endpoint metadata implementing `IBufferResponseStreamMetadata`. - -To enable this on all MVC endpoints, there is an extension method that can be used as follows: - -```cs -app.MapDefaultControllerRoute() - .BufferResponseStream(); -``` - -## Shared session state - -In order to support `HttpContext.Session`, endpoints must opt-into it via metadata implementing `ISessionMetadata`. - -**Recommendation**: To enable this on all MVC endpoints, there is an extension method that can be used as follows: - -```cs -app.MapDefaultControllerRoute() - .RequireSystemWebAdapterSession(); -``` - -This also requires some implementation of a session store. For details of options here, see [here](./session-state/session.md). - -## Remote session exposes additional endpoint for application - -The [remote session support](session-state/remote-session.md) exposes an endpoint that allows the core app to retrieve session information. This may cause a potentially long-lived request to exist between the core app and the framework app, but will time out with the current request or the session timeout (by default is 20 minutes). - -**Recommendation**: Ensure the API key used is a strong one and that the connection with the framework app is done over SSL. - -## Virtual directories must be identical for framework and core applications - -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. - -**Recomendation**: If you are using virtual directories, ensure your two applications are on different sites with the same application/virtual directory layout.