.NET on AWS Blog

Modernize your legacy WCF services: Automated migration to ASP.NET Core Web API using AWS Transform custom

Enterprises who need to migrate business-critical Windows Communication Foundation (WCF) services to ASP.NET Core Web API sometimes struggle with the scope of the change. These services still run on .NET Framework, the pool of engineers fluent in service contracts keep shrinking, and modern .NET does not support WCF. The community CoreWCF project (a .NET Foundation project that Microsoft officially recommends) fills the server-side hosting gap, but it preserves the SOAP architecture rather than replacing it. When the goal is to move away from SOAP entirely, a REST-based Web API is the appropriate target, and it addresses the broader challenges with cross-platform hosting, modern tooling, and an actively developed runtime.

However, manually converting a WCF service to a Web API is tedious and error-prone, because it is an architecture change. Every [ServiceContract] becomes an attribute-routed controller, every operation that returned null now owes an HTTP 404, and Entity Framework 6 (EF6) patterns that tolerated in-memory filtering become genuine performance defects under Entity Framework Core (EF Core), across every operation in every service.

This post walks through using AWS Transform custom to automate the migration of a sample .NET Framework WCF SOAP service to an ASP.NET Core Web API on .NET 10. You will learn how to create a custom transformation definition, execute the transformation, and verify the result, all from PowerShell on Windows, with minimal manual effort.

About AWS Transform custom

AWS Transform custom is an agentic AI service that performs large-scale modernization of software, code, libraries, and frameworks. It handles API and service migrations, language version upgrades, framework upgrades, code refactoring, and organization-specific transformations.

AWS Transform custom offers out-of-the-box (AWS-managed) transformations for scenarios such as Java SDK version upgrades, .NET modernization, Node.js and Python version upgrades. This walkthrough uses a custom definition for AWS Transform custom that targets a specific architectural conversion: WCF SOAP to ASP.NET Core REST Web API, with explicit decisions and correctness checks for routing, DTOs, EF Core, and HTTP semantics. You define a transformation using natural language, and the AI agent executes it against your codebase. Once saved, the definition is reusable across every WCF service in your organization.

Solution overview

This is the migration workflow:

  1. Start with a .NET Framework WCF SOAP service using Entity Framework 6.
  2. Use AWS Transform custom to convert it to an ASP.NET Core Web API on .NET 10 with EF Core.
  3. Optionally, continue with AWS Transform Full-Stack Windows Modernization for the surrounding estate (databases, UI frameworks, Linux deployment).

The AWS Transform custom workflow follows these phases:

  1. Define — Create a transformation definition describing the WCF-to-Web-API conversion rules in natural language
  2. Plan — The AI agent analyzes the codebase and generates a step-by-step execution plan
  3. Execute — The agent transforms files incrementally, verifying the build after each step
  4. Validate — Confirm all exit criteria are met: successful build, no remaining WCF artifacts, correct REST semantics

Prerequisites

Before you start, verify that you have the following:

Environment setup: Amazon EC2 Windows instance

The original project requires the .NET Framework build toolchain (MSBuild), and the transformed project requires the .NET 10 SDK. To meet both requirements, run the AWS Transform CLI (atx) natively on Windows with PowerShell on a Windows EC2 instance. All commands in this post are PowerShell.

To set up the environment:

  1. Launch a Windows EC2 instance (m6i.xlarge is sufficient) and connect over RDP.
  2. Install Git for Windows and Node.js 22+.
  3. Install the AWS Transform CLI and confirm it resolves in PowerShell.
  4. Install .NET Framework 4.8 SDK and MSBuild on the Windows host.
  5. Install the .NET 10 SDK.

Sample application

This walkthrough uses a sample WCF catalog service, the eShopWCFService project from the open source eShopModernizing reference application, reproduced as a standalone sample to demonstrate the migration:

  • Repository: sample-aws-transform-custom-wcf-to-webapi
  • main branch: Original WCF service source code
  • atx-result-staging-20260805_064143_27e9dc2c branch: ASP.NET Core Web API converted by AWS Transform custom – browse on GitHub

