dotnet / dotnet/systemweb-adapters

Microsoft.AspNetCore.SystemWebAdapters.CoreServices 2.3.0 crashes on Linux with .NET 10 — IIISEnvironmentFeature removed from shared runtime

Open
#687 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
C#
Stars
384
Forks
76
Avg merge
2h 24m
Merged PRs (30d)
1

Description

## What happened

I upgraded my web application from .NET 8 to .NET 10 and deployed it on RHEL 9. The service crashes immediately at startup before handling a single request. It was working fine on .NET 8 with package version 1.4.0.

`IIISEnvironmentFeature` was removed from the Linux copy of `Microsoft.AspNetCore.Server.IIS.dll` in .NET 10. `SystemWebAdapters.CoreServices.dll` still references it. When `AddSystemWebAdapters()` registers the assembly as an MVC application part, `ControllerFeatureProvider` calls `GetDefinedTypes()` on it at startup — and crashes because the type no longer exists in the shared runtime.

## Exception

```text
Unhandled exception. System.Reflection.ReflectionTypeLoadException:
Unable to load one or more of the requested types.
Could not load type 'Microsoft.AspNetCore.Server.IIS.IIISEnvironmentFeature'
from assembly 'Microsoft.AspNetCore.Server.IIS, Version=10.0.0.0,
Culture=neutral, PublicKeyToken=adb9793829ddae60'.
at System.Reflection.RuntimeModule.GetDefinedTypes()
at Microsoft.AspNetCore.Mvc.Controllers.ControllerFeatureProvider.PopulateFeature(...)
at Microsoft.AspNetCore.Mvc.ApplicationParts.ApplicationPartManager.PopulateFeature[TFeature](...)
at Microsoft.AspNetCore.Mvc.ApplicationModels.ControllerActionDescriptorProvider.GetControllerTypes()
at Microsoft.AspNetCore.Builder.ControllerEndpointRouteBuilderExtensions.MapControllers(...)
at Program.$(String[] args)
```

## How to reproduce

1. Create a `net10.0` ASP.NET Core web app.
2. Reference `Microsoft.AspNetCore.SystemWebAdapters.CoreServices` 2.3.0.
3. Call `builder.Services.AddSystemWebAdapters()` and `app.UseSystemWebAdapters()` unconditionally.
4. Call `app.MapControllers()`.
5. Run on Linux with `Microsoft.AspNetCore.App 10.0.8`.

Process dies at startup with the exception above.

## What I checked

I ran `strings` against the runtime DLL to confirm the type is actually gone — this isn't a load path issue:

```bash
$ strings /usr/lib64/dotnet/shared/Microsoft.AspNetCore.App/10.0.8/Microsoft.AspNetCore.Server.IIS.dll \
| grep IIISEnvironmentFeature
# no output
```

`COREHOST_TRACE` confirms the assembly is coming straight from the shared framework, not a local copy:

```text
Adding tpa entry: /usr/lib64/dotnet/shared/Microsoft.AspNetCore.App/10.0.8/Microsoft.AspNetCore.Server.IIS.dll,
AssemblyVersion: 10.0.0.0, FileVersion: 10.0.826.23019
```

## Workaround

### Option 1 — OS guard

_Sufficient when `SystemWebAdapters.CoreServices` is the only assembly referencing the removed type._

`AddSystemWebAdapters()` is what registers `SystemWebAdapters.CoreServices.dll` as an MVC application part.
Wrapping with an OS check prevents the registration on Linux, so `GetDefinedTypes()` is never called on it:

```csharp
if (OperatingSystem.IsWindows())
{
builder.Services.AddSystemWebAdapters();
}

// ...

if (OperatingSystem.IsWindows())
{
app.UseSystemWebAdapters();
}
```

### Option 2 — Resilient `ControllerFeatureProvider`

_Needed when other assemblies in the project (e.g. pre-compiled DLLs built against .NET 8) also carry a reference to `IIISEnvironmentFeature`._

Replace `ControllerFeatureProvider` with an implementation that catches `ReflectionTypeLoadException` during type scanning.

> **Important:** this must run _after_ all `AddMvc` / `AddControllersWithViews` calls, because each of those internally checks `OfType().Any()` and re-adds the default one if none is found.

```csharp
builder.Services.AddControllersWithViews()
.ConfigureApplicationPartManager(manager =>
{
var toRemove = manager.FeatureProviders
.OfType()
.ToList();
foreach (var fp in toRemove)
manager.FeatureProviders.Remove(fp);
manager.FeatureProviders.Add(new ResilientControllerFeatureProvider());
});
```

`ResilientControllerFeatureProvider` must **inherit** `ControllerFeatureProvider` (so the internal `Any()` guard does not re-add the original) and use **explicit interface implementation** so `ApplicationPartManager` dispatches to the resilient `PopulateFeature` override instead of the base class method:

```csharp
internal sealed class ResilientControllerFeatureProvider
: ControllerFeatureProvider, IApplicationFeatureProvider
{
void IApplicationFeatureProvider.PopulateFeature(
IEnumerable parts, ControllerFeature feature)
{
foreach (var part in parts.OfType())
{
IEnumerable types;
try
{
types = part.Types.ToList();
}
catch (ReflectionTypeLoadException ex)
{
types = ex.Types
.Where(t => t is not null)
.Select(t => t!.GetTypeInfo());
}

foreach (var typeInfo in types)
{
try
{
if (IsController(typeInfo) && !feature.Controllers.Contains(typeInfo))
feature.Controllers.Add(typeInfo);
}
catch { }
}
}
}
}
```

## The problem

The package lists `net10.0` as a supported TFM on NuGet, but it references a type that no longer exists in the .NET 10 Linux shared runtime. Either:

- the NuGet compatibility metadata should be restricted to `net10.0-windows`, or
- the reference to `IIISEnvironmentFeature` inside the package should be guarded so it does not cause reflection-based scanning to fail on non-Windows platforms.

## Environment

| Field | Value |
|---|---|
| OS | RHEL 9 x64 |
| .NET runtime | `Microsoft.AspNetCore.App 10.0.8` |
| .NET host | `10.0.8`, commit `94ea82652c`, path `/usr/lib64/dotnet/dotnet` |
| Broken package | `Microsoft.AspNetCore.SystemWebAdapters.CoreServices` 2.3.0 |
| Also referenced | `Microsoft.AspNetCore.SystemWebAdapters` 2.3.0 |
| Last known good | `Microsoft.AspNetCore.SystemWebAdapters.CoreServices` 1.4.0 on .NET 8 / Linux |

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.