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
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.<Main>$(String[] args)
How to reproduce
- Create a
net10.0 ASP.NET Core web app.
- Reference
Microsoft.AspNetCore.SystemWebAdapters.CoreServices 2.3.0.
- Call
builder.Services.AddSystemWebAdapters() and app.UseSystemWebAdapters() unconditionally.
- Call
app.MapControllers().
- 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:
$ 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:
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:
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<ControllerFeatureProvider>().Any() and re-adds the default one if none is found.
builder.Services.AddControllersWithViews()
.ConfigureApplicationPartManager(manager =>
{
var toRemove = manager.FeatureProviders
.OfType<ControllerFeatureProvider>()
.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:
internal sealed class ResilientControllerFeatureProvider
: ControllerFeatureProvider, IApplicationFeatureProvider<ControllerFeature>
{
void IApplicationFeatureProvider<ControllerFeature>.PopulateFeature(
IEnumerable<ApplicationPart> parts, ControllerFeature feature)
{
foreach (var part in parts.OfType<IApplicationPartTypeProvider>())
{
IEnumerable<TypeInfo> 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 |
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.
IIISEnvironmentFeaturewas removed from the Linux copy ofMicrosoft.AspNetCore.Server.IIS.dllin .NET 10.SystemWebAdapters.CoreServices.dllstill references it. WhenAddSystemWebAdapters()registers the assembly as an MVC application part,ControllerFeatureProvidercallsGetDefinedTypes()on it at startup — and crashes because the type no longer exists in the shared runtime.Exception
How to reproduce
net10.0ASP.NET Core web app.Microsoft.AspNetCore.SystemWebAdapters.CoreServices2.3.0.builder.Services.AddSystemWebAdapters()andapp.UseSystemWebAdapters()unconditionally.app.MapControllers().Microsoft.AspNetCore.App 10.0.8.Process dies at startup with the exception above.
What I checked
I ran
stringsagainst the runtime DLL to confirm the type is actually gone — this isn't a load path issue:$ strings /usr/lib64/dotnet/shared/Microsoft.AspNetCore.App/10.0.8/Microsoft.AspNetCore.Server.IIS.dll \ | grep IIISEnvironmentFeature # no outputCOREHOST_TRACEconfirms the assembly is coming straight from the shared framework, not a local copy:Workaround
Option 1 — OS guard
Sufficient when
SystemWebAdapters.CoreServicesis the only assembly referencing the removed type.AddSystemWebAdapters()is what registersSystemWebAdapters.CoreServices.dllas an MVC application part.Wrapping with an OS check prevents the registration on Linux, so
GetDefinedTypes()is never called on it:Option 2 — Resilient
ControllerFeatureProviderNeeded when other assemblies in the project (e.g. pre-compiled DLLs built against .NET 8) also carry a reference to
IIISEnvironmentFeature.Replace
ControllerFeatureProviderwith an implementation that catchesReflectionTypeLoadExceptionduring type scanning.ResilientControllerFeatureProvidermust inheritControllerFeatureProvider(so the internalAny()guard does not re-add the original) and use explicit interface implementation soApplicationPartManagerdispatches to the resilientPopulateFeatureoverride instead of the base class method:The problem
The package lists
net10.0as a supported TFM on NuGet, but it references a type that no longer exists in the .NET 10 Linux shared runtime. Either:net10.0-windows, orIIISEnvironmentFeatureinside the package should be guarded so it does not cause reflection-based scanning to fail on non-Windows platforms.Environment
Microsoft.AspNetCore.App 10.0.810.0.8, commit94ea82652c, path/usr/lib64/dotnet/dotnetMicrosoft.AspNetCore.SystemWebAdapters.CoreServices2.3.0Microsoft.AspNetCore.SystemWebAdapters2.3.0Microsoft.AspNetCore.SystemWebAdapters.CoreServices1.4.0 on .NET 8 / Linux