The atx-result-staging-20260403_074329_289c738e branch was created and committed automatically by atx as a local branch during the transformation process. It was then pushed to the remote repository manually.

Application structure

The service targets .NET Framework and includes the following components:

src\eShopWCFService\
├── CatalogService.svc               # WCF service host declaration
├── CatalogService.svc.cs            # 10 operation implementations
├── ICatalogService.cs               # [ServiceContract]
├── EntityModel.cs                   # EF6 DbContext
├── CatalogServiceClient.cs          # WCF channel factory client
├── CatalogServiceMock.cs            # in-memory test double
├── eShopWCFService.csproj           # non-SDK project file, 142 lines
├── Models\
│   ├── CatalogItem.cs              # [DataContract] product entity
│   ├── CatalogBrand.cs             # brand lookup entity
│   ├── CatalogType.cs              # product type lookup entity
│   ├── CatalogItemsStock.cs        # per-day stock entity
│   ├── DiscountItem.cs             # date-ranged discount entity
│   └── Infrastructure\
│       ├── CatalogDBInitializer.cs # CreateDatabaseIfNotExists
│       ├── CatalogConfiguration.cs # ConnectionString resolution
│       └── PreconfiguredData.cs    # reference data
└── Properties\
    └── AssemblyInfo.cs              # assembly metadata

The service demonstrates WCF and Entity Framework 6 (EF6) patterns commonly found in enterprise applications:

  • [ServiceContract] interface with 10 [OperationContract] operations over basicHttpBinding
  • [DataContract] entities that also carry EF mapping attributes
  • In-memory filtering .ToList() followed by .Where(...) in three operations
  • Max(Id) + 1 key assignment with [DatabaseGenerated(DatabaseGeneratedOption.None)]
  • HasPrecision(19, 4) and a money column type on the price property
  • CreateDatabaseIfNotExists seeding with preconfigured reference data
  • Fully synchronous data access throughout

Before running the transformation, create a baseline tag. Since ATX modifies files in place and commits to its own branch, that tag serves as your checkpoint to the original, untouched source.

PS C:\repos\eshop-wcf-service-sample> git tag atx-baseline

Walkthrough

Step 1: Start AWS Transform custom

Launch atx from the project directory. The interactive CLI displays the session banner and accepts natural language commands.

PS C:\repos\eshop-wcf-service-sample\src\eShopWCFService> atx

 █████╗ ██╗    ██╗███████╗
██╔══██╗██║    ██║██╔════╝
███████║██║ █╗ ██║███████╗
██╔══██║██║███╗██║╚════██║
██║  ██║╚███╔███╔╝███████║
╚═╝  ╚═╝ ╚══╝╚══╝ ╚══════╝
████████╗██████╗  █████╗ ███╗   ██╗███████╗███████╗ ██████╗ ██████╗ ███╗   ███╗
╚══██╔══╝██╔══██╗██╔══██╗████╗  ██║██╔════╝██╔════╝██╔═══██╗██╔══██╗████╗ ████║
   ██║   ██████╔╝███████║██╔██╗ ██║███████╗█████╗  ██║   ██║██████╔╝██╔████╔██║
   ██║   ██╔══██╗██╔══██║██║╚██╗██║╚════██║██╔══╝  ██║   ██║██╔══██╗██║╚██╔╝██║
   ██║   ██║  ██║██║  ██║██║ ╚████║███████║██║     ╚██████╔╝██║  ██║██║ ╚═╝ ██║
   ╚═╝   ╚═╝  ╚═╝╚═╝  ╚═╝╚═╝  ╚═══╝╚══════╝╚═╝      ╚═════╝ ╚═╝  ╚═╝╚═╝     ╚═╝

Region: us-east-1

Welcome to AWS Transform. You can discover, create, and execute transformations
(AWS-managed or custom ones published to your registry). How can I help?


