Claude Code vs GitHub Copilot for ASP.NET Core Developers

If you write C# for a living, the question is no longer whether to try an AI coding tool. The real decision is which workflow fits the way you already build ASP.NET Core applications. This guide compares Claude Code and GitHub Copilot for C# work, then gives you a repeatable evaluation you can run on your own code. Claude Code is an agentic coding tool that you drive from a terminal, a desktop app, or an editor extension. GitHub Copilot is a family of features that lives inside Visual Studio, VS Code, GitHub, and a command-line agent. They overlap more than they used to, but they start from different places.

Short answer: neither tool wins for every C# developer. Copilot is the more natural fit if you work in Visual Studio and want inline completions. Claude Code is a strong fit if you like a terminal-driven agent that edits many files under explicit permission rules. The only reliable way to choose is to test both on your own code, and this guide shows you how.

Claude Code vs GitHub Copilot for ASP.NET Core and C# development

What this article is, and is not: it is an evaluation guide, not a benchmark. It reports no head-to-head test results, timings, or accuracy percentages. Plans and prices were last cross-checked on October 25, 2026 against GitHub's plans and billing documentation, Anthropic's Help Center articles on Team and Enterprise plans, and Anthropic's pricing page, and the documentation behind each product claim is linked where it appears. Vendors change plans and prices often, so confirm on the official pages before you buy anything.

About the sample code: the expected results in this guide (such as "7 passed") describe what the code is designed to produce, based on reading it against Microsoft's documentation. It was reviewed by inspection, not executed for this article, and a second review pass in an environment without the .NET SDK could not build or run it either. Treat your own build and test output as the final word, and use the troubleshooting section if it differs. The sample is an evaluation fixture, not a production template; what to add before shipping is covered later.


Claude Code vs GitHub Copilot for C#: quick verdict

There is no universal winner, and anyone who declares one without running your codebase is guessing. Based on the vendors' own documentation, this is a reasonable starting split:

  • Start with GitHub Copilot if you live in Visual Studio 2026 or VS Code, want inline completions, and want AI features tied to GitHub pull requests, issues, and organization policies.
  • Start with Claude Code if you are comfortable in a terminal, want an agent that plans and edits across many files with explicit allow, ask, and deny rules, and want a tool that is available across the terminal, the desktop app, and VS Code.
  • Consider both if you want completions in the IDE and a separate agent for larger tasks. GitHub's documentation includes a page on Anthropic Claude in GitHub Copilot and a supported models list, so the two products are not strictly "different AI brains."

How that plays out for different developers:

  • Students and beginners: Copilot Free, or Copilot Student if you are a verified student, is the lowest-cost way to experiment. Ask the tool to explain code, then write and run it yourself.
  • Visual Studio-centric teams: Copilot has the native path inside Visual Studio. Anthropic documents no official Visual Studio integration for Claude Code (details below).
  • Terminal and cross-editor developers: Claude Code is available across the terminal, the desktop app, and VS Code, which may appeal if you mix C# with other stacks. Features can differ by surface, so check the documentation for the one you use.
  • Regulated or security-sensitive teams: start with your security team's approval, plan type, retention terms, and logging needs, not with features.

The diagram below turns the split into a quick decision. Treat it as a hypothesis to test, not a ruling.

Which tool to try firstA flowchart. If you mostly work in Visual Studio, start with Copilot. If not, and you prefer a terminal, start with Claude Code. If neither applies, run the evaluation on your own code and decide with your scorecard.Pick a tool to testMostly inVisual Studio?YesStart withCopilotNoPrefer workingin a terminal?YesStart withClaude CodeNoRun the evaluationon your own codeDecide withyour scorecard
A starting-point decision, not a verdict. If you mostly use Visual Studio, begin with Copilot; if you prefer a terminal, begin with Claude Code; otherwise run the evaluation described later in this article.

Terms that matter for this comparison

  • Language model: the underlying AI model that generates text and code. Both products can use more than one model.
  • AI coding assistant: a tool that suggests or explains code while you stay in control of every edit.
  • Coding agent: a tool that can plan a task, read files, edit them, run commands such as dotnet build, look at the result, and keep going.
  • Command-line tool: a program you run in a terminal. Claude Code started here, and GitHub now ships Copilot CLI too.
  • DTO (data transfer object): a small class that describes what an API accepts or returns, so you never expose database entities directly.
  • Integration test: a test that runs the real request pipeline (routing, validation, controllers, database) instead of one method in isolation.

Both products now span several of these categories, which is why feature-for-feature tables can mislead. The tool around the model also decides what context the model sees, so the same model can behave differently in each product. Compare workflows, not labels.

What Claude Code and GitHub Copilot are

Claude Code

Claude Code is Anthropic's agentic coding tool. You start it in a project folder, describe a goal, and it can read your code, make edits, and run commands, asking for approval according to the permission settings you choose.

  • Operating systems: Anthropic's setup page lists macOS 13.0 or later, Windows 10 (version 1809) or later, Windows Server 2019 or later, Ubuntu 20.04 or later, Debian 10 or later, and Alpine Linux 3.19 or later.
  • Windows: you can run it natively or inside WSL (Windows Subsystem for Linux). On native Windows, Git for Windows is recommended but optional; without it, Claude Code runs shell commands through PowerShell.
  • Editors: the terminal, a desktop app, and an official VS Code extension are documented. Anthropic also documents a JetBrains plugin, but its supported-IDE list names IntelliJ IDEA, PyCharm, Android Studio, WebStorm, PhpStorm, and GoLand and does not name Rider. Users have reported Rider detection problems in Anthropic's issue tracker, so test it before relying on it. The command-line tool still runs in Rider's terminal.
  • Visual Studio (the full IDE): Anthropic's documentation describes no first-party Visual Studio integration. You can run the command-line tool in Visual Studio's terminal window or a separate terminal. Community-built Visual Studio extensions exist, but they are not from Anthropic, so review any extension's source and permissions before installing it.
  • Accounts: Claude Code requires a Pro, Max, Team, Enterprise, or Console account, and the free Claude plan does not include it. It can also run through Amazon Bedrock, Google Cloud, or Microsoft Foundry.

Features that help on .NET projects:

  • Project instructions: a CLAUDE.md file in your repository stores conventions such as the target framework, test commands, and naming rules.
  • Permissions: allow, ask, and deny rules let you pre-approve dotnet build and dotnet test while still reviewing everything else. A working example appears later.
  • C# code intelligence: Claude Code supports language-server plugins for features such as diagnostics and go-to-definition. Check Anthropic's plugin directory for a C# plugin and its requirements, because such plugins typically need a separately installed C# language server.
  • Extensibility: subagents, hooks, and MCP servers exist. Copilot's agents support MCP servers too, so this alone is not a deciding factor.
  • Sandboxing: an OS-level sandbox can limit what shell commands touch. Anthropic's documentation lists macOS, Linux, and WSL 2 as supported and states that native Windows is not supported, where commands run unsandboxed, so if you develop on Windows and want the sandbox, run Claude Code inside WSL 2. Network access is blocked by default until you add hosts to the sandbox's allowed-domains list, so dotnet restore can fail to reach your NuGet feed until you allow that host.

GitHub Copilot

GitHub Copilot is a set of features rather than one program. For a .NET developer, four surfaces matter most:

  • Inline suggestions and next edit suggestions in Visual Studio and VS Code. GitHub says these are not billed in AI credits on paid plans.
  • Copilot Chat and agent mode inside the IDE, where Copilot can work through a multi-step task.
  • Copilot CLI, a terminal-based agent. See GitHub's CLI quickstart and its allowed-tools documentation, and note that GitHub's plans page says all plans include Copilot CLI.
  • Copilot cloud agent and code review on GitHub, which work from issues and pull requests.

Visual Studio specifics. Visual Studio 2026 includes Copilot Chat and agent features, and some features ship as previews that differ by plan or organization policy. GitHub's billing documentation ties accurate usage and pricing information in Visual Studio to a minimum IDE version, so update to the latest Visual Studio 2026 build before you evaluate. Copilot also runs in other editors, including JetBrains IDEs, which can matter if your team mixes Visual Studio and Rider.

Models. GitHub's plans page lists Claude models alongside models from other providers. In practice, "Claude versus Copilot" can be partly a question of interface and workflow, not just the model underneath. Free and Student plans use automatic model selection only.


Side-by-side comparison

