
Testing ASP.NET Web API with HTTP REPL and HTTP Files
Author - Abdul Rahman (Bhai)
Web API
29 Articles
Table of Contents
What we gonna do?
Before deploying an API to production, you need to confirm it behaves correctly under a wide range of inputs — valid requests, missing fields, malformed bodies, and authenticated versus unauthenticated calls. Manual API testing is your first line of defence. In this article, we'll explore the full toolkit: HTTP REPL, .http files in Visual Studio, and the Endpoints Explorer — all demonstrated against the ILoveDotNet Articles API.
Why we gonna do?
A deployed API accepts any input a client chooses to send — invalid data, unexpected headers, or requests without authentication. Before your API reaches a consumer you need to verify:
- Does it return the right HTTP status codes?
- Does it expose implementation details in error responses?
- Do authorization rules block the right users?
- Does it return the correct data?
Several tools exist for this. Here is a quick overview:
┌────────────────────┬───────────────────────────────────────────────────────────┐
│ Tool │ Best for │
├────────────────────┼───────────────────────────────────────────────────────────┤
│ Scalar UI │ Quick interactive testing, especially with JWT auth │
│ Postman / Insomnia │ Collections, scripted test suites, CI integration │
│ Fiddler / SOAP UI │ Low-level traffic inspection, REST and SOAP │
│ HTTP REPL │ CLI navigation of OpenAPI-described APIs │
│ .http files │ Version-controlled request files inside Visual Studio │
│ Endpoints Explorer │ Generating .http snippets from existing controller routes │
└────────────────────┴───────────────────────────────────────────────────────────┘
HTTP REPL and .http files are first-party Microsoft tools that live inside the developer's normal workflow — no extra applications to install or switch to.
How we gonna do?
Part 1: HTTP REPL — A CLI for Your API
HTTP REPL (HTTP Read-Eval-Print Loop) is a command-line tool
that lets you navigate an API like a filesystem — you cd into endpoints the
same way you navigate directories. It parses your OpenAPI spec automatically, so it
knows which routes and parameters exist.
Step 1: Install HTTP REPL
dotnet tool install --global Microsoft.dotnet-httprepl
Step 2: Set OpenAPI Version to 3.0 for HTTP REPL Compatibility
At the time of writing, HTTP REPL cannot parse OpenAPI 3.1 (the default generated by Microsoft.AspNetCore.OpenApi in .NET 10). Force the spec to version 3.0 in your Program.cs:
using Microsoft.AspNetCore.OpenApi;
using Microsoft.OpenApi;
builder.Services.AddOpenApi("v1", options =>
{
// Downgrade to 3.0 so the HTTP REPL can parse the spec
options.OpenApiVersion = OpenApiSpecVersion.OpenApi3_0;
});
builder.Services.AddOpenApi("v2", options =>
{
options.OpenApiVersion = OpenApiSpecVersion.OpenApi3_0;
});
Step 3: Navigate and Query with HTTP REPL
Start the REPL pointing at your running API and the location of the OpenAPI spec:
httprepl https://localhost:7001 --openapi https://localhost:7001/v1.json
Core navigation commands:
# List available endpoints at the current path
ls
# Navigate into a path segment (like cd in a terminal)
cd api
cd v1
cd articles
# Send a GET request to the current path
get
# Send a GET request requesting XML output
get -h "Accept:application/xml"
# Navigate to a specific resource
cd 1
get
When you navigate to the articles path and run ls, the REPL reads the OpenAPI spec to show you which HTTP verbs are available:
https://localhost:7001/api/v1/articles> ls
GET
POST
https://localhost:7001/api/v1/articles> get
HTTP/1.1 200 OK
Content-Type: application/json
[
{ "id": 1, "title": "Logging in ASP.NET Web API", "author": "Abdul Rahman", "channel": "WebAPI" },
{ "id": 2, "title": "JWT Auth in ASP.NET Web API", "author": "Abdul Rahman", "channel": "WebAPI" }
]
Tip: keep route parameter names consistent across controllers.
HTTP REPL builds its navigation map from the OpenAPI spec. If two controllers handling
the same path segment use different parameter names — for example id in
one endpoint and articleId in another — the REPL shows both as possible
values when you run ls, which breaks navigation. The fix is
to align parameter names across all handlers that share a route segment.
Step 4: POST with an Editor
For POST requests you need to provide a body. Configure a default editor first:
# Set your preferred editor (macOS example)
pref set editor.command.default "open -a 'TextEdit'"
# On Windows:
pref set editor.command.default "notepad"
Then run post. Your editor opens with a sample JSON body derived from the OpenAPI spec. Edit it, save, and close — the REPL sends the request:
post -h "Content-Type:application/json"
# Editor opens with sample body → edit → save → close
HTTP/1.1 201 Created
Location: /api/v1/articles/5
Step 5: Authenticated Requests with HTTP REPL
Disable auth temporarily to explore the API, then re-enable it. To send authenticated requests, first obtain a token from the login endpoint and set it as a global header:
# Navigate to the login endpoint (not in the OpenAPI spec — REPL can reach it anyway)
cd /api/auth/login
post -h "Content-Type:application/json"
# Editor opens → enter: { "userName": "author", "password": "Pa$$w0rd" }
# Copy the returned token
# Set the token as a global header for all subsequent requests
set header Authorization "Bearer eyJhbGciOiJI..."
# Now protected endpoints return 200
cd /api/v1/articles
get
# HTTP/1.1 200 OK
The set header command persists for the life of the REPL session — no need to pass the token on every individual command.
Part 2: .http Files — Test Requests in Your IDE
.http files let you define HTTP requests in a plain-text file that Visual Studio (and VS Code with the REST Client extension) can execute directly. They are version-controlled alongside your code, making them ideal for sharing test scenarios with the team.
Basic .http File Structure
@baseUrl = https://localhost:7001
@token =
### Login and get a token
POST {{baseUrl}}/api/auth/login
Content-Type: application/json
{
"userName": "author",
"password": "Pa$$w0rd"
}
###
### Get all articles (authenticated)
GET {{baseUrl}}/api/v1/articles
Authorization: Bearer {{token}}
###
### Get a single article
GET {{baseUrl}}/api/v1/articles/1
Authorization: Bearer {{token}}
Named Requests and Response Chaining
One of the most useful .http file features is the ability to reference a previous response in a subsequent request. Name the login request with # @name, then access its JSON response body using JSON path notation:
@baseUrl = https://localhost:7001
### Login
# @name tokenRequest
POST {{baseUrl}}/api/auth/login
Content-Type: application/json
{
"userName": "author",
"password": "Pa$$w0rd"
}
###
### Get all articles — token extracted automatically from the login response
GET {{baseUrl}}/api/v1/articles
Authorization: Bearer {{tokenRequest.response.body.$.token}}
The $ represents the root of the JSON response body (JSON Path notation).
$.token drills into the token property of the login
response. You can also update the shared @token variable at the top:
@baseUrl = https://localhost:7001
@token = {{tokenRequest.response.body.$.token}}
###
# @name tokenRequest
POST {{baseUrl}}/api/auth/login
Content-Type: application/json
{ "userName": "author", "password": "Pa$$w0rd" }
###
### All subsequent requests use the refreshed token automatically
GET {{baseUrl}}/api/v1/articles
Authorization: Bearer {{token}}
###
GET {{baseUrl}}/api/v2/articles
Authorization: Bearer {{token}}
Environment Files — Switching Between Dev and Production
Create an http-client.env.json file in the project root (or any parent folder) to define variable values per environment. Visual Studio displays a drop-down in the .http file editor to switch between them:
{
"shared": {
"contentType": "application/json"
},
"dev": {
"baseUrl": "https://localhost:7001"
},
"staging": {
"baseUrl": "https://api-staging.ilovedotnet.org"
},
"prd": {
"baseUrl": "https://api.ilovedotnet.org"
}
}
Variables defined in the shared key are available in all
environments. Per-environment variables override shared ones when there is a conflict.
With the dev environment selected, {{baseUrl}} resolves to
https://localhost:7001 automatically.
Variable precedence: If you also declare @baseUrl
directly inside the .http file, that declaration
overrides the environment file. To let the environment file control the
value, remove or comment out the in-file @baseUrl line — only then
will switching environments in the drop-down take effect.
Part 3: Endpoints Explorer in Visual Studio
Endpoints Explorer is a Visual Studio tool window (View → Other Windows → Endpoints Explorer) that discovers all routes in your project and lists them with their HTTP method, route template, and controller/action. It is useful for two things:
- Navigating large APIs. Click any endpoint to jump straight to the handler code.
- Generating .http snippets. Right-click → Generate Request adds the
request to your
.httpfile as a starting point — saving you from typing route templates by hand.
Keep in mind that Endpoints Explorer is not perfect: it may generate requests for internal methods or miss constraints. Treat its output as a starting point, not a complete test suite. For large codebases, GitHub Copilot can generate .http file content from existing endpoints even more accurately.
Summary
- Use Scalar UI for a polished interactive playground; use Postman/Insomnia for full test collections; use HTTP REPL when you prefer a CLI experience integrated with OpenAPI.
- If HTTP REPL fails to parse your spec, set options.OpenApiVersion = OpenApiSpecVersion.OpenApi3_0 — HTTP REPL does not support OpenAPI 3.1 yet.
- In HTTP REPL, navigate endpoints like a filesystem with ls and cd; use set header to set a global Bearer token once instead of per-request.
- .http files live in source control alongside the code and
support named requests and
response chaining via JSON path notation
(
requestName.response.body.$.propertyName). - Use http-client.env.json to define environment-specific base URLs; the shared key contains variables available in all environments.
- Endpoints Explorer in Visual Studio is a handy starting point for generating .http request stubs, though it is not always 100% accurate.