> transform the WCF service in the current directory to an ASP.NET Core Web API on .NET 10

Step 2: Create the transformation definition

When you ask atx to transform WCF to a Web API, it first checks the transformation registry. The agent calls list_available_transformations_from_registry, finds the AWS-managed transformations but none for WCF to ASP.NET Core Web API. The agent recognizes the gap and decides to create a custom transformation definition, asking clarifying questions to shape it:

> I don't see an existing WCF-to-Web-API transformation in the registry. Let me create
  a custom one for you. To make this transformation definition as effective as possible,
  it would be useful to know:

  1. Should the EF6 data layer move to EF Core with database-generated IDs and
     server-side filtering, or preserve the existing patterns as-is?
  2. Should the Web API use RESTful resource-based routing or preserve an RPC-style
     flat route structure?
  3. Should data access and controller actions be async, or stay synchronous?

> 1. EF Core with identity IDs and server-side filtering, 2. RESTful resource routing
  under api/catalog, 3. Full async

The agent continued the authoring interview across service-layer design, connection-string handling and DTOs, each question carrying its own recommendation. Two answers are worth calling out because they overrode the defaults: keeping the legacy ConnectionString environment variable in the configuration fallback chain (existing deployments set it), and requiring DTOs without ReferenceHandler.Preserve,  $id/$ref pairs and broken ordinary JSON clients. The interview also supplied a set of correctness rules for the definition to encode status-code semantics for absence (404, not a 200 with a null body), SQL-translatable date comparisons, database-generated keys, preserved decimal precision, non-destructive seeding, and no invented API surface. Each rule corresponds to a defect class that compiles cleanly and is still wrong.

After the interview, atx generates the transformation definition as a markdown file. The definition includes an objective, entry criteria, 10 implementation steps, and numbered validation/exit criteria. Here is a snippet of the generated definition:

Using tool: editor (trusted)
 ● Path: ~/.aws/atx/custom/20260805_064143_27e9dc2c/artifacts/tp-staging/SKILL.md
 ● Command: create
 ● File content:
   ---
   name: wcf-to-webapi-dotnet10
   description: Convert a .NET Framework 4.8 WCF SOAP service to an ASP.NET Core
     Web API on net10.0 with EF Core, REST/JSON controllers, DTOs, and async.
   ---

   # WCF Service to ASP.NET Core Web API (net10.0)

   RootNamespace and AssemblyName MUST remain `WcfService1` throughout every step.
   Changing them will break all `using` directives.

   ## Entry Criteria
   1. The project directory contains a non-SDK style `.csproj` referencing
      .NET Framework (4.x).
   2. A `packages.config` file is present listing EntityFramework 6.x.
   3. At least one `.svc` file exists (WCF service endpoint).
   ...

   ## Implementation Steps

   ### Step 1: Replace the project file with SDK-style targeting net10.0
   ...
   ### Step 2: Delete WCF infrastructure files
   ...
   ### Step 3: Create ASP.NET Core host (Program.cs)
   ...

   ## Validation / Exit Criteria
   1. `dotnet build eShopWCFService.csproj` exits with code 0.
   ...
   33. `grep -c "NotFound()" Controllers/CatalogItemsController.cs` returns at
       least 3 - the lookup miss on GET plus PUT and DELETE of a non-existent entity.
 ● Completed in 2ms

Before saving, the agent reviewed the quality of its own draft and proposed five fixes including one to the instructions it was given: the interview had described the discount date lookup as a half-open range like the stock lookup, but the legacy GetDiscount check is a closed inclusive range (Start <= day && End >= day) and porting it half-open silently exclude discounts on their end date. Finally, the agent saves the definition as a draft:

Using tool: save_transformation (trusted)
 ● Name: wcf-to-webapi-dotnet10
 ● Description: Convert a .NET Framework 4.8 WCF SOAP service to an ASP.NET Core
   Web API on net10.0 with EF Core, REST/JSON controllers, DTOs, and async.
 ● Publish: No (Draft)
 ● New Transformation: Yes
 ● Completed in 3.1s