Key differences between Claude Code and GitHub Copilot (checked October 25, 2026)
AreaClaude CodeGitHub Copilot
Primary workflowAgent you direct in a terminal, desktop app, or editor extensionFeatures across IDE, CLI, GitHub.com, and cloud agent
Inline completions while typingNo inline-completion mode in the documentation reviewedYes; unlimited on paid plans and Copilot Student, and not billed in AI credits (Copilot Free: up to 2,000 per month)
Visual Studio 2026No official integration documented; CLI can run alongside; community extensions existNative Copilot Chat and agent features
VS CodeOfficial extensionNative
JetBrains IDEs (including Rider)Documented plugin for several JetBrains IDEs; Rider is not named in the docs, so test itSupported
Terminal agentCore productCopilot CLI (GitHub's plans page says all plans include it)
MCP serversSupportedSupported
Project instructionsCLAUDE.md.github/copilot-instructions.md, personal and organization instructions, prompt files
Model choiceAnthropic models (or Bedrock, Google Cloud, Foundry)Multiple providers, depending on plan
Usage modelIncluded in paid Claude plans with shared session and weekly limits, or pay-as-you-go API billingPlan price plus monthly AI credits; usage-based since June 1, 2026
GitHub PR and issue integrationPossible through tools and integrationsBuilt in (code review, cloud agent)
Free tierNone for Claude CodeCopilot Free with limited features

Some rows are not like-for-like. Claude Code has no direct equivalent to GitHub.com pull request summaries, and Copilot's inline completion has no direct equivalent in Claude Code. That is why the evaluation below is built around tasks rather than features.


Pricing and usage limits

The two vendors meter usage differently, and both change plans often. Figures below were cross-checked on October 25, 2026, against GitHub's plans and billing documentation, Anthropic's Help Center, and the Anthropic's pricing page. Prices exclude tax.

GitHub Copilot: individual plans

Since June 1, 2026, Copilot usage that calls AI models is metered in GitHub AI credits, and one credit equals $0.01. Chat, agents, CLI, and cloud agent draw from the monthly allowance based on token usage and the model you pick.

GitHub Copilot individual plans (GitHub Docs, checked October 25, 2026)
PlanPrice per monthBase creditsFlex allotmentTotal monthly creditsCode completions
Free$0A limited allowance; GitHub does not publish a number. Auto model selection only.Up to 2,000
StudentFree for verified studentsAn allowance; no number published. Auto model selection only.Unlimited
Pro$101,0005001,500Unlimited
Pro+$393,9003,1007,000Unlimited
Max$10010,00010,00020,000Unlimited

Each total is the base amount plus the flex allotment, and base credits are used first. At $0.01 per credit, Pro includes the equivalent of $15 of usage, Pro+ $70, and Max $200. Pro+ and Max include access to premium models, and Pro includes a selection of models.

Availability note: plan availability and sign-up options change, so check GitHub's plans page to see what you can buy today. When this article was checked on October 25, 2026, that page listed Free, Student, Pro, Pro+, Max, Business, and Enterprise without a sign-up pause notice for the individual plans. If you hold an annual Pro or Pro+ subscription, also read GitHub's billing page for individuals to see how your plan is metered.

GitHub Copilot: organizations and enterprises

GitHub Copilot organization plans (GitHub Docs, checked October 25, 2026)
PlanPrice per granted seat per monthIncluded AI credits per user per month
Business$191,900
Enterprise$393,900

Availability note: GitHub announced on April 22, 2026 that it was pausing new self-serve Copilot Business sign-ups for organizations on GitHub Free and GitHub Team and directing them to sales; existing Business customers were not affected. When this article was checked on October 25, 2026, that changelog entry was marked as retired and the plans page showed no pause notice, but neither page said that self-serve sign-up had reopened, and third-party sources still described the contact-sales route. If your organization is on GitHub Free or GitHub Team, confirm in your organization settings whether you can subscribe yourself or need to contact GitHub sales. This does not affect individual plans.

Included credits are pooled at the billing entity, so an enterprise with 100 Business users shares 190,000 credits. Administrators can set budgets for usage beyond the included credits; check the default for your organization before rollout. Copilot Enterprise requires a GitHub Enterprise Cloud subscription, so confirm the details on GitHub's plans page. GitHub's documentation also describes GitHub Actions minutes for some cloud features such as the cloud agent and code review, so check whether your usage incurs those costs too.

Claude Code

Claude Code has no separate subscription. It is included in the paid Claude plans and shares usage limits with the rest of Claude. Limits reset on a rolling five-hour session window, and paid plans add weekly limits, so terminal work and chats draw from one pool. Usage-based Enterprise plans work differently: the seat fee includes no usage allowance, every token is billed at API rates, and administrators can set organization-level and per-user spend limits. For heavy sessions you can use a Console account at API rates, or buy usage credits on a paid plan.

Claude plans and Claude Code (Anthropic pricing, cross-checked October 25, 2026)
PlanPriceNotes
Free$0Does not include Claude Code
Pro$20 per month billed monthly, or $17 per month billed annuallyIncludes Claude Code
Max 5x$100 per monthAbout 5x the usage of Pro; Max is sold monthly
Max 20x$200 per monthAbout 20x the usage of Pro
Team, Standard seat (teams of 2 to 150)$20 per seat per month billed annually, or $25 billed monthlyClaude Code is included with every Team seat, per Anthropic's Claude Code with Team or Enterprise plans article
Team, Premium seat$100 per seat per month billed annually, or $125 billed monthlyIncludes Claude Code with more usage than a Standard seat; seat types can be mixed
Enterprise$20 per user per month, billed annually (single Enterprise seat), plus usage billed separately at API ratesThe seat includes Claude Code but no usage allowance. Administrators can set organization-level and per-user spend limits. Self-serve usage is bought upfront as credits, and sales-assisted usage is billed monthly in arrears. Self-serve Enterprise has a 20-seat minimum, and sales-assisted terms are agreed with Anthropic's sales team. Older Chat and Standard/Premium seat contracts have different terms and move to the single seat at renewal. See Anthropic's Enterprise plan and Enterprise billing articles
Console (API)Pay as you go at API token ratesBilled separately from subscriptions

Team seat minimums and usage allowances have changed more often than consumer plans, so verify both on the live pricing page before you buy for a team.

What to compare

Do not compare sticker prices alone. Estimate your real usage during the evaluation: how many agent sessions per day, how large your repository is, and which models you use. A metered plan can be cheap for light use and expensive for long autonomous sessions, and a flat subscription can feel generous until you hit a session or weekly limit mid-sprint. Record the usage each tool reports for every run (the scorecard below has a column for it).


Privacy and data handling

Claude Code's data policy depends on account type, per Anthropic's data usage page:

  • Free, Pro, and Max: a setting controls whether your data can be used to improve future models. With it on, retention is five years; with it off, 30 days. You can change the setting at any time in your privacy controls.
  • Team, Enterprise, and API: Anthropic does not train generative models on code or prompts sent to Claude Code under commercial terms, unless the customer opts into a program such as the Development Partner Program. Standard retention is 30 days. Zero data retention is available to qualified accounts for Claude Code on Claude for Enterprise and is not part of the standard Enterprise plan.
  • Local files: Claude Code stores session transcripts on your machine in plaintext for 30 days by default, so keep secrets out of prompts and files.
  • Feedback commands: transcripts you send with /feedback are retained for five years, so do not send them from a project with sensitive code. If you answer yes to the optional follow-up that offers to share a session transcript after a quality survey, the shared transcript is retained for up to six months.

For Copilot, data handling depends on your plan and your organization's policies. GitHub's individual-plans page notes that Copilot Free lacks features enterprises need, including access management, audit logs, policy management, file exclusion, usage data, and indemnification coverage. This article does not map which privacy controls each Copilot plan includes, so read GitHub's privacy documentation for your exact plan before sending proprietary code, especially if your employer has compliance requirements. GitHub's model-hosting documentation warns that when Claude Fable 5 or Claude Fable 5.1 is used through Copilot, Anthropic retains prompts and outputs by default to operate safety classifiers, and that other Claude models continue to run under zero data retention. Eligible enterprises can request a time-limited zero-data-retention exemption through the end of 2026 while Anthropic rolls out Enterprise Frontier Safeguards (EFS); after that, continued use of those two models requires EFS. An administrator must still enable each model, and enterprises that use the exemption agree to use the model only for internal operations. Check the model-specific terms and your organization's configuration before enabling a model.


A reproducible ASP.NET Core evaluation

Important: this article does not report head-to-head results. Neither tool was run against this project for the article, so there are no scores, timings, or accuracy percentages here. What follows is a method you can run yourself, with the same repository, prompts, and acceptance criteria for each tool. You will build a small ASP.NET Core Web API with seven integration tests, then run the evaluation and score the results. The evaluation contains six numbered tasks, with Task 3 split into two independent scenarios. That gives seven evaluation scenarios, two tools, and two runs per scenario: 28 runs in total.

What you need before you start

  • The .NET 10 SDK and Git, with your Git name and email configured (git config --global user.name and git config --global user.email).
  • Visual Studio 2026, or VS Code with the C# Dev Kit extension.
  • For Claude Code: a paid Claude plan (Pro, Max, Team, or Enterprise), a Console account, or access through Amazon Bedrock, Google Cloud, or Microsoft Foundry. The free Claude plan does not include Claude Code.
  • For GitHub Copilot: a GitHub account with a Copilot plan. Copilot Free works for light testing, but long agent sessions can use its small credit allowance quickly. In Visual Studio 2026, update to the latest build.
  • A local SQL Server instance for running the API by hand: LocalDB on Windows, or a SQL Server container on macOS and Linux. The automated tests do not need it.
  • A Bash-compatible shell for the commands in this guide: Git Bash, WSL, or a macOS or Linux terminal. PowerShell 7 also works if you swap mkdir -p for New-Item -ItemType Directory -Force.

.NET SDK and installing the tools

This evaluation targets .NET 10, the long-term-support release that arrived in November 2025 and is supported through November 2028. Any current 10.0 SDK works; Microsoft's download page lists the newest one. If you are still on .NET 8 or 9, both reach end of support on November 10, 2026 per Microsoft's announcement and support policy, so plan the move.

Create a folder for the experiment:

Bash
mkdir codingvila-orders-eval
cd codingvila-orders-eval

Then save the following as global.json in that folder:

JSON
{
  "sdk": {
    "version": "10.0.100",
    "rollForward": "latestFeature"
  }
}

With latestFeature, the SDK uses the newest installed 10.0 feature band at or above 10.0.100 and will not jump to a newer major version. Run dotnet --version and record the exact output. You should see a 10.0.x version.

Install Claude Code with the native installer on macOS, Linux, or WSL:

Bash
curl -fsSL https://claude.ai/install.sh | bash
claude --version

On native Windows, run this in PowerShell (your prompt starts with PS). Installations through WinGet do not auto-update.

PowerShell
irm https://claude.ai/install.ps1 | iex
claude --version

# Alternative (does not auto-update)
winget install Anthropic.ClaudeCode

When the installer finishes, open a new terminal and run claude --version; you should see a version number. Then run claude in your project folder and sign in. For Copilot, sign in to Visual Studio 2026 or VS Code with a GitHub account that has a Copilot plan; for the terminal agent, follow GitHub's Copilot CLI quickstart.

Create the sample project

The .NET 10 SDK creates a .slnx solution file (a newer XML solution format) by default, and dotnet build and dotnet test work with it. The commands pin every EF Core package, Microsoft.AspNetCore.Mvc.Testing, and dotnet-ef to the same version so the packages cannot drift apart. Version 10.0.13 was a current stable servicing release on NuGet when this article was checked on October 25, 2026. If a newer 10.0 patch exists when you read this, use it, but keep every EF Core package, Microsoft.AspNetCore.Mvc.Testing and dotnet-ef on the same version. The xunit template is whatever your SDK generates, and the tests below use only Fact, Theory, InlineData, Assert, and IClassFixture.

Bash
git init -b main
dotnet new gitignore
dotnet new sln -n CodingvilaOrders
dotnet new webapi -n OrdersApi --use-controllers
dotnet new xunit -n OrdersApi.Tests
dotnet sln add OrdersApi/OrdersApi.csproj OrdersApi.Tests/OrdersApi.Tests.csproj
dotnet add OrdersApi package Microsoft.EntityFrameworkCore.SqlServer --version 10.0.13
dotnet add OrdersApi package Microsoft.EntityFrameworkCore.Design --version 10.0.13
dotnet add OrdersApi.Tests reference OrdersApi/OrdersApi.csproj
dotnet add OrdersApi.Tests package Microsoft.AspNetCore.Mvc.Testing --version 10.0.13
dotnet add OrdersApi.Tests package Microsoft.EntityFrameworkCore.Sqlite --version 10.0.13
dotnet tool install --global dotnet-ef --version 10.0.13

Delete the template's sample files so they don't distract either tool: OrdersApi/Controllers/WeatherForecastController.cs, OrdersApi/WeatherForecast.cs, and OrdersApi.Tests/UnitTest1.cs. If dotnet-ef is already installed, run dotnet tool update --global dotnet-ef --version 10.0.13 instead of the install command. If NuGet does not list Microsoft.AspNetCore.Mvc.Testing at 10.0.13, use the nearest 10.0.x version it does list.

The starter code is deliberately small. It is a teaching baseline with no authentication, so it is not a production design; the section on what the sample leaves out lists what a real API still needs. The source files below are collapsed to keep the comparison easy to find; expand the section and create each file.

Show the sample project source files

File: OrdersApi/Models/Product.cs

C#
namespace OrdersApi.Models;

public class Product
{
    public int Id { get; set; }
    public required string Name { get; set; }
    public decimal Price { get; set; }
    public int StockQuantity { get; set; }
}

File: OrdersApi/Data/AppDbContext.cs

C#
using Microsoft.EntityFrameworkCore;
using OrdersApi.Models;

namespace OrdersApi.Data;

public class AppDbContext(DbContextOptions<AppDbContext> options) : DbContext(options)
{
    public DbSet<Product> Products => Set<Product>();

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Product>(entity =>
        {
            entity.Property(p => p.Name).HasMaxLength(200).IsRequired();
            entity.Property(p => p.Price).HasPrecision(18, 2);
        });
    }
}

