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.
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.
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.mdfile 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 buildanddotnet testwhile 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 restorecan 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
| Area | Claude Code | GitHub Copilot |
|---|---|---|
| Primary workflow | Agent you direct in a terminal, desktop app, or editor extension | Features across IDE, CLI, GitHub.com, and cloud agent |
| Inline completions while typing | No inline-completion mode in the documentation reviewed | Yes; unlimited on paid plans and Copilot Student, and not billed in AI credits (Copilot Free: up to 2,000 per month) |
| Visual Studio 2026 | No official integration documented; CLI can run alongside; community extensions exist | Native Copilot Chat and agent features |
| VS Code | Official extension | Native |
| JetBrains IDEs (including Rider) | Documented plugin for several JetBrains IDEs; Rider is not named in the docs, so test it | Supported |
| Terminal agent | Core product | Copilot CLI (GitHub's plans page says all plans include it) |
| MCP servers | Supported | Supported |
| Project instructions | CLAUDE.md | .github/copilot-instructions.md, personal and organization instructions, prompt files |
| Model choice | Anthropic models (or Bedrock, Google Cloud, Foundry) | Multiple providers, depending on plan |
| Usage model | Included in paid Claude plans with shared session and weekly limits, or pay-as-you-go API billing | Plan price plus monthly AI credits; usage-based since June 1, 2026 |
| GitHub PR and issue integration | Possible through tools and integrations | Built in (code review, cloud agent) |
| Free tier | None for Claude Code | Copilot 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.
| Plan | Price per month | Base credits | Flex allotment | Total monthly credits | Code completions |
|---|---|---|---|---|---|
| Free | $0 | A limited allowance; GitHub does not publish a number. Auto model selection only. | Up to 2,000 | ||
| Student | Free for verified students | An allowance; no number published. Auto model selection only. | Unlimited | ||
| Pro | $10 | 1,000 | 500 | 1,500 | Unlimited |
| Pro+ | $39 | 3,900 | 3,100 | 7,000 | Unlimited |
| Max | $100 | 10,000 | 10,000 | 20,000 | Unlimited |
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
| Plan | Price per granted seat per month | Included AI credits per user per month |
|---|---|---|
| Business | $19 | 1,900 |
| Enterprise | $39 | 3,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.
| Plan | Price | Notes |
|---|---|---|
| Free | $0 | Does not include Claude Code |
| Pro | $20 per month billed monthly, or $17 per month billed annually | Includes Claude Code |
| Max 5x | $100 per month | About 5x the usage of Pro; Max is sold monthly |
| Max 20x | $200 per month | About 20x the usage of Pro |
| Team, Standard seat (teams of 2 to 150) | $20 per seat per month billed annually, or $25 billed monthly | Claude 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 monthly | Includes 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 rates | The 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 rates | Billed 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
/feedbackare 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.nameandgit 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 -pforNew-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:
mkdir codingvila-orders-eval
cd codingvila-orders-eval
Then save the following as global.json in that folder:
{
"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:
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.
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.
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
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
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
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)
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.
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
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.
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.
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.
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.
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:
# 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:
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:
- 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. - Start the app with
--launch-profile https. The console prints anhttpsaddress and anhttpaddress, each with its own port. - Send requests to the
httpsaddress. A request to thehttpaddress gets a307redirect 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.
dotnet dev-certs https --trust
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:
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 buildanddotnet testpre-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.
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.
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.
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.
| Task | Starts from | Depends on another task? |
|---|---|---|
| 1. Explain a controller | eval-baseline | No |
| 2. Add PUT and DELETE | eval-baseline | No |
| 3a. Fix a compile error | eval-compile-error | No |
| 3b. Fix a failing behavior | eval-zero-price | No |
| 4. Refactor mapping | eval-baseline | No |
| 5. Add tests for PUT and DELETE | eval-reference-crud | Uses the reference result of Task 2, not each tool's own |
| 6. Review a branch | eval-review-seeded | Uses 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.
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
Task 1: Explain an unfamiliar controller
Starts from: eval-baseline. Prompt:
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
AppDbContextas the only dependency (injected through the primary constructor) and says it is registered withAddDbContextinProgram.cs, using SQL Server except in theTestingenvironment, where the test factory supplies SQLite. - Describes
GET api/products/{id:int}as200with aProductResponseor404, andPOST api/productsas201with aLocationheader, or400from 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 statusshows no changed files.
Task 2: Implement a CRUD endpoint with validation
Starts from: eval-baseline. Prompt:
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}returns200with aProductResponsefor a valid update, trims the name, and applies the same validation rules as creation.PUTwith a valid body and a missing ID returns404.PUTwith an invalid body returns400even for a missing ID, because[ApiController]validates the model before the method runs. Do not penalize that ordering.DELETE api/products/{id}returns204on success and404if missing.- The
CancellationTokenreaches 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).
[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):
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
StockQuantityinProduct.cs. Renaming every usage toStockalso 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.Testsshows no changes, and the explanation identifies the root cause rather than a symptom.
Task 4: Refactor while preserving behavior
Starts from: eval-baseline. Prompt:
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:
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
OrdersApiFactoryinstead 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 asserts204and then confirms the product returns404. - Mutation check: temporarily apply each change on a scratch branch, run the tests, and revert. Mutation 1: make
Deletealways returnNoContent(). Mutation 2: remove theSaveChangesAsynccall fromUpdate. 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.
[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:
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):
- S1, entity exposure:
GetAllreturnsProductentities instead ofProductResponse. - S2, unbounded query: no paging or limit on a table that can grow.
- S3, unnecessary tracking: a read-only query without
AsNoTracking. - S4, ignored cancellation:
GetAlltakes noCancellationToken. - 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.
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.
| Criterion | 0 | 1 | 2 |
|---|---|---|---|
| Correctness | Does not build, fails a required test, or breaks a required behavior | Builds and passes tests, but a stated behavior is wrong or unverified | Builds, all tests pass, every behavioral criterion met |
| Completeness | Fewer than half of the acceptance criteria met | Most criteria met, some missing | Every acceptance criterion met |
| Verification honesty | Claimed 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 incompletely | Ran build and tests after the final edit and reported results accurately |
| Security | Introduced a vulnerability, weakened validation, or exposed a secret | No new risk, but missed an obvious risk within scope | No new risk, and relevant risks noted or handled |
| Maintainability | Unrelated edits, large rewrites, or broke the existing style | Minor unrelated changes or style drift | Minimal 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.
{
"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 updatefrom 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.
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.
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.
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.
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.
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.
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.
// 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
bypassPermissionsmode 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 foundafter installing. The install folder is not on your PATH yet. Open a new terminal window and runclaude --versionagain; Anthropic's installation troubleshooting page covers fixing PATH if it still fails.The token '&&' is not a valid statement separatorduring install. You ran the CMD version of the installer command in PowerShell. Use the PowerShell command shown earlier.- A
307redirect, or a warning that the HTTPS port could not be determined. A307means you called thehttpaddress while the app knows its HTTPS address. Call thehttpsaddress 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
curlor PowerShell. Rundotnet dev-certs https --trustand restart your terminal. Skipping certificate validation is for throwaway local tests only. CS0122: 'Program' is inaccessible due to its protection levelin the test project. Addpublic partial class Program { }at the bottom ofProgram.cs, as shown above.- The app stops at startup with "Connection string 'DefaultConnection' is missing". Set the user secret with the
--projectoption, and run in theDevelopmentenvironment. If you see this while running tests, the factory'sUseEnvironment("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=Trueto the connection string. dotnet efis not recognized. Install it withdotnet 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, anddotnet-efall 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
Testingenvironment and thatProgram.csskips the SQL Server registration in that environment. Author identity unknownwhen you commit. Set your name and email withgit config --global user.nameandgit config --global user.email, then commit again.dotnet restorefails 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 exampleapi.nuget.org, plus any other host named in the restore error) undersandbox.network.allowedDomainsin 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.mdand.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
- List your real constraints first: IDE, operating system, budget, employer policy, and code sensitivity.
- 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.
- Run the scenarios from their tagged starting points, twice per tool.
- Score with the rubric and record the minutes of manual correction and the reported usage.
- Check the bill or usage meter after a normal week of work.
- 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.
- Claude Code: Advanced setup (system requirements, installation, accounts)
- Claude Code: Data usage
- Claude Code: Permissions
- Claude Code: Sandboxing
- Claude Code: Visual Studio Code
- Claude Code: JetBrains IDEs
- Anthropic: Claude plans and pricing
- Claude Help Center: Use Claude Code with your Team or Enterprise plan
- Claude Help Center: What is the Enterprise plan?
- Claude Help Center: How am I billed for my Enterprise plan?
- GitHub Docs: Plans for GitHub Copilot
- GitHub Docs: Individual Copilot plans and AI credits
- GitHub Docs: Usage-based billing for organizations and enterprises
- GitHub Docs: Supported AI models in Copilot
- GitHub Docs: Copilot CLI quickstart
- GitHub Docs: Hosting of models for GitHub Copilot
- GitHub Changelog: Pausing new self-serve signups for Copilot Business (April 22, 2026)
- Microsoft: Download .NET 10.0
- .NET Blog: .NET 8 and .NET 9 end of support
- Microsoft: .NET support policy
- Microsoft Learn: EF Core migrations
- Microsoft Learn: Enforce HTTPS in ASP.NET Core
- Microsoft Learn: Integration tests in ASP.NET Core
- Microsoft Learn: Choosing a testing strategy for EF Core
- Microsoft Learn: ASP.NET Core authorization overview
- Microsoft Learn: ASP.NET Core authentication
- Microsoft Learn: RangeAttribute class
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.
