Skip to content
Merged
Show file tree
Hide file tree
Changes from 40 commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
e8e734b
Add module types to adapters and type forwards
twsouthwick Feb 17, 2023
6c09ace
Add sample project for modules
twsouthwick Feb 17, 2023
601b52b
Add module implementation
twsouthwick Feb 17, 2023
f47ee3c
Add HttpModuleCollection
twsouthwick Mar 15, 2023
96aa728
Add APIs to HttpContextBase/Wrapper
twsouthwick Mar 15, 2023
4e44d35
Merge remote-tracking branch 'origin/main' into tasou/http-modules
twsouthwick Mar 15, 2023
2d31372
Pass HttpModuleCollection
twsouthwick Mar 15, 2023
c9708a1
add doc
twsouthwick Mar 22, 2023
34fc21a
Merge remote-tracking branch 'origin/main' into tasou/http-modules
twsouthwick Mar 22, 2023
fa98762
Clean up docs/sample
twsouthwick Mar 22, 2023
a02fca6
expand sample
twsouthwick Mar 22, 2023
0c45942
Update http-modules.md
twsouthwick Mar 27, 2023
b6980b0
Update http-modules.md
twsouthwick Mar 27, 2023
f955ebf
remove ihtmlstring
twsouthwick Mar 27, 2023
bc28871
Update http-modules.md
twsouthwick Mar 27, 2023
62e3fe3
add check if middleware already added
twsouthwick Mar 27, 2023
8d129e5
fix up sharing code
twsouthwick Mar 27, 2023
f5cd865
Merge remote-tracking branch 'origin/main' into tasou/http-modules
twsouthwick Mar 27, 2023
27c8c69
fix warnings
twsouthwick Mar 27, 2023
3fcf714
use nullable
twsouthwick Mar 27, 2023
7ee0ff4
fix up refs
twsouthwick Mar 27, 2023
a0449d4
Clean up some weird merge stuff
twsouthwick Mar 28, 2023
fdbfb4e
ensure state lock is disposed
twsouthwick Mar 28, 2023
608b463
Simplify HttpApplication creation
twsouthwick Mar 28, 2023
d04cb4d
remove unused class
twsouthwick Mar 28, 2023
a51c3d3
per feedback
twsouthwick Mar 28, 2023
54f4dcd
rename events per feedback
twsouthwick Mar 29, 2023
4f6e527
respond to some feedback
twsouthwick Mar 29, 2023
a8be80c
Add exception handling
twsouthwick Mar 30, 2023
a59a7a7
clean up session handling
twsouthwick Mar 30, 2023
466e2ff
update ref
twsouthwick Mar 30, 2023
544d50f
Clean up some naming
twsouthwick Mar 30, 2023
97c22c1
add GetVaryByCustomString
twsouthwick Mar 30, 2023
e6e7097
remove using
twsouthwick Mar 30, 2023
f94e470
remove custom state
twsouthwick Mar 30, 2023
6ff6088
no need to add HttpApplication itself to features
twsouthwick Mar 31, 2023
483ee80
revert getvaryby for now
twsouthwick Mar 31, 2023
c2fb5e0
fix doc design per API name change
twsouthwick Apr 6, 2023
a01e349
Move startup filter to own class
twsouthwick Apr 7, 2023
f528692
remove extra usings
twsouthwick Apr 7, 2023
cdd7175
change to static and add link to docs
twsouthwick Apr 17, 2023
0240340
update some more docs
twsouthwick Apr 17, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

<PropertyGroup>
<Nullable>enable</Nullable>
<LangVersion>10</LangVersion>
<LangVersion>11</LangVersion>
</PropertyGroup>

