If you maintain a classic ASP.NET application—WebForms pages, MVC controllers, .asmx web services, maybe a couple of .svc WCF endpoints—you have heard the same advice for years: rewrite it. System.Web never made the jump to .NET Core, Microsoft has said it never will, and the official migration story is a gradual rewrite behind a YARP proxy.
This article presents a different option: a port of the ASP.NET stack itself—WebForms, MVC 4, Razor, Web Pages, Web API, .asmx, and .svc hosting—to .NET 10, running under Kestrel instead of IIS. Your .aspx, .ascx, .master, .cshtml, code-behind, controllers, and web.config stay exactly as they are. System.Web.UI.Page is still System.Web.UI.Page; System.Web.Mvc.Controller is still System.Web.Mvc.Controller. What changes is the host and the project file—not your application code.
The port is built from Mono's open-source implementation of System.Web and Microsoft's open-source aspnetwebstack (MVC, Razor, Web API), compiled in place against .NET 10, with the IIS/AppDomain-specific machinery replaced by an ASP.NET Core middleware.
The source code is available on GitHub: GitHub - pieroviano/Core.Windows.Forms · GitHub
The Problem: Why System.Web Never Came Along
Three hard blockers kept System.Web off .NET Core, and each needed a real answer.
1. The Assembly Identity Is Taken
Modern .NET ships an empty System.Web.dll facade inside the shared framework, and the runtime gives the shared framework precedence. An app-local System.Web.dll compiles fine and then fails at startup with FileNotFoundException.
The port therefore ships its assemblies under new names (Core.Web, Core.Configuration, and so on) while keeping the namespaces untouched, so your code and your generated page classes never notice.
2. Runtime Page Compilation Used CodeDOM
On .NET Core, CodeDomProvider's compile functionality throws PlatformNotSupportedException.
The port keeps CodeDOM for code generation (which still works) and hands the generated code to Roslyn for compilation.
Both C# and VB.NET pages compile at runtime, just as they did under IIS.
3. BinaryFormatter Is Gone
View state and out-of-process session state used BinaryFormatter for arbitrary object graphs.
Rather than silently replacing it with something insecure, the port refuses a type with no native encoding and reports which type caused the issue. You then opt in to a serializer (a JSON-based serializer is included, or you can provide your own).
Hosting: One Extension Method
The entire hosting model is a single ASP.NET Core extension method: UseWebForms.
Here is the complete Program.cs of a working WebForms application:
using Microsoft.AspNetCore.Builder;
using System.Web.Hosting.Kestrel;
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
// Static files first: let Kestrel serve .css/.js/images directly rather than
// routing them through the System.Web pipeline.
app.UseStaticFiles();
app.UseWebForms(options =>
{
options.PhysicalPath = app.Environment.ContentRootPath;
options.VirtualPath = "/";
options.SiteName = "WebFormsSample";
// Opt in to a serializer for state objects with no native encoding.
options.StateSerializer = new System.Web.JsonStateObjectSerializer();
});
app.Run();
That is the entire host.
Behind UseWebForms, the port initializes the System.Web runtime (HttpRuntime, BuildManager, the module and handler pipeline, configuration) inside a single process. Since there are no AppDomains anymore, an adapter maps each incoming ASP.NET Core request onto an HttpWorkerRequest, exactly the abstraction IIS previously supplied.
Everything downstream remains the ASP.NET you already know:
web.configis read with full<location>, inheritance, and<httpModules>/<httpHandlers>semantics.Global.asax,HttpApplicationevents, custom modules, and handlers run unchanged.Postbacks,
__VIEWSTATE, validators, master pages, user controls,GridView,Repeater,UpdatePanel, andScriptManagerall work.App_Codeis still compiled at runtime byBuildManager.
Your Pages Don't Change
To make the point concrete, here is code-behind from the sample application. There is nothing unusual to see—and that is the point.
This is ordinary WebForms code running on .NET 10.
public class DefaultPage : Page
{
protected Label message;
protected Label counter;
protected Repeater items;
protected GridView grid;
// Survives postbacks through __VIEWSTATE, not a field.
int PostbackCount
{
get
{
object v = ViewState["postbacks"];
return v == null ? 0 : (int)v;
}
set
{
ViewState["postbacks"] = value;
}
}
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
message.Text = IsPostBack
? "hello again (postback)"
: "hello from Page_Load";
if (IsPostBack)
PostbackCount = PostbackCount + 1;
counter.Text = PostbackCount.ToString();
if (!IsPostBack)
{
items.DataSource = new List<string>
{
"alpha",
"beta",
"gamma"
};
items.DataBind();
}
}
}
MVC, Web API, and the Rest of the Stack
MVC is not a separate host. Under classic ASP.NET, it was always just an HttpModule and a handler registered inside System.Web, and the same is true here.
The MVC sample host is almost identical to the WebForms host.
app.UseWebForms(options =>
{
options.PhysicalPath = app.Environment.ContentRootPath;
options.VirtualPath = "/";
options.SiteName = "MvcSample";
// Controllers are discovered by scanning loaded assemblies.
options.ApplicationAssemblies =
new[]
{
typeof(MvcSample.Controllers.HomeController).Assembly
};
});
Supported Framework Components
| Stack | Status on .NET 10 |
|---|---|
| WebForms (.aspx, .ascx, .master) | Works, C# and VB.NET, runtime compiled by Roslyn |
| ASP.NET MVC | MVC 4 with Razor v2, attribute routing ([Route]), bundling, and @await in views |
| Web Pages (.cshtml) | Works |
| Web API | Works with routing, content negotiation, and model binding |
| .asmx SOAP services | Works |
| .svc WCF services | Served by CoreWCF at the same URLs; [ServiceContract] moves from System.ServiceModel to CoreWCF |
| Session State | InProc, StateServer (reimplemented over IDistributedCache), and SQLServer using ASPState database with unchanged web.config |
| Dynamic Data / LinqDataSource | Works using IQueryable and EF Core |
| Script and Style Bundling | System.Web.Optimization works |
| System.Web.Mail | Works unchanged |
Getting Started
A single package reference is enough for a WebForms application.
<ItemGroup>
<PackageReference Include="AspNetCore.Web.Hosting.Kestrel" Version="1.0.0" />
</ItemGroup>
It transitively brings in the core of the port (AspNetCore.Web.Base, which contains the Core.Web assembly, along with configuration, services, and extensions).
Its build assets also perform important project setup automatically.
Facade Removal
The empty .NET System.Web.dll and System.Configuration.dll facades are removed from the reference set.
Without this, you would encounter CS0433 type conflicts during compilation.
The build targets also verify that Core.Web.dll is copied to the output directory, turning what would otherwise be a runtime TypeLoadException into a clear build-time error.
Content Handling
The build process automatically copies:
.aspx.cshtmlweb.config
and related content beside your application assembly.
App_Code is copied as content because it is compiled by the runtime rather than MSBuild.
The bin and obj folders are excluded.
Afterward, simply add one package for each additional framework you use:
AspNetCore.Web.Mvcfor MVCAspNetCore.Web.HttpAspNetCore.Web.Http.WebHostAspNetCore.Net.Http.Formattingfor Web APIAspNetCore.Web.ServiceModelfor.svcAspNetCore.Web.SessionStatefor out-of-process session support
Notice the deliberate naming split:
Package:
AspNetCore.Web.BaseAssembly:
Core.WebNamespace:
System.Web
The assembly name is only exposed when explicitly referenced in web.config.
Honest Limitations
A port is only trustworthy if it clearly explains what it does not support.
.NET Remoting (
.rem/.soap) is not portable. Transparent proxies are a CLR feature unavailable on CoreCLR. These endpoints should be replaced with Web API.Mobile Controls (
System.Web.Mobile) require a rewrite. They were deprecated many years ago, and there is no implementation available to port.MVC remains MVC 4, not ASP.NET Core MVC. Features such as Tag Helpers and View Components are unavailable, although attribute routing and
@awaitare supported.Custom
ServiceHostFactoryimplementations inside.svcfiles are not supported because CoreWCF constructs the service host.Third-party server controls compiled against Microsoft's strong-named
System.Webassembly cannot be loaded. They must be recompiled or their source code must be available.Out-of-process session data cannot be shared with existing StateServer or ASPState instances. Types that previously worked only because InProc session avoided serialization will now be validated.
<system.webServer>configuration that depends on IIS-native modules must be rewritten as ASP.NET Core middleware or classic<httpModules>.
How Well Is It Tested?
The port includes 445 tests across fifteen projects.
These are not isolated unit tests.
The functional test suites:
Start a real Kestrel server.
Execute requests using a real
HttpClient.Drive browser scenarios through Playwright using Chromium.
Click buttons and execute real postbacks.
Validate rendered controls and validators.
Additional test suites verify:
VB.NET runtime compilation
MVC routing and Razor views
Web API content negotiation
WCF SOAP services through CoreWCF
StateServer and SQL Server session state
Dynamic Data scaffolding over HTTP
Conclusion
"Rewrite it" is good advice when time and budget allow.
However, many organizations maintain large, stable WebForms and MVC applications where the only real limitation is the platform they run on: .NET Framework, IIS, and Windows.
This port removes that dependency.
The same pages, controllers, and web.config run under Kestrel on .NET 10 across any platform supported by .NET 10 while benefiting from modern tooling and a supported runtime.
The migration becomes a host swap instead of a rewrite—and a host swap is measured in days rather than months.
The full source code, sample applications, tests, and a step-by-step porting guide are available in the repository: GitHub - pieroviano/Core.Windows.Forms · GitHub
Happy coding!
Jasen FiciPosted Aug 3, 2026, 2:03 PM
We featured this for DotNetNews readers here: https://dotnetnews.co/archive/the-net-news-daily-issue-510/