.NET Remoting, AppDomains and other .NET Framework-only APIs on .NET 8 or 10
Most .NET Framework code compiles on .NET 8 or 10 once the project file is converted. A few technologies do not come along at all: Microsoft removed them and names what to use instead. This guide goes through that list, what each removal means for an existing application, and how to find which of them your code touches.
.NET Remoting
Remoting was built on application domains, and Microsoft’s page says it also needs runtime support that is expensive to maintain. There is no version of it in .NET. Because of that, BeginInvoke and EndInvoke on delegate objects throw PlatformNotSupportedException as well.
What is usually behind it
In an application of that era, Remoting is one of a few things: a Windows service that exposes a singleton object to a desktop client over a TCP or IPC channel; a server that raises events into its clients, which Remoting delivered through callbacks on a MarshalByRefObject; or a plugin host that creates objects in another AppDomain and talks to them through a MarshalByRefObject proxy. The library you pick matters less than deciding what crosses the process boundary. An object that was passed by reference and changed on both sides has to become a message.
Replacements
- Pipes and sockets.
System.IO.Pipesfor two processes on one machine,System.Net.Socketsacross machines. You define the message format yourself. These are the two Microsoft names first. - gRPC. A contract-first RPC framework: services and messages are defined in
.protofiles and the tooling generates servers and clients in C#. It supports client, server and bidirectional streaming, which covers a server that pushes events. Microsoft’s desktop upgrade guides name gRPC and ASP.NET Core for communication across machines. - StreamJsonRpc. Microsoft’s open-source JSON-RPC library that runs over an existing stream or pipe connection, named on the same page for the more complex cases.
- CoreWCF. If the contract is already a WCF
ServiceContract, or clients outside your control speak SOAP, the server side of WCF runs on .NET through CoreWCF ↗, a .NET Foundation project under the MIT license. Its packages include NetTcp and NetNamedPipe transports. See WCF services on .NET 8 or 10.
CoreRemoting ↗ is an open-source library (MIT license) that describes itself as an RPC library with “classic .NET Remoting flavour”, written to help move applications that use .NET Remoting to .NET. It targets .NET Standard 2.0 and supports events and delegates across the wire, sessions, a Remoting-style CallContext, TCP and named-pipe channels, and several serializers (BSON by default; a BinaryFormatter adapter is a separate package). Its readme does not mention MarshalByRefObject, and it rules out REST, SOAP and non-.NET clients. The repository was last pushed to in September 2026, and its latest release, 1.3.6, is from May 2026.
AppDomains
.NET has one application domain. AppDomain.CreateDomain throws PlatformNotSupportedException; other members still work (AppDomain.UnhandledException, for example) and some do nothing (SetCachePath). Microsoft points to the System.AppDomain source in the dotnet/runtime repository to check the members you use, on the branch of your runtime version.
AppDomains were used for two different things, and .NET replaces them separately.
Loading and unloading plugins
AssemblyLoadContext loads a set of assemblies into its own context, so two plugins can bring different versions of the same dependency. Created with isCollectible: true, the context can be unloaded. AssemblyDependencyResolver reads the plugin’s .deps.json and finds its dependencies next to it. This is the class from Microsoft’s documentation, with the two pieces put together:
using System.Reflection;
using System.Runtime.Loader;
class PluginLoadContext : AssemblyLoadContext
{
private readonly AssemblyDependencyResolver _resolver;
public PluginLoadContext(string pluginPath) : base(isCollectible: true)
{
_resolver = new AssemblyDependencyResolver(pluginPath);
}
protected override Assembly? Load(AssemblyName name)
{
string? path = _resolver.ResolveAssemblyToPath(name);
return path != null ? LoadFromAssemblyPath(path) : null;
}
}Loading and unloading then takes three lines:
var context = new PluginLoadContext(pluginPath);
Assembly assembly = context.LoadFromAssemblyPath(pluginPath);
context.Unload();Two things differ from AppDomain.Unload. With AppDomains the unload was forced: threads in the domain were aborted. With AssemblyLoadContext it is cooperative. Unload only starts it, and it finishes when no thread has a method from the context on its stack and nothing outside the context holds a reference to its assemblies, types or instances, other than weak references. A static field, a cached MethodInfo, a running thread, a strong GC handle or a local the JIT kept alive keeps the whole context loaded. Microsoft’s page on unloadability shows how to find the culprit with WinDbg or LLDB and the SOS gcroot command. Two limits apply to collectible contexts: C++/CLI assemblies are not supported, and ReadyToRun code is ignored.
The plugin project needs <EnableDynamicLoading>true</EnableDynamicLoading>, and its reference to the shared interface assembly needs <Private>false</Private> and <ExcludeAssets>runtime</ExcludeAssets>. Without them the plugin loads its own copy of the interface, the runtime sees two different types, and the host’s type check fails. Plugins should target a runtime such as net8.0 or net10.0; a .NET Standard target gives a .deps.json that may miss the implementation assemblies of packages.
Isolation
An AppDomain also kept a misbehaving plugin away from the host, and a restricted PermissionSet limited what it could do. AssemblyLoadContext gives none of that. Microsoft’s plugin tutorial says that untrusted code cannot be safely loaded into a trusted .NET process, and that a security or reliability boundary has to come from the operating system or a virtualization platform. In practice that means a separate process per plugin, or per group of plugins, that talks to the host over pipes or gRPC, so the plugin host ends up with the same design as the Remoting server above.
BinaryFormatter
The state depends on the version you move to:
- .NET 8:
SerializeandDeserializethrowNotSupportedExceptionin every project type except Windows Forms and WPF, and the APIs are obsolete as an error everywhere. TheEnableUnsafeBinaryFormatterSerializationswitch from .NET 7 still turns them back on. - .NET 9 and later: the implementation is gone from the runtime. The types still exist, and every use throws
PlatformNotSupportedException, whatever the switch says. To keep using it, the application project references theSystem.Runtime.Serialization.FormattersNuGet package and setsEnableUnsafeBinaryFormatterSerializationto true. Microsoft labels the package unsupported and unsafe. The package does not change the type identity ofBinaryFormatter, so libraries do not have to reference it; only the application does.
Microsoft recommends four serializers in its place, depending on the data: System.Text.Json (JSON), DataContractSerializer (XML), MessagePack and protobuf-net (binary). All four assume you control both the writer and the reader of the data. Its security guide lists SoapFormatter, NetDataContractSerializer, LosFormatter and ObjectStateFormatter as equally dangerous, so moving from one of those to another gains nothing.
Data you already have
The harder case is data on disk or in a database that BinaryFormatter wrote: saved documents, cached objects, settings, queued messages. .NET 9 added NrbfDecoder in the System.Formats.Nrbf package. It reads the format (NRBF, “.NET Remoting: Binary Format”) without loading or instantiating any type, so it is safe on untrusted input. The package also targets .NET Standard 2.0 and .NET Framework, so the conversion can start before the runtime changes. It works when the set of serialized types is known and closed: you read the records and build your own objects from them. Microsoft’s suggested pattern is to check each payload with NrbfDecoder.StartsWithPayloadHeader, convert an old payload on first read, serialize it with the new serializer and write it back. The same page names SettingsPropertyValue, which can hold any object from a configuration file, as a case that cannot be migrated without a breaking change to the API.
Windows Forms and WPF have their own pages in the migration guide for the clipboard, drag and drop and .resx resources. Strings and icons in resources work without BinaryFormatter; resources of custom types need the compatibility package and switch.
COM+, MSMQ and Workflow Foundation
System.EnterpriseServices (COM+)
Not supported, and Microsoft’s list gives no replacement. The desktop upgrade guides say that an application which uses COM+ for object pooling, transactions or role-based security has to be redesigned: System.Transactions for distributed transactions, ASP.NET Core or worker services for hosting. Distributed transactions in System.Transactions exist on .NET from version 7 and are opt-in: TransactionManager.ImplicitDistributedTransactions is false by default, escalation then throws NotSupportedException, and the setter is marked Windows only.
System.Messaging (MSMQ)
The System.Messaging namespace is documented for .NET Framework only, and the Windows Compatibility Pack does not list messaging among its areas. The community package MSMQ.Messaging ↗ (MIT license) calls itself a drop-in replacement for System.Messaging for .NET on Windows; its latest version on NuGet, 1.0.4, is from June 2022, and the repository was last pushed to in January 2024. CoreWCF ships an MSMQ transport as well. If the queue itself can change, Microsoft’s list of open-source messaging projects for .NET names MassTransit, NServiceBus, Rebus and Brighter, and clients for Azure Service Bus and RabbitMQ.
Workflow Foundation
Not supported. Microsoft points to CoreWF ↗, a port of the WF runtime maintained by UiPath under the MIT license. Its readme says it targets .NET 6, runs XAML workflows through ActivityXamlServices, and does not include the designer; XAML produced by the Visual Studio designer runs “with some limitations”. The repository was last pushed to in August 2026. The NuGet package UiPath.Workflow is unlisted by its owner, and its page describes the project as experimental, so look at the repository for how the current code is built and shipped.
Code Access Security
CAS is gone, and most of its types are obsolete from .NET 5 on, with warning SYSLIB0003: PermissionSet, SecurityPermissionAttribute, FileIOPermission, PrincipalPermission and the rest of System.Security.Permissions. What that means for old code:
- A
Demandalways succeeds at run time. Remove it.PrincipalPermissionis the exception and has its own guidance underSYSLIB0002. - An
Assertdoes nothing. Remove the attribute or the call. - A
DenyorPermitOnlyis not honored. Microsoft says to review these with a security advisor, because code that relied on CAS to restrict access now runs without the restriction. - Security transparency attributes (
SecurityTransparent,SecurityCritical,SecuritySafeCritical) mark a boundary that .NET no longer enforces.
If the application sandboxed plugins with a restricted PermissionSet in a separate AppDomain, both halves of that protection are gone. The replacement is a process boundary under an account that has only the rights it needs, or a container.
Windows-only APIs and the compatibility pack
A different group of APIs exists on .NET only on Windows. The Microsoft.Windows.Compatibility package adds about 20,000 of them: registry, event log, WMI, performance counters, directory services, ACLs, Windows services, Windows cryptography, WCF, ODBC, CodeDom, configuration, serial ports, drawing. Microsoft says existing code compiles with almost no modifications once the package is referenced, and that it is meant for applications that stay on Windows, at least as a first step.
System.Configurationandapp.configkeep working through theSystem.Configuration.ConfigurationManagerpackage, which has the same API. Moving toappsettings.jsonis a separate step and not required.System.Drawing.Commonis Windows only from .NET 6. On Linux or macOS the first call throwsTypeInitializationExceptionwithPlatformNotSupportedExceptioninside. TheSystem.Drawing.EnableUnixSupportswitch existed in .NET 6 only and was removed in .NET 7. For image work outside Windows Microsoft names SkiaSharp, ImageSharp, Aspose.Drawing andMicrosoft.Maui.Graphics.- The compiler reports all of this as
CA1416, on by default from .NET 5. A call to a Windows-only API from code that could run elsewhere is a warning until you wrap it inif (OperatingSystem.IsWindows()), mark the method[SupportedOSPlatform("windows")], or targetnet8.0-windows, which marks the whole assembly as Windows only.
Windows Forms, WPF and Windows services
Both desktop frameworks are part of .NET and still receive new work, and both remain Windows only. The upgrade guides for Windows Forms ↗ and WPF ↗ list the same preparation: target .NET Framework 4.7.2 first, move from packages.config to PackageReference, convert to the SDK-style project file while still on .NET Framework, then update packages and prefer ones that target .NET Standard 2.0 or .NET. The pitfalls they name:
- Third-party controls and libraries that target only .NET Framework may not work. Audit them before anything else; a vendor without a .NET build is a blocker you cannot code around.
- ClickOnce still exists: Visual Studio 2019 version 16.8 and later publish .NET desktop apps with it. The
System.DeploymentAPI does not. From .NET 7 the launcher passes the deployment properties to the application as environment variables such asClickOnce_IsNetworkDeployedandClickOnce_ActivationUri, and Microsoft provides a sampleApplicationDeploymentclass that reads them. The methods of the old class have no equivalent. - The WPF
WebBrowsercontrol is based on Internet Explorer; Microsoft points to WebView2. BinaryFormatterin the clipboard, drag and drop and resources, as above.
A Windows service becomes a Worker Service: dotnet new worker, the Microsoft.Extensions.Hosting.WindowsServices package, builder.Services.AddWindowsService(...), and sc.exe create pointing at the published executable. Microsoft recommends publishing it as a single self-contained file. From .NET 6 an unhandled exception in a BackgroundService stops the host cleanly, so the Service Control Manager does not restart it; call Environment.Exit(1) from the catch block if you rely on recovery options.
ASP.NET Web Forms and the server side of WCF have their own guides here: ASP.NET Web Forms on .NET 8 or 10 and WCF services on .NET 8 or 10.
Not sure what your application relies on?
Send us the list of projects in the solution, or a description of the application, and we answer by email with what blocks the move and what we propose for each item.
The check is free and does not commit you to anything. If you want us to do the work, we agree a fixed price for the agreed scope by email.
- What does the application do and who uses it?
- Which .NET Framework version and project types (ASP.NET MVC, Web Forms, WCF, Windows service, WinForms/WPF)?
- Which third-party packages or components does it depend on?
- What is the goal: .NET 8/10 on Windows, Linux, containers, cloud?
How to find these in your own code
Start by searching the source and the project files:
rg -l "System.Runtime.Remoting|MarshalByRefObject|RemotingConfiguration" --type cs
rg -l "AppDomain.CreateDomain|AppDomain.Unload|CreateInstanceAndUnwrap" --type cs
rg -l "BinaryFormatter|SoapFormatter|NetDataContractSerializer" --type cs
rg -l "System.EnterpriseServices|ServicedComponent" --type cs
rg -l "System.Messaging|MessageQueue" --type cs
rg -l "System.Activities|WorkflowInvoker|WorkflowApplication" --type cs
rg -l "PermissionSet|SecurityPermission|SecurityTransparent|SecurityCritical" --type cs
rg -l "System.Deployment|ApplicationDeployment" --type cs
rg -l "System.Runtime.Remoting|System.EnterpriseServices|System.Messaging|System.Activities|System.Workflow" --glob "*.csproj" --glob "*.config"A hit in a .csproj or .config reference list counts even when the namespace never appears in C#: the reference may be there for a vendor library that uses it. The .NET Upgrade Assistant, a Visual Studio extension and CLI tool, analyzes a solution and reports incompatibilities before changing anything. Microsoft has deprecated it in favor of the GitHub Copilot app modernization agent, which does the same assessment and then applies changes. Whichever you use, build the application on .NET early and run it: several of the APIs above compile and fail only when called.
If the list you end up with is longer than you expected, the migration service starts with the same check and takes the work from there.
Sources
- Microsoft Learn: .NET Framework technologies unavailable on .NET ↗
- Microsoft Learn: create a .NET application with plugins ↗
- Microsoft Learn: how to use and debug assembly unloadability ↗
- Microsoft Learn: BinaryFormatter disabled across most project types (.NET 8) ↗
- Microsoft Learn: in-box BinaryFormatter implementation removed (.NET 9) ↗
- Microsoft Learn: BinaryFormatter migration guide ↗
- Microsoft Learn: BinaryFormatter compatibility package ↗
- Microsoft Learn: read BinaryFormatter (NRBF) payloads ↗
- Microsoft Learn: deserialization risks in use of BinaryFormatter and related types ↗
- Microsoft Learn: SYSLIB0003, code access security APIs are obsolete ↗
- Microsoft Learn: use the Windows Compatibility Pack to port code ↗
- Microsoft Learn: System.Drawing.Common only supported on Windows ↗
- Microsoft Learn: CA1416, validate platform compatibility ↗
- Microsoft Learn: overview of upgrading Windows Forms apps ↗
- Microsoft Learn: overview of upgrading WPF apps ↗
- Microsoft Learn: deploy a .NET Windows Desktop app with ClickOnce ↗
- Microsoft Learn: access ClickOnce deployment properties for .NET ↗
- Microsoft Learn: create a Windows Service using BackgroundService ↗
- Microsoft Learn: overview for gRPC on .NET ↗
- Microsoft Learn: .NET Upgrade Assistant overview ↗
- Microsoft Learn: upgrade .NET apps overview ↗
- Microsoft Learn: System.Messaging namespace ↗
- Microsoft Learn: TransactionManager.ImplicitDistributedTransactions ↗
- Microsoft, .NET open source developer projects: messaging ↗
- CoreRemoting repository ↗
- CoreWCF repository ↗
- CoreWF repository ↗
- UiPath.Workflow on NuGet ↗
- MSMQ.Messaging on NuGet ↗