File: OrdersApi/Dtos/ProductDtos.cs

C#
using System.ComponentModel.DataAnnotations;

namespace OrdersApi.Dtos;

public class CreateProductRequest
{
    [Required, StringLength(200, MinimumLength = 1)]
    public string Name { get; init; } = string.Empty;

    [Range(typeof(decimal), "0.01", "1000000", ParseLimitsInInvariantCulture = true)]
    public decimal Price { get; init; }

    [Range(0, 1_000_000)]
    public int StockQuantity { get; init; }
}

public record ProductResponse(int Id, string Name, decimal Price, int StockQuantity);

The Range(typeof(decimal), ...) form compares decimals exactly instead of converting them to double. Because the limits are strings, ParseLimitsInInvariantCulture = true makes "0.01" mean the same thing on every server; without it, the limits are parsed with the current culture, and a culture that uses a comma as the decimal separator could misread them. The request DTO also keeps callers from setting fields such as Id directly, which is the over-posting protection Task 6 will ask each tool to reason about. See Microsoft's RangeAttribute documentation.

File: OrdersApi/Controllers/ProductsController.cs (read and create endpoints only; Task 2 asks each tool to add the rest)

C#
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using OrdersApi.Data;
using OrdersApi.Dtos;
using OrdersApi.Models;

namespace OrdersApi.Controllers;

[ApiController]
[Route("api/[controller]")]
public class ProductsController(AppDbContext db) : ControllerBase
{
    [HttpGet("{id:int}")]
    public async Task<ActionResult<ProductResponse>> GetById(int id, CancellationToken ct)
    {
        var product = await db.Products
            .AsNoTracking()
            .FirstOrDefaultAsync(p => p.Id == id, ct);

        if (product is null)
        {
            return NotFound();
        }

        return new ProductResponse(product.Id, product.Name, product.Price, product.StockQuantity);
    }

    [HttpPost]
    public async Task<ActionResult<ProductResponse>> Create(CreateProductRequest request, CancellationToken ct)
    {
        var product = new Product
        {
            Name = request.Name.Trim(),
            Price = request.Price,
            StockQuantity = request.StockQuantity
        };

        db.Products.Add(product);
        await db.SaveChangesAsync(ct);

        var response = new ProductResponse(product.Id, product.Name, product.Price, product.StockQuantity);
        return CreatedAtAction(nameof(GetById), new { id = product.Id }, response);
    }
}

File: OrdersApi/Program.cs (replace the whole file). The Testing check lets the test project supply its own database, and the empty partial class at the bottom makes Program visible to WebApplicationFactory. UseHttpsRedirection stays on; the HTTPS section explains how to call the API.

C#
using Microsoft.EntityFrameworkCore;
using OrdersApi.Data;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();
builder.Services.AddOpenApi();

// Integration tests register their own database, so skip SQL Server there.
if (!builder.Environment.IsEnvironment("Testing"))
{
    var connectionString = builder.Configuration.GetConnectionString("DefaultConnection")
        ?? throw new InvalidOperationException(
            "Connection string 'DefaultConnection' is missing. Set it with dotnet user-secrets.");

    builder.Services.AddDbContext<AppDbContext>(options =>
        options.UseSqlServer(connectionString));
}

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.MapOpenApi();
}

app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();

app.Run();

public partial class Program { }

The tests run the real ASP.NET Core pipeline against an in-memory SQLite database, so they need no SQL Server. The first file is a custom factory that swaps SQL Server for SQLite. Setting the environment to Testing also stops the app from loading your user secrets during tests.

File: OrdersApi.Tests/OrdersApiFactory.cs

C#
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using OrdersApi.Data;

namespace OrdersApi.Tests;

public sealed class OrdersApiFactory : WebApplicationFactory<Program>
{
    // An in-memory SQLite database lives only while this connection stays open.
    private readonly SqliteConnection _connection = new("DataSource=:memory:");

    protected override void ConfigureWebHost(IWebHostBuilder builder)
    {
        builder.UseEnvironment("Testing");
        builder.ConfigureServices(services =>
            services.AddDbContext<AppDbContext>(options => options.UseSqlite(_connection)));
    }

    protected override IHost CreateHost(IHostBuilder builder)
    {
        _connection.Open();

        var host = base.CreateHost(builder);

        using var scope = host.Services.CreateScope();
        scope.ServiceProvider.GetRequiredService<AppDbContext>().Database.EnsureCreated();

        return host;
    }

    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
            _connection.Dispose();
        }

        base.Dispose(disposing);
    }
}

The factory calls EnsureCreated instead of applying your migrations, so these tests check your API logic but not your migrations. They also run against SQLite, which differs from SQL Server in decimal handling, collation, and concurrency behavior. A passing test run here does not prove the code works on SQL Server. Microsoft's EF Core testing guidance covers the trade-offs, and teams that need higher fidelity can test against SQL Server in a container. All tests in this class share one fixture, so they share one database.

File: OrdersApi.Tests/ProductsEndpointTests.cs. The Theory contributes four test cases and each Fact one, for seven in total.

C#
using System.Net;
using System.Net.Http.Json;
using Microsoft.AspNetCore.Mvc.Testing;
using OrdersApi.Dtos;
using Xunit;

namespace OrdersApi.Tests;

public class ProductsEndpointTests(OrdersApiFactory factory) : IClassFixture<OrdersApiFactory>
{
    // An https base address keeps UseHttpsRedirection from redirecting test requests,
    // even if ASPNETCORE_HTTPS_PORT happens to be set in your shell.
    private readonly HttpClient _client = factory.CreateClient(
        new WebApplicationFactoryClientOptions { BaseAddress = new Uri("https://localhost") });

    [Fact]
    public async Task Create_ValidRequest_ReturnsCreatedAndProductCanBeRead()
    {
        var response = await _client.PostAsJsonAsync("/api/products",
            new { name = "Codingvila notebook", price = 12.50m, stockQuantity = 10 });

        Assert.Equal(HttpStatusCode.Created, response.StatusCode);

        var created = await response.Content.ReadFromJsonAsync<ProductResponse>();
        Assert.NotNull(created);
        Assert.Equal("Codingvila notebook", created.Name);
        Assert.NotNull(response.Headers.Location);

        var fetched = await _client.GetAsync(response.Headers.Location);
        Assert.Equal(HttpStatusCode.OK, fetched.StatusCode);
    }