<PropertyGroup>
Expand Down
26 changes: 25 additions & 1 deletion Microsoft.AspNetCore.SystemWebAdapters.sln
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,15 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MvcApp", "samples\RemoteAut
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MvcCoreApp", "samples\RemoteAuth\Identity\MvcCoreApp\MvcCoreApp.csproj", "{2BF8EE74-1AB3-4DB8-ADDE-27A35981CA04}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CoreApp", "samples\CoreApp\CoreApp.csproj", "{431651D7-D40A-403E-813C-496A1414AA22}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CoreApp", "samples\CoreApp\CoreApp.csproj", "{431651D7-D40A-403E-813C-496A1414AA22}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Modules", "Modules", "{4ED7A31C-8DBE-4A32-A17A-D72794F9FE2C}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ModulesLibrary", "samples\Modules\ModulesLibrary\ModulesLibrary.csproj", "{5597A485-4D9B-4CE6-A489-DEBE9338450F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ModulesFramework", "samples\Modules\ModulesFramework\ModulesFramework.csproj", "{B262AD69-11F0-4AE0-949A-AEAA2300C061}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ModulesCore", "samples\Modules\ModulesCore\ModulesCore.csproj", "{F8B33C59-27CF-45DC-955C-2EBF9DA9DB7E}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Expand Down Expand Up @@ -147,6 +155,18 @@ Global
{431651D7-D40A-403E-813C-496A1414AA22}.Debug|Any CPU.Build.0 = Debug|Any CPU
{431651D7-D40A-403E-813C-496A1414AA22}.Release|Any CPU.ActiveCfg = Release|Any CPU
{431651D7-D40A-403E-813C-496A1414AA22}.Release|Any CPU.Build.0 = Release|Any CPU
{5597A485-4D9B-4CE6-A489-DEBE9338450F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5597A485-4D9B-4CE6-A489-DEBE9338450F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5597A485-4D9B-4CE6-A489-DEBE9338450F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5597A485-4D9B-4CE6-A489-DEBE9338450F}.Release|Any CPU.Build.0 = Release|Any CPU
{B262AD69-11F0-4AE0-949A-AEAA2300C061}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B262AD69-11F0-4AE0-949A-AEAA2300C061}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B262AD69-11F0-4AE0-949A-AEAA2300C061}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B262AD69-11F0-4AE0-949A-AEAA2300C061}.Release|Any CPU.Build.0 = Release|Any CPU
{F8B33C59-27CF-45DC-955C-2EBF9DA9DB7E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F8B33C59-27CF-45DC-955C-2EBF9DA9DB7E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F8B33C59-27CF-45DC-955C-2EBF9DA9DB7E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F8B33C59-27CF-45DC-955C-2EBF9DA9DB7E}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -176,6 +196,10 @@ Global
{174A36F1-27ED-43FC-A3A1-00DA58C4E30C} = {9C8EBDB5-FA17-4C9C-8946-04692AC752CE}
{2BF8EE74-1AB3-4DB8-ADDE-27A35981CA04} = {9C8EBDB5-FA17-4C9C-8946-04692AC752CE}
{431651D7-D40A-403E-813C-496A1414AA22} = {95915611-30BF-4AFF-AE41-5CDC6F57DCF7}
{4ED7A31C-8DBE-4A32-A17A-D72794F9FE2C} = {95915611-30BF-4AFF-AE41-5CDC6F57DCF7}
{5597A485-4D9B-4CE6-A489-DEBE9338450F} = {4ED7A31C-8DBE-4A32-A17A-D72794F9FE2C}
{B262AD69-11F0-4AE0-949A-AEAA2300C061} = {4ED7A31C-8DBE-4A32-A17A-D72794F9FE2C}
{F8B33C59-27CF-45DC-955C-2EBF9DA9DB7E} = {4ED7A31C-8DBE-4A32-A17A-D72794F9FE2C}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {DABA3C65-9D74-4EB6-9B1C-730328710EAD}
Expand Down
109 changes: 109 additions & 0 deletions designs/http-modules.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
# IHttpModules and Emulated Pipeline Support

> **Note**: This implementation is not tied to IIS and does not hook into any of IIS events if ran on IIS.

Support for `HttpApplication` and `IHttpModule` is emulated as best as possible on the ASP.NET Core pipeline. This is not tied to IIS and will work on Kestrel or any other host by using middleware to invoke the expected events at the times that best approximate the timing from ASP.NET Core. An attempt has been made to get the events to fire at the appropriate time, but because of the substantial difference between ASP.NET and ASP.NET Core there may still be unexpected behavior.

In order to register either an `HttpApplication` or `IHttpModule` instance, use the following pattern:

```csharp
using System.Web;
using ModulesLibrary;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddSystemWebAdapters()
// Non-generic version available if no custom HttpApplication is needed
.AddHttpApplication<MyApp>(options =>
{
// Size of pool for HttpApplication instances. Should be what the expected concurrent requests will be
options.PoolSize = 10;

// Register a module by name
options.RegisterModule<MyModule>("Module");
});

var app = builder.Build();

app.UseSystemWebAdapters();

app.Run();

class MyApp : HttpApplication
{
protected void Application_Start()
{
...
}

protected void Session_Start()
{
...
}

protected void Begin_Request()
{
...
}

...
}

class MyModule : IHttpModule
{
public void Init(HttpApplication app)
{
...
}

public void Dispose()
{
}
}
```

The normal `.UseSystemWebAdapters()` middleware builder will enable majority of the events. However, the authentication and authorization events require two additional middleware calls in order to enable them if you want the events to fire in the expected order. If they are omitted, they will be called at the point `UseSystemWebAdapters()` is added.