The transformation definition is saved and is reusable across other WCF project. Publish it to the registry so team members use it directly:

PS C:\repos> atx custom def publish -n "wcf-to-webapi-dotnet10" --sd .\wcf-to-webapi-dotnet10 --json
{"name":"wcf-to-webapi-dotnet10","version":"<minted-version-id>"}

Harden the definition before you publish it

A draft definition is a hypothesis until its output has been read. Before publishing, apply the draft to the codebase and inspect what it produced, and this loop is worth walking through, because it is where a definition earns its reuse.

The first application looked like success. The build was clean and the Validate phase reported 27 of 28 exit criteria passing:

## EXIT CRITERIA RESULTS

Criterion: 1. dotnet build eShopWCFService.csproj exits with code 0
Status: PASS
Evidence: Build succeeded. 0 Warning(s), 0 Error(s).

...

However, reading the generated code told a different story:

// generated on the first pass - compiles cleanly, passes the gate
var items = await _db.CatalogItems.AsNoTracking().ToListAsync();
return items.Where(x =>
    (brandIdFilter == 0 ? true : x.CatalogBrandId == brandIdFilter) &&
    (typeIdFilter == 0 ? true : x.CatalogTypeId == typeIdFilter)).ToList();

This is the legacy defect, preserved in async form: the whole table still materializes before the filter runs. The definition’s original criterion grepped for the legacy .ToList().Where() shape, which this code no longer matches so the gate stayed green. Reading further found five more defects of the same kind, all compiling, none caught by a criterion: the Max(Id)+1 key assignment retained along with the DatabaseGeneratedOption.None attribute that forces it, PUT and DELETE returning 204 for a missing entity instead of 404, DELETE binding a [FromBody] entity instead of the route id, and DateTime.Date comparisons EF Core cannot translate.

The fix is a feedback message to the agent aimed at the definition, not the files:

> The build passes but the correctness rules were not all applied. Fix the definition,
  not just the generated files, then re-apply. GetCatalogItemsAsync filters after
  ToListAsync (Services/CatalogService.cs), CreateCatalogItemAsync still computes
  MaxAsync(i => i.Id) + 1, and DatabaseGeneratedOption.None is still on all four models.

The agent amended the definition, adding a mechanically checkable exit criterion per defect class. This is the one it wrote for the filtering defect, note it targets the async shape that evaded the original check:

31. grep -nE "ToListAsync\(\)\s*;" -A2 Services/CatalogService.cs
    | grep -cE "\.(Where|FirstOrDefault|Any)\(" returns 0 — no LINQ predicate may
    appear after a materializing call. Converting .ToList().Where(...) to
    .ToListAsync() then .Where(...) preserves the whole-table scan and is not a fix.

Re-applying the amended definition to a fresh checkout produced code with all six defects fixed, and the validation gate now 34 criteria prove it. That is the whole economics of a reusable definition: patching generated code fixes one run; amending the definition fixes every subsequent run, and the criteria added in this loop are exactly what lets the next execution pass with no human steering at all.

Step 3: Execute the transformation

Once the definition published, execution is the short path a teammate ever needs to run. Pin the version for reproducibility, and pass the build command that becomes the agent’s per-step verification gate:

PS C:\repos\...\eShopWCFService> atx custom def exec `
  -n "wcf-to-webapi-dotnet10" `
  --tv "<minted-version-id>" `
  -p (Get-Location).Path `
  -c "dotnet build eShopWCFService.csproj"

atx analyzes the codebase and generates a transformation plan:

> The WCF project structure is fully analyzed. Let me create the transformation plan.

  **Codebase Analysis Summary:**
  - **Project type:** WCF Service Application targeting .NET Framework
  - **Files to convert:** 13 C# files - ICatalogService (10 operations),
    CatalogService.svc.cs, EF6 EntityModel, 5 data-contract models, seeding
    infrastructure
  - **Key constructs to convert:** [ServiceContract] -> attribute-routed controllers,
    [DataContract] entities -> EF Core entities + DTOs, Web.config bindings ->
    Program.cs + appsettings.json, synchronous EF6 -> async EF Core

