microsoft/migrating-wcf-to-corewcf
> Migrates server-side WCF services from .NET Framework to CoreWCF for .NET 6+. Converts hosting, configuration, bindings, behavior extensions, and APM-style contracts. Use when a project references System.ServiceModel via GAC, contains .svc files, uses ServiceHost, or needs WCF endpoints rehosted in ASP.NET Core. Also handles mixed client/server projects by preserving client packages.
npx skills add https://github.com/microsoft/upgrade-agent-plugins --skill migrating-wcf-to-corewcf
Migrate .NET Framework WCF services to CoreWCF for .NET 6+ by converting service hosting, configuration, and dependencies while preserving service functionality.
string, int). Use builder.Configuration["appSettings:<key>"] ?? <default> for unknown valuesweb.config/app.config, ServiceHostFactory subclasses, startup code) for the intended setting. If a clear match is found, apply it. If the original intent is ambiguous or no match exists, present the error with your findings and suggested options to the user rather than guessingMigration Progress:
If analysis results already contain WCF dependency information, use those. Otherwise, check for references in these categories:
| Category | References | Action |
|----------|------------|--------|
| Server-side WCF (.NET Fx) | System.ServiceModel and other System.ServiceModel.* via <Reference> (GAC) | Primary migration target → proceed with remaining steps |
| WCF client (.NET Core NuGet) | System.ServiceModel.Primitives, .Http, .NetTcp | May be client-only (ChannelFactory<T>, ClientBase<T>). Do not remove blindly — see Step 3 |
| CoreWCF (already migrated) | CoreWCF.Primitives, .ConfigurationManager, .Http, .WebHttp, .NetTcp | Skip conversion; only validate config/hosting patterns |
If none found, skip and inform user there is nothing to do.
If WCF migration is part of the .NET Framework upgrade, project should already be upgraded to new .NET version and SDK-style. If it is not, switch to plan steps doing project .NET version upgrade and come back to WCF feature upgrade after that.
However, if WCF migration is done separately per user request, check that project is SDK-style and targets .NET version 6.0 or above. If it is not, let user know and ask them to upgrade project to newer .NET version first.
Add CoreWCF NuGet packages based on the bindings and features actually used by the project:
CoreWCF.Primitives — always requiredCoreWCF.Http — if the project uses HTTP-based bindings (BasicHttpBinding, WSHttpBinding, etc.)CoreWCF.WebHttp — if the project uses WebHttpBinding or REST-style endpointsCoreWCF.NetTcp — if the project uses NetTcpBindingCoreWCF.ConfigurationManager — if the project reads WCF configuration from web.config/app.configDetermine which bindings are in use by examining web.config/app.config <bindings> sections and any binding instances created in code. Only add packages the project actually needs.
Before removing any System.ServiceModel.* references, search for ChannelFactory< and ClientBase< usage. If found, keep client NuGet packages and only migrate server-side pieces; inform user which packages were retained. If no client usage, remove all System.ServiceModel.* dependencies.
Search the project for all files containing using System.ServiceModel statements. For each, replace the System.ServiceModel portion of the namespace with CoreWCF (the sub-namespace structure is the same — e.g., System.ServiceModel.Channels → CoreWCF.Channels). Apply this to both service contract interfaces and implementation classes. If a file mixes server and client code, keep System.ServiceModel usings for client types only.
Program.cs with standard ASP.NET Core host setup[ServiceContract] interfaces, their implementations, .svc files, and ServiceHost usages to discover every service, endpoint path, and bindingRegister each discovered service using this pattern:
services.AddServiceModelServices();
services.AddServiceModelMetadata();
app.UseServiceModel(builder =>
{
// Repeat for each service discovered in the project:
builder.AddService<DiscoveredService>();
builder.AddServiceEndpoint<DiscoveredService, IDiscoveredContract>(
new /* binding matching original */(), "/original/path/Service.svc");
});
var serviceMetadataBehavior = app.Services.GetRequiredService<ServiceMetadataBehavior>();
serviceMetadataBehavior.HttpGetEnabled = true;
services.AddServiceModelMetadata() is required for WSDL/metadata. Derive endpoint paths from the original .svc file locations — you must preserve the original subfolder structure.
Endpoint path ordering: Register more specific paths first (e.g., /Services/MyService.svc/wshttp before /Services/MyService.svc).
ServiceHost mutations (.Credentials, .Description, .Authorization) must move into ConfigureServiceHostBase<TService>. Search for any code that directly modifies ServiceHost properties (often in ServiceHostFactory subclasses or startup code) and relocate it into the appropriate delegate for each service.
web.config/app.config for behaviorExtensions elements. If none are found, skip this step.type attribute to locate the BehaviorExtensionElement subclass, then check its BehaviorType property for the actual behavior class and CreateBehavior() for how it's instantiated.Service behaviors (IServiceBehavior): Register via DI — CoreWCF automatically discovers and applies them. Do not add them inside ConfigureServiceHostBase.
If the project has only one service (or a behavior should apply to every service), use a plain registration:
builder.Services.AddSingleton<IServiceBehavior, MyServiceBehavior>();
If the project has multiple services and a behavior should apply to only some of them, use keyed DI with the service type as the key so CoreWCF scopes it correctly:
// Only applies to OrderService, not to other services in the project
builder.Services.AddKeyedSingleton<IServiceBehavior, MyServiceBehavior>(typeof(OrderService));
Without keyed registration, every IServiceBehavior registered in the container will be applied to all hosted services, which is almost never the intent when different services had different behavior configurations.
Endpoint behaviors (IEndpointBehavior): Use ConfigureServiceHostBase<TService> (prefer over ConfigureAllServiceHostBase to avoid cross-applying). Skip system endpoints (endpoint.IsSystemEndpoint).
After registration, you must:
BehaviorExtensionElement (delete file if it only contains those)<behaviorExtensions> from config (remove <extensions> too if it only contained <behaviorExtensions>).svc activation. Remove .svc files only — keep .svc.cs if they contain service implementation<Content> and <Compile Update> / <DependentUpon> metadata for .svc items from project file (SDK-style includes .svc.cs by default). When deleting files, use applicable tool; if no such tools then use other available tools to delete filesnet.tcp), configure the app to auto-start and stay running to simulate WCF-style activation. This is not needed for HTTP/HTTPS-only servicesMigrate remaining WCF configuration from web.config/app.config to code. Check for and handle each of these:
| Config Element | Migration Target |
|---------------|------------------|
| <serviceThrottling> (maxConcurrentCalls, maxConcurrentSessions, maxConcurrentInstances) | Configure via ServiceThrottlingBehavior registered in DI |
| <serviceDebug includeExceptionDetailInFaults> | Configure via ServiceDebugBehavior in DI |
| <serviceCredentials> (certificates, user-name validation) | Configure in ConfigureServiceHostBase<TService> — see Step 8 for security notes |
| <dataContractSerializer maxItemsInObjectGraph> | Configure via DataContractSerializerOperationBehavior |
| <protocolMapping> | Not supported in CoreWCF — configure bindings explicitly in UseServiceModel |
| Custom <appSettings> used by service logic | Migrate to appsettings.json and inject via IConfiguration or IOptions<T> |
If the project uses CoreWCF.ConfigurationManager, some <system.serviceModel> sections can remain in a config file and be loaded via builder.AddServiceModelConfigurationManagerFile("wcf.config"). This is an alternative to migrating everything to code — useful for complex configurations. Confirm with the user which approach they prefer.
Some WCF binding features are not supported or behave differently in CoreWCF. Check for these and notify the user:
| WCF Feature | CoreWCF Status | Alternative |
|-------------|---------------|-------------|
| NetTcpBinding with TCP port sharing | Not supported | Use separate ports per service, or switch to HTTP-based binding |
| WSDualHttpBinding | Not supported | Use BasicHttpBinding or WSHttpBinding with a callback alternative |
| NetNamedPipeBinding | Not supported | Use NetTcpBinding or HTTP-based binding |
| MsmqBinding / MSMQ transport | Not supported | Use a message queue library (e.g., RabbitMQ, Azure Service Bus) |
| TransactionScope flowing across service boundaries | Limited support | Verify with user; may require architectural changes |
| reliableSession | Supported on NetTcpBinding only | For HTTP bindings, remove reliable session config |
Security configuration caution: WCF security settings (<security mode>, <transport>, <message>, certificate configuration) are critical to migrate correctly. Do not silently change security modes. If the original service uses Transport, Message, or TransportWithMessageCredential security, preserve the same mode in CoreWCF. If CoreWCF does not support the exact security configuration, present the gap to the user with options — do not downgrade security silently.
Inform the user of any unsupported features found and document them in the task's progress notes.
Search for [OperationContract(AsyncPattern = true)] and IAsyncResult methods. If none found, skip this step. CoreWCF does not support APM — convert BeginX/EndX pairs to Task<TResult> XAsync(...) (remove AsyncPattern = true).
Inspect the Begin/End method bodies to decide which conversion strategy to use:
Strategy A — Real APM underneath: The Begin method delegates to an inherently asynchronous API that returns IAsyncResult (e.g., Stream.BeginRead, Socket.BeginSend, a third-party async I/O library). Wrap the pair with Task<TResult>.Factory.FromAsync, passing the Begin/End methods and parameters directly — do not use the overload that takes a pre-started IAsyncResult (severe performance issues):
public Task<string> FooAsync(string param1, int param2)
{
return Task<string>.Factory.FromAsync(this.BeginFoo, this.EndFoo, param1, param2, null);
}
Strategy B — No real APM underneath: The Begin/End methods perform synchronous work (e.g., direct database calls, in-memory computation, file I/O via synchronous APIs) and only used the APM pattern because WCF required AsyncPattern = true for async contracts. In this case do not wrap them with Task.Factory.FromAsync. Instead, remove the Begin/End pair entirely and rewrite the operation as a straightforward method:
async Task<TResult> with await if the work can be converted to truly async calls (e.g., replacing DbCommand.ExecuteReader() with await ExecuteReaderAsync())Task.FromResult<TResult>(result) if no async alternative existsAfter converting, update all callers to use the new Task-based signatures.
ServiceHost. These will not compile after migrationServiceHost-based test setup to use WebApplicationFactory<Program> with HttpClient. This requires the test project to reference the web host project and may need InternalsVisibleTo or a public Program classSystem.ServiceModel replaced with CoreWCF (client packages retained if needed)using statements updated from System.ServiceModel to CoreWCF namespacesAddServiceModelMetadata(); endpoints ordered specific-firstConfigureServiceHostBase<TService>; system endpoints skipped.svc files removed (.svc.cs preserved); APM contracts converted to Task-based asyncTake microsoft/migrating-wcf-to-corewcf from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.