    [Theory]
    [InlineData("", 10.0, 1)]
    [InlineData("   ", 10.0, 1)]
    [InlineData("Valid name", 0.0, 1)]
    [InlineData("Valid name", 10.0, -1)]
    public async Task Create_InvalidInput_ReturnsBadRequest(string name, double price, int stockQuantity)
    {
        var response = await _client.PostAsJsonAsync("/api/products",
            new { name, price = (decimal)price, stockQuantity });

        Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
    }

    [Fact]
    public async Task Create_NameLongerThan200Characters_ReturnsBadRequest()
    {
        var response = await _client.PostAsJsonAsync("/api/products",
            new { name = new string('x', 201), price = 10m, stockQuantity = 1 });

        Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
    }

    [Fact]
    public async Task GetById_MissingProduct_ReturnsNotFound()
    {
        var response = await _client.GetAsync("/api/products/999999");

        Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
    }
}

The diagram shows why the code is split this way. Manual runs use SQL Server through UseSqlServer, while the tests go through OrdersApiFactory into the same controllers and use an in-memory SQLite database through UseSqlite.

How the sample API runs in development and in testsAn architecture diagram. In development, a manual HTTPS request reaches OrdersApi, which uses the SQL Server provider to talk to SQL Server. In tests, xUnit tests use OrdersApiFactory to send in-process requests to the same OrdersApi pipeline, which uses the SQLite provider with an in-memory SQLite database.Manual request(curl or browser)xUnit tests(OrdersApiFactory)HTTPSin-processOrdersApi (ASP.NET Core Web API)ProductsController, validation, AppDbContextUseSqlServerUseSqliteSQL ServerLocalDB or containerSQLite in memory(tests only)
The same OrdersApi pipeline serves both paths. Manual runs talk to SQL Server over HTTPS; the tests swap in an in-memory SQLite database, so they run without any server installed.

Protect secrets before you create the database

Keep connection strings out of source control and out of files an agent will read. Use user secrets for local development, and run these commands from the solution folder. User secrets load only in the Development environment, which the standard launch profiles and dotnet ef use by default. They are stored in your user profile, outside the repository.

Bash
dotnet user-secrets init --project OrdersApi/OrdersApi.csproj
dotnet user-secrets set "ConnectionStrings:DefaultConnection" "<your-local-connection-string>" --project OrdersApi/OrdersApi.csproj

For Windows LocalDB, a development-only value looks like Server=(localdb)\MSSQLLocalDB;Database=OrdersApiEval;Trusted_Connection=True;TrustServerCertificate=True. For a SQL Server container, use its host, port, user, and password instead. TrustServerCertificate=True skips certificate validation, so never use it in production.

Create the schema and tag the baseline

Create the schema, then confirm the baseline builds and passes before any AI tool touches it. Both --project and --startup-project are set so dotnet ef finds the right project from the solution folder. For background on migrations, see Microsoft's EF Core migrations documentation.

Bash
dotnet ef migrations add InitialCreate --project OrdersApi/OrdersApi.csproj --startup-project OrdersApi/OrdersApi.csproj
dotnet ef database update --project OrdersApi/OrdersApi.csproj --startup-project OrdersApi/OrdersApi.csproj
dotnet build
dotnet test

You should see Build succeeded, then a test summary reporting 7 passed and 0 failed. Next, add the shared instruction files. Put identical text in CLAUDE.md and .github/copilot-instructions.md so neither tool gets better guidance than the other:

Markdown
# Project instructions

## Project
ASP.NET Core Web API on .NET 10 with controllers. EF Core with SQL Server (SQLite in tests).

## Commands
- Build: `dotnet build`
- Test: `dotnet test`

## Rules
- Never edit tests just to make them pass.
- Never read or print secrets; configuration comes from user secrets.
- Do not run `dotnet ef database` commands.
- Requirement: write endpoints (POST, PUT, DELETE) must require an authenticated
  caller in production. This sample has no authentication configured yet, so do not
  add authentication unless asked.

These files deliberately omit style rules such as "pass the CancellationToken" or "never expose entities." Task 6 checks whether a reviewer notices those problems, and stating the rules would give the answer away. Each task prompt that needs such a rule states it explicitly. Claude Code reads its permission rules from .claude/settings.json, so save the file shown in Control over edits now to make it part of the tagged baseline, and approve dotnet build and dotnet test in Copilot through its own approval prompts. Because both instruction files carry identical text, a tool that also reads the other tool's file sees the same rules. Then commit and tag:

Bash
git add .
git commit -m "Baseline for AI tool evaluation"
git tag eval-baseline

Run and call the API over HTTPS

The sample calls app.UseHttpsRedirection(), which sends plain HTTP requests to HTTPS when the app knows its HTTPS port. Do not remove it to make a command work. Instead, run the HTTPS launch profile and call the HTTPS address:

  1. Trust the ASP.NET Core development certificate once per machine: dotnet dev-certs https --trust. This works directly on Windows and macOS; on Linux, trust varies by distribution, so see Microsoft's HTTPS enforcement documentation.
  2. Start the app with --launch-profile https. The console prints an https address and an http address, each with its own port.
  3. Send requests to the https address. A request to the http address gets a 307 redirect to HTTPS, and the redirect keeps the method and body.

If you run dotnet run without a profile, it uses the first profile in Properties/launchSettings.json, which in the standard template is the HTTP-only profile. With no HTTPS endpoint, the middleware cannot determine an HTTPS port, logs a warning such as "Failed to determine the https port for redirect," and does not redirect. Requests work, but they are unencrypted, so use this only for a quick local check. Check your own launchSettings.json, because templates can change.

Bash
dotnet dev-certs https --trust
Bash
dotnet run --project OrdersApi/OrdersApi.csproj --launch-profile https

# In a second terminal, use the https address printed by the first one:
curl -i -X POST "https://localhost:<https-port>/api/products" \
  -H "Content-Type: application/json" \
  -d '{"name":"Codingvila notebook","price":12.50,"stockQuantity":10}'

Expected result: an HTTP/2 201 or HTTP/1.1 201 Created status, a Location header, and a JSON body containing the new product's id. In Windows PowerShell, plain curl is an alias for a different command and the quoting differs, so use this instead:

PowerShell
Invoke-RestMethod -Method Post -Uri "https://localhost:<https-port>/api/products" `
  -ContentType "application/json" `
  -Body '{"name":"Codingvila notebook","price":12.50,"stockQuantity":10}'

Expected result: PowerShell prints the created product, including its id. This works without extra flags once the development certificate is trusted. If a tool reports an untrusted certificate, trust the certificate rather than disabling validation; curl -k and PowerShell 7's -SkipCertificateCheck skip validation and are acceptable only for a throwaway local test.

The tests avoid the redirect question by giving the test client an https://localhost base address. That keeps them independent of whether ASPNETCORE_HTTPS_PORT happens to be set in your shell. See Microsoft's integration tests documentation for the client options.

Ground rules for a fair comparison

  • Same starting state. Every run starts from a tagged commit on its own new branch, so the source is byte-identical. The next sections define the tags.
  • Same prompt and criteria. Paste the prompt exactly as written. Do not add hints in one run that you withheld in the other.
  • Fresh session per run. Start a new chat or session, with no resumed context. Turn off or note any memory feature (Copilot and Claude Code both have project or user memory options), and record what you left on.
  • Same approval posture. Use each tool's normal ask-before-acting mode, with dotnet build and dotnet test pre-approved in both. Do not use autopilot, bypass, or auto-approve modes in either.
  • Keep the answer keys out of the repository. The reference solution and the seeded-defect list in this article must never be committed to the evaluated repository or pasted into a session.
  • Repeat every scenario at least twice per tool. AI output varies between runs, so one run proves little. Tasks 3a and 3b count as separate scenarios, so a full pass is seven scenarios, two tools, and two runs each: 28 runs.
  • Judge the final git diff, not the tool's explanation of what it did.

Comparing products versus models. Each product picks a default model and wraps it in its own prompts and tools, so a default-settings run compares two complete product experiences, not two language models. If both products let you select the same model family on your plan, add a second pass with the model matched and record the exact model name each tool displays. Treat the first pass as a product comparison and the second as a tooling comparison. Neither isolates model quality, and you should not claim that it does.

What this method cannot tell you. Two runs per scenario reveal obvious gaps, not statistical differences. A small API cannot predict behavior on a large multi-project solution, and models and tools update often, so rerun the evaluation when versions change.

Tracking each run. Record these fields for every run: date, task, tool and version (claude --version, the extension version, or the Visual Studio version), surface (terminal, VS Code, Visual Studio), model as displayed, plan, permission mode, run number, starting tag, build result, test result (passed out of total), wall-clock minutes, manual correction minutes, number of permission prompts, and the usage or credits each tool reports. Save each run's patch outside the repository.

Tag the fixed starting points

Tasks do not all start from the same code, and Tasks 5 and 6 depend on Task 2. To keep both tools on identical footing, create fixed starting points yourself rather than letting each tool build on its own earlier output. From the baseline tag, create these branches and tags, each seeded once and shared by both tools. The Task 2 reference solution and the flawed GetAll method appear later in this guide.

Task 3, compile error: renaming StockQuantity to Stock in Product.cs alone leaves the controller referring to a missing property, which produces real compiler errors.

Bash
git switch -c seed/compile-error eval-baseline
# Edit OrdersApi/Models/Product.cs: rename the property StockQuantity to Stock. Change nothing else.
dotnet build        # expect CS0117 and CS1061 errors in ProductsController.cs
git commit -am "Seed: compile error"
git tag eval-compile-error
git switch main