atx creates new branch automatically before any edit, so the original branch is preserved:

Using tool: vcs_control (trusted)
 ● Action: setup
 ● Repository: C:\repos\eshop-wcf-service-sample
 ● Current Branch: main
 ● Will Create Branch: atx-result-staging-20260805_064143_27e9dc2c
 ● Note: Creates a new branch for transformation commits (original branch preserved)
 ● Completed in 267ms

The agent then works through the implementation steps, building after each step. Because the first 3 steps are interdependent, the WCF artifacts must be deleted before their ASP.NET Core replacements compile, the definition batches them, and the run lands as a single comprehensive commit on the staging branch:

PS C:\repos\eshop-wcf-service-sample> git log --oneline main..atx-result-staging-20260805_064143_27e9dc2c
298d295 Step 1: Complete WCF to ASP.NET Core Web API migration - replaced csproj with
        SDK-style net10.0, deleted WCF artifacts, migrated EF6 to EF Core, created async
        service layer, REST controllers with DTOs, Program.cs with DI, and
        MIGRATION-NOTES.md. Build status: Success

After all steps are complete, the validation summary confirms every exit criterion, with evidence per criterion:

# Validation Result Summary

## OVERALL STATUS: COMPLETE

**Criterion:** 1. `dotnet build eShopWCFService.csproj` exits with code 0.
**Status:** PASS
**Evidence:** Build succeeded with 0 warnings, 0 errors.

**Criterion:** 7. `test ! -f CatalogService.svc && echo PASS` prints PASS.
**Status:** PASS

**Criterion:** 30. `grep -rl "DatabaseGeneratedOption.None" Models/ | wc -l` returns 0.
**Status:** PASS

**Criterion:** 33. `grep -c "NotFound()" Controllers/CatalogItemsController.cs`
returns at least 3.
**Status:** PASS

## UNMET CRITERIA
None

As a result, 36 files changed, 10 SOAP operations became 10 REST endpoints across 5 controllers, and the project builds clean on .NET 10 with the assembly name preserved:

PS C:\repos\...\eShopWCFService> dotnet build eShopWCFService.csproj
  eShopWCFService -> bin\Debug\net10.0\WcfService1.dll

Build succeeded.
    0 Warning(s)
    0 Error(s)

Step 4: Verify beyond the gate

A passing validation gate proves your criteria pass, nothing more. Two minutes of independent checks are worth running after any transformation. The following checks verify the defect classes that matter for this migration, no legacy namespaces or artifacts, no filtering after materialization, no sync-over-async, endpoint parity, identity keys, preserved decimal precision, and all pass:

== Post-transformation verification (expectations in parentheses) ==
1. legacy namespaces (0)             : 0
2. svc/Web.config/packages.config (0): 0
3. filter-after-materialize (0)      : 0
4. DateTime.Date inside Where (0)    : 0
5. .Result / .Wait() (0)             : 0
6. HTTP endpoints (10)               : 10
7. MaxAsync / DbGeneratedOption.None (0): 0
8. HasPrecision preserved (>=1)      : 1

Reading the generated code still surfaced one quirk the gate had no criterion for: the list endpoint accepts pageSize and pageIndex parameters that are never applied, no Skip/Take in the query. Behavior matches the legacy service, so nothing fails, but the API surface advertises pagination does not perform. The fix is the same discipline as before: feed it back into the definition as a new rule, and the next repository benefits.

Before and after: Code comparison

The following four examples show what AWS Transform custom produced.

Service contract to attribute-routed controllerWCF (ICatalogService.cs / CatalogService.svc.cs):

[ServiceContract]
		public interface ICatalogService : IDisposable
{
    [OperationContract]
    CatalogItem FindCatalogItem(int id);
}