```diff
app.UseRouting();

app.UseAuthentication();
+ app.UseAuthenticationEvents();

app.UseAuthorization();
+ app.UseAuthorizationEvents();

app.UseSystemWebAdapters();
```

## When should this be used?

> Most of the time, this should not be used. Prefer direct ASP.NET Core middleware if possible.

This is intended mostly for scenarios where a module needs to be run on ASP.NET Core but is unable to be migrated easily. Ideally, the code in a module should be restructured to be used as middleware. This is especially recommended when only a single or few events are used; those can usually be migrated in a straightfoward way.

However, if a module has many thousands of line of code and many events being used (the initial driver of this feature), this can provide a stepping stone to migrating that functionality to ASP.NET Core.

## Emulated Events

> For details on how this worked in .NET Framework, see the [official documentation](https://learn.microsoft.com/en-us/dotnet/api/system.web.httpapplication)

The IIS event pipeline that is expected by `IHttpModule` and `HttpApplication` is emulated using middleware by the adapters. As part of this, it will add additional middleware that will invoke the events. This is done via a feature that is inserted early on in the adapter pipeline [IHttpApplicationFeature](../src/Microsoft.AspNetCore.SystemWebAdapters/Adapters/IHttpApplicationFeature.cs). This exposes the `HttpApplication` for the request, as well as the ability to raise events on it.

Events have a prescribed order which is replicated with these emulated events. However, because the rest of the ASP.NET Core pipeline is unaware of these events and so some of the state of the request may not be exactly replicated.

A common pattern is to be able to call `HttpRequest.End()` or `HttpApplication.CompleteRequest()`. Both of these are supported, as well as continuing to raise the events that are raised in IIS with this (including `EndRequest` and the logging events).

> Note: In the cases in which no modules or `HttpApplication` type is registered, the emulated pipeline is not added to the middleware chain.

## HttpApplication lifetime

On ASP.NET Framework, each request would get an individual `HttpApplication` instance. This object contains the following information:

- Event callbacks registered either on the `HttpApplication` type itself or on registered modules
- Any state contained in the `HttpApplication` instance or its registered modules

In order to support this, one of the first middlewares invoked will retrieve an instance of `HttpApplication`. This uses a `PooledObjectPolicy<HttpApplication>` that will create an instance of the application's `HttpApplication` type and register all modules on it. When the request is exiting that middleware, it will return the `HttpApplication` instance to the pool which will also remove the `HttpContext` instance assigned to it.

This can potentially create a number of instances of `HttpApplication` that are only used a limited number of times. The pool can be controlled by customizing the `HttpApplicationOptions.PoolSize` option or providing a custom implementation of `ObjectPool<HttpApplication>` that can use the `PooledObjectPolicy<HttpApplication>` provided to override the pooling behavior.
14 changes: 14 additions & 0 deletions samples/Modules/ModulesCore/ModulesCore.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\..\..\src\Microsoft.AspNetCore.SystemWebAdapters.CoreServices\Microsoft.AspNetCore.SystemWebAdapters.CoreServices.csproj" />
<ProjectReference Include="..\ModulesLibrary\ModulesLibrary.csproj" />
</ItemGroup>

</Project>
27 changes: 27 additions & 0 deletions samples/Modules/ModulesCore/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using System.Web;
using ModulesLibrary;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddSystemWebAdapters()
.AddHttpApplication<MyApp>(options =>
{
// Size of pool for HttpApplication instances. Should be what the expected concurrent requests will be
options.PoolSize = 10;

// Register a module by name
options.RegisterModule<EventsModule>("Events");
});

var app = builder.Build();

app.UseSystemWebAdapters();

app.Run();

class MyApp : HttpApplication
{
protected void Application_Start()
{
}
}
37 changes: 37 additions & 0 deletions samples/Modules/ModulesCore/Properties/launchSettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:30447",
"sslPort": 44395
}
},
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "http://localhost:5202",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "https://localhost:7148;http://localhost:5202",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
9 changes: 9 additions & 0 deletions samples/Modules/ModulesCore/appsettings.Development.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"DetailedErrors": true,
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}
9 changes: 9 additions & 0 deletions samples/Modules/ModulesCore/appsettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}
Binary file added samples/Modules/ModulesCore/wwwroot/favicon.ico
Binary file not shown.
16 changes: 16 additions & 0 deletions samples/Modules/ModulesFramework/Handler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace ModulesFramework
{
public class Handler : IHttpHandler
{
public bool IsReusable => true;

public void ProcessRequest(HttpContext context)
{
}
}
}
Loading