Task 3, behavior failure: lowering the price minimum to zero makes the zero-price Theory case return 201 instead of 400, so exactly one of the seven cases fails.

Bash
git switch -c seed/zero-price eval-baseline
# Edit OrdersApi/Dtos/ProductDtos.cs: change the Range minimum "0.01" to "0". Change nothing else.
dotnet test         # expect 1 failed (the zero-price case), 6 passed
git commit -am "Seed: zero price allowed"
git tag eval-zero-price
git switch main

Tasks 5 and 6: both start from a commit that contains the reference PUT and DELETE, so every run sees identical endpoint code regardless of how each tool handled Task 2.

Bash
git switch -c seed/reference-crud eval-baseline
# Paste the reference PUT and DELETE methods (shown later) into ProductsController.cs
dotnet build
dotnet test         # expect 7 passed
git commit -am "Seed: reference PUT and DELETE"
git tag eval-reference-crud

# Now add the flawed GetAll method (shown in Task 6)
dotnet build
dotnet test         # expect 7 passed
git commit -am "Seed: flawed GetAll for the review task"
git tag eval-review-seeded
git switch main

The table shows which tag each task starts from.

Starting tag for each evaluation task
TaskStarts fromDepends on another task?
1. Explain a controllereval-baselineNo
2. Add PUT and DELETEeval-baselineNo
3a. Fix a compile erroreval-compile-errorNo
3b. Fix a failing behavioreval-zero-priceNo
4. Refactor mappingeval-baselineNo
5. Add tests for PUT and DELETEeval-reference-crudUses the reference result of Task 2, not each tool's own
6. Review a brancheval-review-seededUses the reference result of Task 2 plus the seeded GetAll

For every run, use the same loop. Tests run before the tool starts (the tag already passes, except the two seeded failures), the tool runs the build and tests itself as the prompt requires, and you run them again afterward yourself. Starting each run from a new branch and a clean git status restores an identical state, so nothing carries over between runs. If you ran the API by hand, use a disposable database and recreate it between runs; the automated tests use in-memory SQLite and need no cleanup.

Bash
START_TAG=eval-baseline                          # change per task, see the table above
mkdir -p ../eval-results                   # once, outside the repository
git switch -c run/task2/claude-code/1 "$START_TAG"
git status                                  # must be clean before the tool starts
# ... start a fresh tool session and paste the prompt ...
git add -A && git commit -m "Run: task2 claude-code 1"
git diff "$START_TAG" > ../eval-results/task2-claude-code-1.patch
dotnet build
dotnet test
git switch main
Evaluation loop for each task and toolA flowchart. Tag fixed baselines, create one branch per run from the right tag, run the same prompt, review the diff, build and tests, then score the run and log minutes. If runs remain, return to creating a branch. Otherwise compare the scorecards.Tag fixedbaselinesBranch from tag,one per runRun the samepromptReview diff,build, testScore the run,log minutesAll runsdone?NoYesComparescorecards
The repeatable evaluation loop. Every run starts from a tagged commit on its own branch, failures are logged when you score, and each scenario is run at least twice per tool before you compare scorecards.

Task 1: Explain an unfamiliar controller

Starts from: eval-baseline. Prompt:

Text
Explain ProductsController. List every dependency it uses, how each is registered, what each endpoint returns for success and failure, and any behavior you cannot confirm from the code. Do not change any files.

Acceptance criteria:

  • Names AppDbContext as the only dependency (injected through the primary constructor) and says it is registered with AddDbContext in Program.cs, using SQL Server except in the Testing environment, where the test factory supplies SQLite.
  • Describes GET api/products/{id:int} as 200 with a ProductResponse or 404, and POST api/products as 201 with a Location header, or 400 from automatic [ApiController] validation.
  • States what it cannot confirm: the connection string, authentication or authorization (none is configured), database constraints beyond the model, and concurrency behavior.
  • Invents no dependency, and git status shows no changed files.

Task 2: Implement a CRUD endpoint with validation

Starts from: eval-baseline. Prompt:

Text
Add PUT and DELETE endpoints to ProductsController. PUT must validate input with the same rules as the create request, return 404 for a missing product, and use the CancellationToken. DELETE must return 204 on success and 404 if missing. Follow the existing style. Build the solution and run the tests when finished.

Acceptance criteria:

  • PUT api/products/{id} returns 200 with a ProductResponse for a valid update, trims the name, and applies the same validation rules as creation.
  • PUT with a valid body and a missing ID returns 404. PUT with an invalid body returns 400 even for a missing ID, because [ApiController] validates the model before the method runs. Do not penalize that ordering.
  • DELETE api/products/{id} returns 204 on success and 404 if missing.
  • The CancellationToken reaches every EF Core call.
  • No authentication is added. Adding [Authorize] without configuring an authentication scheme breaks requests at run time and fails Correctness.
  • The diff is limited to ProductsController.cs (plus tests if the tool adds them), existing routes are unchanged, and the build succeeds with all seven original tests still passing.

Here is one reference solution, so you can compare against it. Do not paste it into either tool's session or commit it anywhere either tool can read. The sample has no concurrency token, so two simultaneous updates overwrite each other (last write wins).

C#
[HttpPut("{id:int}")]
public async Task<ActionResult<ProductResponse>> Update(int id, CreateProductRequest request, CancellationToken ct)
{
    var product = await db.Products.FirstOrDefaultAsync(p => p.Id == id, ct);

    if (product is null)
    {
        return NotFound();
    }

    product.Name = request.Name.Trim();
    product.Price = request.Price;
    product.StockQuantity = request.StockQuantity;

    await db.SaveChangesAsync(ct);

    return new ProductResponse(product.Id, product.Name, product.Price, product.StockQuantity);
}

[HttpDelete("{id:int}")]
public async Task<IActionResult> Delete(int id, CancellationToken ct)
{
    var deleted = await db.Products
        .Where(p => p.Id == id)
        .ExecuteDeleteAsync(ct);

    return deleted == 0 ? NotFound() : NoContent();
}

Two details to notice. Update loads the entity with tracking on purpose, because EF Core must track it to detect the changes. Delete uses ExecuteDeleteAsync, which sends one DELETE statement without loading the row first.

Task 3: Diagnose a deliberate failure

Starts from: eval-compile-error for 3a and eval-zero-price for 3b. Run the two as separate scenarios. Prompt (same for both):

Text
The solution does not build or its tests fail. Run the build and tests, find the root cause, and fix it with the smallest change. Explain what you changed and why. Do not modify tests to make them pass.

Acceptance criteria:

  • 3a: the build succeeds and all seven tests pass. The smallest fix restores the property name StockQuantity in Product.cs. Renaming every usage to Stock also compiles but changes the EF model, so score it lower on Maintainability.
  • 3b: all seven tests pass because the price minimum is restored to "0.01". Loosening the test fails Correctness.
  • git diff -- OrdersApi.Tests shows no changes, and the explanation identifies the root cause rather than a symptom.

Task 4: Refactor while preserving behavior

Starts from: eval-baseline. Prompt:

Text
Extract the repeated Product-to-ProductResponse mapping in ProductsController into a single extension method. Do not change any public behavior or routes. Run all tests before and after and report both results.

Acceptance criteria: the mapping lives in one extension method, routes and response shapes are unchanged, the diff is limited to ProductsController.cs and one new or existing static class, no package is added, and the tool reports test results from both before and after the change (seven passed each time).

Task 5: Extend the tests for new behavior

Starts from: eval-reference-crud. Prompt:

Text
Add xUnit integration tests for the new PUT and DELETE endpoints, reusing OrdersApiFactory. Each test must create its own data and must not depend on other tests. Required tests:
1. Successful update: send a valid PUT, assert 200, then GET the product and assert the changed name, price and stock quantity were persisted.
2. Invalid update: create a product, send a PUT with a price of 0, and assert 400.
3. Missing product update: send a valid PUT to an ID that does not exist and assert 404.
4. Successful delete: create a product, DELETE it, assert 204, then GET it and assert 404.
5. Missing product delete: DELETE an ID that does not exist and assert 404.
Run the tests and report the results. Tell me which behaviors the tests do not cover.

The requirement that each test creates its own data exists because all tests in the class share one database. The specific read-back, zero-price, and delete-then-GET assertions exist so the mutation checks below are reproducible instead of depending on whatever tests a tool happens to generate. The prompt applies equally to both tools.

Acceptance criteria:

  • At least five new tests covering the five required scenarios, reusing OrdersApiFactory instead of inventing a second database setup, and all tests pass.
  • Each test creates its own data and does not depend on an ID or on another test.
  • The successful update test reads the product back and asserts the persisted values. The invalid update test sends a price of zero and asserts 400. The successful delete test asserts 204 and then confirms the product returns 404.
  • Mutation check: temporarily apply each change on a scratch branch, run the tests, and revert. Mutation 1: make Delete always return NoContent(). Mutation 2: remove the SaveChangesAsync call from Update. Mutation 3: lower the price minimum to "0". Because the prompt requires the read-back, the zero-price update and the delete-then-GET, a good suite fails at least one new test for each mutation. If a mutation survives, record which required assertion was missing.
  • The tool's list of uncovered behaviors is honest (for example, concurrency and authorization).

Task 6: Review the code for security and maintainability

Starts from: eval-review-seeded, which contains the reference PUT and DELETE plus the flawed method below. Do not tell either tool about the flaws.

C#
[HttpGet]
public async Task<IActionResult> GetAll()
{
    var products = await db.Products.ToListAsync();
    return Ok(products);
}