public CatalogItem FindCatalogItem(int id)
{
    CatalogItem item = ents.CatalogItems.FirstOrDefault(x => x.Id == id);
    if (item != null)
    {
        item.CatalogBrand = ents.CatalogBrands.FirstOrDefault(x => x.Id == item.CatalogBrandId);
        item.CatalogType = ents.CatalogTypes.FirstOrDefault(x => x.Id == item.CatalogTypeId);
        return item;
    }
    else
        return null;
}

C# (CatalogItemsController.cs) converted by AWS Transform custom:

[ApiController]
[Route("api/catalog/items")]
public class CatalogItemsController : ControllerBase
{
    private readonly ICatalogService _service;

    public CatalogItemsController(ICatalogService service)
    {
        _service = service;
    }

    [HttpGet("{id}")]
    public async Task<ActionResult<CatalogItemDto>> GetItem(int id)
    {
        var item = await _service.FindCatalogItemAsync(id);
        if (item == null)
            return NotFound();
        return Ok(MapToDto(item));
    }
}

Conversions: [ServiceContract][ApiController] with [Route], [OperationContract] → HTTP verb attributes, IDisposable removed (the DI container owns the DbContext lifetime), null return → NotFound(), manual navigation-property attachment → Include() in the service, entity → DTO to avoid serialization cycles.

In-memory filtering to SQL-translated query

WCF (CatalogService.svc.cs):

public List<CatalogItem> GetCatalogItems(int brandIdFilter, int typeIdFilter)
{
    bool brandFilterIsNull = brandIdFilter == 0;
    bool typeFilterIsNull = typeIdFilter == 0;
    return ents.CatalogItems.ToList().Where(x =>
        (brandFilterIsNull ? true : x.CatalogBrandId == brandIdFilter) &&
        (typeFilterIsNull ? true : x.CatalogTypeId == typeIdFilter)).ToList();
}

C# (Services/CatalogService.cs):

public async Task<List<CatalogItem>> GetCatalogItemsAsync(int? brandIdFilter, int? typeIdFilter)
{
    var query = _db.CatalogItems.AsNoTracking().AsQueryable();

    if (brandIdFilter.HasValue && brandIdFilter.Value != 0)
    {
        query = query.Where(x => x.CatalogBrandId == brandIdFilter.Value);
    }

    if (typeIdFilter.HasValue && typeIdFilter.Value != 0)
    {
        query = query.Where(x => x.CatalogTypeId == typeIdFilter.Value);
    }

    return await query.ToListAsync();
}

Conversions: predicates composed before the materializing call so EF Core translates them to SQL, sentinel 0 → nullable parameters, .ToList() → await .ToListAsync(), AsNoTracking on the read path.

Date comparison EF Core can translate

WCF (CatalogService.svc.cs):

CatalogItemsStock s = ents.CatalogItemsStocks.Where(x => x.CatalogItemId == catalogItemId)
    .ToList().Where(y => y.Date.Date == date.Date).FirstOrDefault();
if (s != null)
    return s.AvailableStock;
else
    return 0;

C# (Services/CatalogService.cs):

var dayStart = date.Date;
var dayEnd = dayStart.AddDays(1);

var stock = await _db.CatalogItemsStocks
    .AsNoTracking()
    .Where(x => x.CatalogItemId == catalogItemId && x.Date >= dayStart && x.Date < dayEnd)
    .FirstOrDefaultAsync();

return stock?.AvailableStock ?? 0;

Conversions: DateTime.Date inside the predicate (untranslatable by EF Core) → half-open range on the raw column, computed into locals first.

Non-SDK project file to SDK-style

The 142-line non-SDK .csproj with packages.config became this, in its entirety:

<Project Sdk="Microsoft.NET.Sdk.Web">
  <PropertyGroup>
    <TargetFramework>net10.0</TargetFramework>
    <RootNamespace>WcfService1</RootNamespace>
    <AssemblyName>WcfService1</AssemblyName>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="Microsoft.EntityFrameworkCore" Version="10.0.*" />
    <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="10.0.*" />
    <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="10.0.*" PrivateAssets="all" />
  </ItemGroup>