Security policy for this evaluation. The baseline ships without authentication because it is a teaching sample, but the shared instruction file states a requirement: write endpoints (POST, PUT, and DELETE) must require an authenticated caller in production. Against that requirement, the unprotected write endpoints count as a seeded defect. Without the requirement, "missing authorization" would be speculation. This task is a review only, so do not ask either tool to add authentication. For design guidance, see Microsoft's ASP.NET Core authorization overview.

Prompt:

Text
Review the entire current ProductsController and the relevant security configuration in Program.cs against the requirements in the project instructions file, as a skeptical senior reviewer. Use the diff against the eval-baseline tag as context, but do not limit your review to changed lines. Cover correctness, security, performance, and maintainability. Rank findings by severity, quote the lines involved, and say how confident you are in each finding. Do not change any files.

The prompt names broad areas only and points the tool at the whole controller, because the missing-authorization defect (S5) also covers the POST endpoint that already exists in the baseline. It does not list specific defect categories, because listing them would reveal the answers.

How to judge. Compare each tool's findings with the answer key below, and count rather than average.

Answer key (read only after both runs are finished)

Seeded defects (five):

  1. S1, entity exposure: GetAll returns Product entities instead of ProductResponse.
  2. S2, unbounded query: no paging or limit on a table that can grow.
  3. S3, unnecessary tracking: a read-only query without AsNoTracking.
  4. S4, ignored cancellation: GetAll takes no CancellationToken.
  5. S5, missing authorization: POST, PUT, and DELETE lack authorization despite the stated requirement.

Not present (a report of these is a false alarm): over-posting (the DTO prevents it), leaked secrets (they live in user secrets), swallowed exceptions (there is no try or catch), and SQL injection (there is no raw SQL).

Legitimate extra findings a tool may report: no concurrency token (last write wins), no rate limiting, and no explicit maximum on list size. Verify each extra finding yourself against the code and count it as a valid additional finding if it is true.

Reference fix for S1 to S4. One way to resolve the four GetAll defects is a paged endpoint that returns DTOs, reads without tracking, and passes the cancellation token. S5 needs an authentication scheme, which the hardened reference implementation provides in full. The code clamps page to 1,000,000 so that (page - 1) * pageSize (at most 999,999 × 100) can never overflow an int.

C#
public record PagedResponse<T>(IReadOnlyList<T> Items, int Page, int PageSize, int TotalCount);

[HttpGet]
public async Task<ActionResult<PagedResponse<ProductResponse>>> GetAll(
    [FromQuery] int page = 1,
    [FromQuery] int pageSize = 20,
    CancellationToken ct = default)
{
    page = Math.Clamp(page, 1, 1_000_000);
    pageSize = Math.Clamp(pageSize, 1, 100);

    var query = db.Products.AsNoTracking().OrderBy(p => p.Id);
    var total = await query.CountAsync(ct);

    var items = await query
        .Skip((page - 1) * pageSize)
        .Take(pageSize)
        .Select(p => new ProductResponse(p.Id, p.Name, p.Price, p.StockQuantity))
        .ToListAsync(ct);

    return new PagedResponse<ProductResponse>(items, page, pageSize, total);
}

Scoring

Score Tasks 1 to 5 per run on the 0 to 2 scale below. Task 1 is read-only, so Verification honesty is not applicable for it: score it on the other four criteria (maximum 8) and do not penalize the tool for not running builds. For Task 1, Correctness also requires that git status shows no changes. Keep measurements separate from scores, and do not combine them into a single number unless you can justify the weights for your team.

Scoring rubric for Tasks 1 to 5 (score each criterion per run)
Criterion012
CorrectnessDoes not build, fails a required test, or breaks a required behaviorBuilds and passes tests, but a stated behavior is wrong or unverifiedBuilds, all tests pass, every behavioral criterion met
CompletenessFewer than half of the acceptance criteria metMost criteria met, some missingEvery acceptance criterion met
Verification honestyClaimed to build or test without running, or misreported results (Task 1: not applicable)Ran build or tests, but not after the final edit, or reported incompletelyRan build and tests after the final edit and reported results accurately
SecurityIntroduced a vulnerability, weakened validation, or exposed a secretNo new risk, but missed an obvious risk within scopeNo new risk, and relevant risks noted or handled
MaintainabilityUnrelated edits, large rewrites, or broke the existing styleMinor unrelated changes or style driftMinimal diff in the existing style

For Task 6, record counts instead of 0 to 2 scores: how many of the five seeded defects each tool found, how many valid extra findings, how many false alarms, whether the severity ranking put S5 and S1 above S3, and whether the quoted lines were accurate. Record these measurements for every task, separately from scores: wall-clock minutes, manual correction minutes (time you spent fixing or redoing the output), number of permission prompts, and the usage or credits each tool reports.


Code quality, security, and control over edits

Understanding existing code

Agents that can search and read your repository tend to do better on questions that span files than tools that only see the open file. Both products offer repository-aware modes, but how much context each uses, and how it is selected, differs by surface, model, and settings. Test this on your largest solution, not a toy project.

Entity Framework Core and SQL Server

Check generated data access code for the classic problems: loading entire tables into memory, missing AsNoTracking on read-only queries, string-built SQL, forgotten indexes, and migrations that drop data. Never let an agent run dotnet ef database update against a database you cannot restore. Point it at a disposable local database instead.

Debugging and tests

The most useful agent behavior is the loop: run the build, read the error, change the code, run again. Verify that the tool really ran the command rather than describing what would happen. Be suspicious of any fix that edits a test to make it pass.

Authentication and authorization

AI output for authentication is where subtle mistakes cost the most: tokens in localStorage, overly long token lifetimes, missing audience or issuer validation, and endpoints that were never protected. Treat generated auth code as a draft that needs expert review, and compare it against Microsoft's ASP.NET Core authentication documentation. For a longer walkthrough of refresh tokens and token rotation, see Codingvila's secure JWT authentication tutorial.

Control over edits

Claude Code is built around permission rules that you set to allow, ask, or deny. Rules are evaluated deny first, then ask, then allow, and the first match wins. Copilot's agent features also involve approvals, and GitHub documents an allowed-tools mechanism for the CLI. Whichever tool you use, read the diff and keep Git as your safety net.

Here is a project-level Claude Code example. It lets the agent build and test without prompting and blocks EF Core database updates and drops. Save it as .claude/settings.json, then run /permissions inside Claude Code to confirm the rules loaded. Each command appears twice, once bare and once with a wildcard, so the rule covers both dotnet test and dotnet test --no-build.

JSON
{
  "permissions": {
    "allow": [
      "Bash(dotnet build)",
      "Bash(dotnet build *)",
      "Bash(dotnet test)",
      "Bash(dotnet test *)"
    ],
    "deny": [
      "Bash(dotnet ef database update)",
      "Bash(dotnet ef database update *)",
      "Bash(dotnet ef database drop)",
      "Bash(dotnet ef database drop *)"
    ]
  }
}

Treat these rules as guardrails, not a security boundary. Anthropic's documentation states that a Bash deny rule covers the form of the command Claude usually writes, but not the same program invoked another way, such as with a full path or inside sh -c. Three further cautions apply. First, dotnet build and dotnet test execute project code (MSBuild targets and your test code), so pre-approving them means trusting the repository. Second, allow rules in a project's settings.json take effect only after you accept the workspace trust dialog. Third, for enforcement that does not depend on command text, use the sandbox. The real protection is a disposable database, user secrets, and an isolated environment.

From the evaluation fixture to production: what to add

The sample API is small so the tools have something clear to work on. It is a fixture for comparing tools, not a template to deploy. Before you ship anything like it, plan for these gaps, and notice whether each tool raises them without being asked:

  • Authentication and authorization on every write endpoint, with a tested policy for each role. The hardened reference below shows the baseline.
  • Paging and filtering on list endpoints, with a maximum page size. The hardened reference below includes a paged endpoint (the Task 6 answer key shows the same pattern, so read that only after you finish the review).
  • Concurrency control, so simultaneous updates do not silently overwrite each other.
  • Consistent error responses. ASP.NET Core can return the standard problem-details format; see Microsoft's documentation on AddProblemDetails.
  • Rate limiting, logging, and health checks, which ASP.NET Core supports through built-in middleware.
  • Migrations in your deployment pipeline rather than dotnet ef database update from a laptop.
  • Tests against the real database engine, for example SQL Server in a container, in addition to the fast SQLite tests used here.

A hardened reference implementation

The evaluation fixture deliberately omits authentication so the review task has something to find. After the evaluation, use the files below as a more complete starting point. They add JWT bearer authentication on the write endpoints, a paged and read-only list endpoint, problem-details error responses, fail-fast configuration checks, and a test authentication scheme so the integration tests still run without an identity provider. Replace the matching files in the sample, run the commands, and expect dotnet test to report 9 passed (the original seven plus two new tests). Like the rest of the sample, these files were checked by reading them against Microsoft's documentation and were not built or run for this article, so treat your own build and test output as the final word.

Assumptions and limits. You have an OpenID Connect or OAuth identity provider that issues JWT access tokens for this API, and you can supply its issuer URL and audience. This keeps the single-role rule ("any authenticated caller may write"); real systems usually need role or policy checks. It does not add the concurrency token, rate limiting, health checks, or the real-SQL-Server test run listed above.

Show the hardened files

Step 1: add the JWT bearer package at the same version as the other packages, and store the two settings in user secrets. The app now refuses to start outside the Testing environment if either setting is missing.

Bash
dotnet add OrdersApi package Microsoft.AspNetCore.Authentication.JwtBearer --version 10.0.13
dotnet user-secrets set "Auth:Authority" "<your-identity-provider-issuer-url>" --project OrdersApi/OrdersApi.csproj
dotnet user-secrets set "Auth:Audience" "<your-api-audience>" --project OrdersApi/OrdersApi.csproj

Step 2: add OrdersApi/Dtos/PagedResponse.cs.

C#
namespace OrdersApi.Dtos;

public record PagedResponse<T>(IReadOnlyList<T> Items, int Page, int PageSize, int TotalCount);

Step 3: replace OrdersApi/Program.cs. Authentication runs before authorization, and the exception handler and status-code pages return the standard problem-details format.

C#
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.EntityFrameworkCore;
using OrdersApi.Data;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();
builder.Services.AddOpenApi();
builder.Services.AddProblemDetails();

builder.Services
    .AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options =>
    {
        options.Authority = builder.Configuration["Auth:Authority"];
        options.Audience = builder.Configuration["Auth:Audience"];
    });
builder.Services.AddAuthorization();

// Integration tests supply their own database and authentication, so skip the real ones there.
if (!builder.Environment.IsEnvironment("Testing"))
{
    var connectionString = builder.Configuration.GetConnectionString("DefaultConnection")
        ?? throw new InvalidOperationException(
            "Connection string 'DefaultConnection' is missing. Set it with dotnet user-secrets.");

    if (string.IsNullOrWhiteSpace(builder.Configuration["Auth:Authority"])
        || string.IsNullOrWhiteSpace(builder.Configuration["Auth:Audience"]))
    {
        throw new InvalidOperationException(
            "Auth:Authority and Auth:Audience must be configured. Set them with dotnet user-secrets.");
    }

    builder.Services.AddDbContext<AppDbContext>(options =>
        options.UseSqlServer(connectionString));
}

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.MapOpenApi();
}
else
{
    app.UseExceptionHandler();
}

app.UseStatusCodePages();
app.UseHttpsRedirection();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();

app.Run();

public partial class Program { }

Step 4: replace OrdersApi/Controllers/ProductsController.cs. Read endpoints stay public, [Authorize] guards every write, every EF Core call receives the cancellation token, the list endpoint never tracks entities or returns them directly, and pageSize is capped at 100.

C#
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using OrdersApi.Data;
using OrdersApi.Dtos;
using OrdersApi.Models;

namespace OrdersApi.Controllers;

[ApiController]
[Route("api/[controller]")]
public class ProductsController(AppDbContext db) : ControllerBase
{
    [HttpGet]
    public async Task<ActionResult<PagedResponse<ProductResponse>>> GetAll(
        [FromQuery] int page = 1,
        [FromQuery] int pageSize = 20,
        CancellationToken ct = default)
    {
        page = Math.Clamp(page, 1, 1_000_000);
        pageSize = Math.Clamp(pageSize, 1, 100);

        var query = db.Products.AsNoTracking().OrderBy(p => p.Id);
        var total = await query.CountAsync(ct);

        var items = await query
            .Skip((page - 1) * pageSize)
            .Take(pageSize)
            .Select(p => new ProductResponse(p.Id, p.Name, p.Price, p.StockQuantity))
            .ToListAsync(ct);

        return new PagedResponse<ProductResponse>(items, page, pageSize, total);
    }

    [HttpGet("{id:int}")]
    public async Task<ActionResult<ProductResponse>> GetById(int id, CancellationToken ct)
    {
        var product = await db.Products
            .AsNoTracking()
            .FirstOrDefaultAsync(p => p.Id == id, ct);

        return product is null ? NotFound() : ToResponse(product);
    }

    [Authorize]
    [HttpPost]
    public async Task<ActionResult<ProductResponse>> Create(CreateProductRequest request, CancellationToken ct)
    {
        var product = new Product
        {
            Name = request.Name.Trim(),
            Price = request.Price,
            StockQuantity = request.StockQuantity
        };

        db.Products.Add(product);
        await db.SaveChangesAsync(ct);

        return CreatedAtAction(nameof(GetById), new { id = product.Id }, ToResponse(product));
    }

    [Authorize]
    [HttpPut("{id:int}")]
    public async Task<ActionResult<ProductResponse>> Update(int id, CreateProductRequest request, CancellationToken ct)
    {
        var product = await db.Products.FirstOrDefaultAsync(p => p.Id == id, ct);

        if (product is null)
        {
            return NotFound();
        }

        product.Name = request.Name.Trim();
        product.Price = request.Price;
        product.StockQuantity = request.StockQuantity;

        await db.SaveChangesAsync(ct);

        return ToResponse(product);
    }

    [Authorize]
    [HttpDelete("{id:int}")]
    public async Task<IActionResult> Delete(int id, CancellationToken ct)
    {
        var deleted = await db.Products
            .Where(p => p.Id == id)
            .ExecuteDeleteAsync(ct);

        return deleted == 0 ? NotFound() : NoContent();
    }

    private static ProductResponse ToResponse(Product p) =>
        new(p.Id, p.Name, p.Price, p.StockQuantity);
}

Step 5: add OrdersApi.Tests/TestAuthHandler.cs. Without it, the write endpoints would answer every test request with 401.

C#
using System.Security.Claims;
using System.Text.Encodings.Web;
using Microsoft.AspNetCore.Authentication;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;

namespace OrdersApi.Tests;

// Test-only scheme: a request is authenticated when it carries the X-Test-User header.
public sealed class TestAuthHandler(
    IOptionsMonitor<AuthenticationSchemeOptions> options,
    ILoggerFactory logger,
    UrlEncoder encoder)
    : AuthenticationHandler<AuthenticationSchemeOptions>(options, logger, encoder)
{
    public const string SchemeName = "Test";
    public const string HeaderName = "X-Test-User";

    protected override Task<AuthenticateResult> HandleAuthenticateAsync()
    {
        if (!Request.Headers.TryGetValue(HeaderName, out var user) || string.IsNullOrWhiteSpace(user))
        {
            return Task.FromResult(AuthenticateResult.NoResult());
        }

        var identity = new ClaimsIdentity([new Claim(ClaimTypes.Name, user.ToString())], SchemeName);
        var ticket = new AuthenticationTicket(new ClaimsPrincipal(identity), SchemeName);
        return Task.FromResult(AuthenticateResult.Success(ticket));
    }
}

Step 6: replace OrdersApi.Tests/OrdersApiFactory.cs. The only changes from the original are the two extra usings and the ConfigureTestServices block that swaps in the test scheme.

C#
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.AspNetCore.TestHost;
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using OrdersApi.Data;

namespace OrdersApi.Tests;

public sealed class OrdersApiFactory : WebApplicationFactory<Program>
{
    // An in-memory SQLite database lives only while this connection stays open.
    private readonly SqliteConnection _connection = new("DataSource=:memory:");

    protected override void ConfigureWebHost(IWebHostBuilder builder)
    {
        builder.UseEnvironment("Testing");

        builder.ConfigureServices(services =>
            services.AddDbContext<AppDbContext>(options => options.UseSqlite(_connection)));

        // Replace JWT bearer with the test scheme so tests need no identity provider.
        builder.ConfigureTestServices(services =>
            services
                .AddAuthentication(TestAuthHandler.SchemeName)
                .AddScheme<AuthenticationSchemeOptions, TestAuthHandler>(TestAuthHandler.SchemeName, _ => { }));
    }

    protected override IHost CreateHost(IHostBuilder builder)
    {
        _connection.Open();

        var host = base.CreateHost(builder);

        using var scope = host.Services.CreateScope();
        scope.ServiceProvider.GetRequiredService<AppDbContext>().Database.EnsureCreated();

        return host;
    }

    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
            _connection.Dispose();
        }

        base.Dispose(disposing);
    }
}

Step 7: update OrdersApi.Tests/ProductsEndpointTests.cs. The original tests keep working because they use the signed-in client; the two new tests cover the 401 path and the paged endpoint.

C#
// Replace the top of ProductsEndpointTests (the usings stay the same) ...
public class ProductsEndpointTests(OrdersApiFactory factory) : IClassFixture<OrdersApiFactory>
{
    private readonly HttpClient _client = CreateClient(factory, signedIn: true);
    private readonly HttpClient _anonymous = CreateClient(factory, signedIn: false);

    private static HttpClient CreateClient(OrdersApiFactory factory, bool signedIn)
    {
        // An https base address keeps UseHttpsRedirection from redirecting test requests.
        var client = factory.CreateClient(
            new WebApplicationFactoryClientOptions { BaseAddress = new Uri("https://localhost") });

        if (signedIn)
        {
            client.DefaultRequestHeaders.Add(TestAuthHandler.HeaderName, "tester");
        }

        return client;
    }

    // ... keep the original four test methods unchanged (they use _client), and add:

    [Fact]
    public async Task Create_WithoutCredentials_ReturnsUnauthorized()
    {
        var response = await _anonymous.PostAsJsonAsync("/api/products",
            new { name = "Codingvila notebook", price = 12.50m, stockQuantity = 10 });

        Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
    }

    [Fact]
    public async Task GetAll_WithoutCredentials_ReturnsPagedResult()
    {
        await _client.PostAsJsonAsync("/api/products",
            new { name = "Paged item", price = 5m, stockQuantity = 1 });

        var response = await _anonymous.GetAsync("/api/products?page=1&pageSize=5");

        Assert.Equal(HttpStatusCode.OK, response.StatusCode);

        var page = await response.Content.ReadFromJsonAsync<PagedResponse<ProductResponse>>();
        Assert.NotNull(page);
        Assert.Equal(5, page.PageSize);
        Assert.InRange(page.Items.Count, 1, 5);
    }
}

For token design, refresh tokens, and role policies, start with Microsoft's authentication and authorization documentation, and test the real token flow against your identity provider outside the in-memory tests.