</Project>

Conversions: packages.configPackageReference, EF 6.1.3 → EF Core 10, RootNamespace/AssemblyName preserved verbatim as required.

Complete conversion reference

The following table summarizes the WCF-to-Web-API conversions that AWS Transform custom handled in this migration:

WCF / .NET Framework construct ASP.NET Core equivalent
[ServiceContract] interface [ApiController] classes with [Route]
[OperationContract] method [HttpGet] / [HttpPost] / [HttpPut] / [HttpDelete] action
.svc host file Kestrel + MapControllers() in Program.cs
basicHttpBinding endpoint (Web.config) Attribute routing; no binding configuration
mex metadata endpoint / WSDL OpenAPI-ready controllers
[DataContract] / [DataMember] DTOs in Contracts\ + System.Text.Json
Return null for absence NotFound() (404)
void create / update / delete CreatedAtAction (201) / NoContent (204) / 404 on missing
EF6 DbContext (self-constructed) EF Core DbContext via constructor injection, scoped
CreateDatabaseIfNotExists initializer Database.Migrate() + seed guarded by existence check
Max(Id) + 1 with DatabaseGeneratedOption.None Database identity columns
.ToList().Where(...) Composed IQueryable + await ToListAsync()
DateTime.Date comparisons Half-open range on the raw column
Synchronous data access async/await end to end, AsNoTracking on reads
IDisposable service + manual Dispose DI-managed lifetime
Web.config connection strings appsettings.json + environment-variable fallback

Next steps: From a migrated service to a modernized estate

A service on cross-platform .NET unlocks the infrastructure work that .NET Framework blocked. AWS Transform Full-Stack Windows Modernization provides AI-driven capabilities to:

  • Port remaining .NET Framework applications to cross-platform .NET
  • Modernize SQL Server databases to Amazon Aurora PostgreSQL
  • Update UI frameworks from ASP.NET Web Forms to Blazor
  • Deploy applications to Amazon EC2 Linux or Amazon ECS

This two-step approach, first WCF to Web API using AWS Transform custom, then the surrounding estate using Full-Stack Windows Modernization, provides a clear, incremental path to fully modernize legacy WCF-based systems. The transformation definition is reusable: once hardened, apply it across every WCF service in your organization.

Clean up

If you launched a Windows EC2 instance for this walkthrough, terminate it to avoid ongoing charges. AWS Transform custom does not create other resources during this process. Transformation definitions remain in your registry at no cost; delete one with atx custom def delete -n <name> if you no longer need it. Locally, the atx-result-staging-20260805_064143_27e9dc2c branch holds the result – merge or delete it, and the atx-baseline tag returns you to pristine input.

Conclusion

This post showed how to use AWS Transform custom natively on Windows with PowerShell to automate the migration of a .NET Framework WCF SOAP service to an ASP.NET Core Web API on .NET 10. The AI agent created a 10-step, 34-criterion transformation definition through a short authoring interview, converted 10 SOAP operations into ten REST endpoints across five controllers with zero build errors, and preserved the business logic and assembly identity.

Two practices made the difference between output that compiles and output that is correct:

  1. Writing exit criteria for the defect classes that survive compilation, status-code semantics, query translation, key generation and
  2. Reading the generated code rather than trusting a green gate.

Feed every finding back into the definition, and the next repository is cheaper than this one.

The sample source code used in this post is available at sample-aws-transform-custom-wcf-to-webapi. To get started with AWS Transform custom, visit the AWS Transform getting started guide to learn more about AWS Transform custom.

Vishwanath Uppala

Vishwanath Uppala

Vishwanath is a Senior Microsoft Specialist Solutions Architect at AWS with over two decades of experience as a developer and architect. He helps organizations across regulated and consumer industries modernize through cloud and AI technologies.