Concurrency. In EF Core, add a row-version property (for example a byte[] marked with [Timestamp]), accept the version from the client on update, catch DbUpdateConcurrencyException, and return 409 Conflict. SQLite does not generate SQL Server-style row versions, so the in-memory tests in this guide cannot exercise this behavior; test it against SQL Server.


Common mistakes and security precautions

  • Accepting changes without reading the diff. The tool's summary is not the change.
  • Disabling approvals on your main machine. Claude Code has a bypassPermissions mode that skips prompts, and Anthropic advises using it only in isolated containers or VMs. Apply the same caution to any auto-approve mode in Copilot.
  • Putting secrets where the agent can read them. Use user secrets, environment variables, or a secret manager. Do not paste production connection strings into prompts.
  • Trusting untrusted content. Text in issues, web pages, dependencies, or code comments can contain instructions aimed at the AI. This is called prompt injection. Both vendors document mitigations, but none are perfect.
  • Letting the agent weaken tests. Tell it explicitly not to edit tests to make them pass, and review any test change.
  • Running database commands on real data. Use disposable databases.
  • Skipping dependency review. Check that every package name the tool adds actually exists and is the one you intended before you restore it.
  • Judging by one run. Variance is normal. Repeat the task.

GitHub's equivalent of CLAUDE.md is a repository custom instructions file at .github/copilot-instructions.md. For this evaluation both files carry the same text, shown in the baseline section.


Common errors and how to fix them

  • claude: command not found after installing. The install folder is not on your PATH yet. Open a new terminal window and run claude --version again; Anthropic's installation troubleshooting page covers fixing PATH if it still fails.
  • The token '&&' is not a valid statement separator during install. You ran the CMD version of the installer command in PowerShell. Use the PowerShell command shown earlier.
  • A 307 redirect, or a warning that the HTTPS port could not be determined. A 307 means you called the http address while the app knows its HTTPS address. Call the https address instead (see Run and call the API over HTTPS). The warning means the app started with an HTTP-only profile; restart with --launch-profile https.
  • An untrusted certificate error from curl or PowerShell. Run dotnet dev-certs https --trust and restart your terminal. Skipping certificate validation is for throwaway local tests only.
  • CS0122: 'Program' is inaccessible due to its protection level in the test project. Add public partial class Program { } at the bottom of Program.cs, as shown above.
  • The app stops at startup with "Connection string 'DefaultConnection' is missing". Set the user secret with the --project option, and run in the Development environment. If you see this while running tests, the factory's UseEnvironment("Testing") did not take effect, so confirm the factory class is the one your tests use.
  • A SQL Server error saying the certificate chain is not trusted. For a local development database only, add TrustServerCertificate=True to the connection string.
  • dotnet ef is not recognized. Install it with dotnet tool install --global dotnet-ef --version 10.0.13, then open a new terminal.
  • A package version conflict between EF Core packages. Make Microsoft.EntityFrameworkCore.SqlServer, .Design, .Sqlite, Microsoft.AspNetCore.Mvc.Testing, and dotnet-ef all use the same version, as the setup commands do.
  • Tests fail with an error about two database providers. The app registered SQL Server while the test factory registered SQLite. Confirm the factory sets the Testing environment and that Program.cs skips the SQL Server registration in that environment.
  • Author identity unknown when you commit. Set your name and email with git config --global user.name and git config --global user.email, then commit again.
  • dotnet restore fails while Claude Code's sandbox is on. The sandbox blocks network access to hosts that are not in its allowed-domains list. Add your NuGet feed host (for example api.nuget.org, plus any other host named in the restore error) under sandbox.network.allowedDomains in your Claude Code settings, or run the restore yourself outside the sandbox. Sandboxing is not available on native Windows, so this applies to macOS, Linux, and WSL 2.

Using both tools together

Many C# developers will not pick one. A workable split is Copilot for inline completions and chat inside Visual Studio, plus Claude Code in a terminal for larger multi-file tasks. A few habits keep that setup safe:

  • Share one set of project rules. Keep identical text in CLAUDE.md and .github/copilot-instructions.md, as the evaluation does, so the two tools do not drift apart.
  • One branch per agent task. Let only one agent edit a working tree at a time, review the diff, and keep Git as the undo button.
  • Know what you pay for twice. GitHub lists Claude models in Copilot, so a Copilot plan and a Claude plan can overlap in model access while billing separately. Check each tool's usage meter during your trial week.
  • Check data terms for both. Each product keeps its own retention and training rules, as described in the privacy section.

How to choose and evaluate

  1. List your real constraints first: IDE, operating system, budget, employer policy, and code sensitivity.
  2. Pick one of your own medium-sized tickets as well as the sample project, because a small API may be too easy to separate the tools.
  3. Run the scenarios from their tagged starting points, twice per tool.
  4. Score with the rubric and record the minutes of manual correction and the reported usage.
  5. Check the bill or usage meter after a normal week of work.
  6. Decide, and revisit in a few months. Models, plans, and IDE integrations are changing quickly.

If both tools perform similarly on your code, choose the one that fits your editor, your policies, and your budget. Workflow friction matters more than small quality differences you cannot reliably measure.


Frequently asked questions

Is Claude Code better than GitHub Copilot for C#?

Neither is better in every situation. Copilot has the more native route inside Visual Studio and offers inline completions. Claude Code is strongest as a terminal-driven agent with explicit permission rules. Test both on your own project.

Which is better for .NET developers who use Visual Studio?

Copilot, if you want AI inside the IDE itself, because it has native Visual Studio 2026 support. You can still run Claude Code in a terminal next to Visual Studio, but Anthropic documents no first-party Visual Studio integration.

Does Claude Code work with Visual Studio?

Not through an official Anthropic integration that its documentation describes. Anthropic documents a VS Code extension and a JetBrains plugin, and you can run the command-line tool alongside Visual Studio. Community extensions for Visual Studio exist, so treat them like any third-party code and review them first.

Can GitHub Copilot use Claude models?

GitHub's documentation lists models from several providers and includes a page on Anthropic Claude in Copilot. Availability depends on your plan and organization settings, so check the supported models page.

Can I use Claude Code and GitHub Copilot together?

Yes. Many developers keep Copilot's inline completions in the IDE and run a terminal agent for larger multi-file tasks. Each tool bills and handles data under its own plan, so check both policies.

Which tool is cheaper?

It depends on how you use it. Copilot Pro lists at $10 per month with 1,500 monthly AI credits, and completions are not billed in credits. Claude Code needs a paid Claude plan (Pro is $20 per month, or $17 per month on annual billing) or API billing. Because the two use different meters, measure your own usage during a trial week rather than comparing list prices.

Is Claude Code included in the Team plan?

Yes. Anthropic's help documentation states that Claude Code is included with every Team plan seat, and Premium seats provide more usage. Standard seats list at $20 per seat per month on annual billing ($25 monthly) and Premium seats at $100 ($125 monthly).

How much does Claude Code cost on an Enterprise plan?

Current Enterprise plans use a single seat listed at $20 per user per month, billed annually, with Claude Code included. The seat fee includes no usage, so every token is billed separately at API rates. Administrators can set organization-level and per-user spend limits, self-serve usage is bought upfront as credits, sales-assisted usage is billed monthly in arrears, and self-serve Enterprise has a 20-seat minimum. Older Enterprise contracts with Chat or Standard/Premium seats have different terms until renewal. See Anthropic's Enterprise billing article and confirm current terms before you buy.

Is it safe to use AI coding tools on private code?

It depends on the plan, the vendor's data terms, and your employer's rules. Read the retention and training policies for your exact plan, avoid placing secrets in files or prompts, and get approval for proprietary code.

Should beginners use these tools?

Yes, carefully. Ask the tool to explain code, then write and run it yourself. If you let AI write everything, you may ship code you cannot debug.

Should I upgrade from .NET 8 or 9 before testing?

You can test on any supported version, but .NET 8 and .NET 9 both reach end of support on November 10, 2026. Use .NET 10 for new work so your sample matches what you will maintain.

Is Claude Code just GitHub Copilot with a Claude model?

No. GitHub lists Claude models inside Copilot, but Claude Code is Anthropic's own agent with its own instruction files, permission rules, and billing. Using a Claude model in Copilot gives you that model inside GitHub's interface and metering, not Claude Code's workflow.

What changed in GitHub Copilot billing in 2026?

Since June 1, 2026, Copilot features that call AI models are metered in GitHub AI credits, where one credit equals $0.01. Each plan includes a monthly credit allowance, and inline suggestions and next edit suggestions are not billed in credits on paid plans. See the pricing section for the allowances.


Conclusion

For C# developers, the choice between Claude Code and GitHub Copilot is mostly about workflow, editor, policy, and cost, not a single winner. Copilot is the natural fit for Visual Studio and GitHub-centered teams, and Claude Code is an appealing option for terminal-based, multi-file agent work. Because GitHub also offers Claude models, many developers will end up using both.

Your next step: create the sample API, confirm that all 7 baseline tests pass, tag the starting points, run the scenarios on separate branches, and keep the scorecard. A structured test on your own code will tell you more than any comparison article, including this one.


References

Plans, prices, and previews change often; confirm them on the vendors' pages before you decide. Sources were last reviewed on October 25, 2026.

Editorial note: this article reports no head-to-head test results. It compares documented features and gives you a method to measure the tools yourself.

Codingvila provides articles and blogs on web and software development for beginners as well as free Academic projects for final year students in Asp.Net, MVC, C#, Vb.Net, SQL Server, Angular Js, Android, PHP, Java, Python, Desktop Software Application and etc.

If you have any questions, contact us on info.codingvila@gmail.com