What is HTTP Request Methods?
HTTP Request Methods (also called HTTP Verbs) are standardized actions that define the desired operation to be performed on a given resource. They form the foundation of RESTful API design and client-server communication. The core purpose is to establish a uniform contract between clients and servers, solving the problem of ambiguous resource operations by providing semantic meaning to each request type.
Common aliases: HTTP Verbs, REST Methods, CRUD Operations
How it works in C#
GET
Explanation: Retrieves data from a specified resource. GET requests should only retrieve data and have no side effects on the server. They are cacheable and should not modify any resources.
public async Task<string> GetUserAsync(int userId)
{
using var httpClient = new HttpClient();
// GET requests typically have parameters in the query string
var response = await httpClient.GetAsync($"https://api.example.com/users/{userId}");
if (response.IsSuccessStatusCode)
{
return await response.Content.ReadAsStringAsync();
}
throw new HttpRequestException($"GET request failed: {response.StatusCode}");
}
POST
Explanation: Submits data to be processed to a specified resource. POST requests are non-idempotent and typically used for creating new resources. The data is enclosed in the request body.
public async Task<string> CreateUserAsync(User user)
{
using var httpClient = new HttpClient();
// Serialize object to JSON for the request body
var jsonContent = JsonSerializer.Serialize(user);
var httpContent = new StringContent(jsonContent, Encoding.UTF8, "application/json");
// POST sends data in the body
var response = await httpClient.PostAsync("https://api.example.com/users", httpContent);
if (response.IsSuccessStatusCode)
{
return await response.Content.ReadAsStringAsync();
}
throw new HttpRequestException($"POST request failed: {response.StatusCode}");
}
PUT
Explanation: Replaces all current representations of the target resource with the request payload. PUT is idempotent and used for updating existing resources or creating if they don’t exist.
public async Task<bool> UpdateUserAsync(int userId, User user)
{
using var httpClient = new HttpClient();
var jsonContent = JsonSerializer.Serialize(user);
var httpContent = new StringContent(jsonContent, Encoding.UTF8, "application/json");
// PUT replaces the entire resource at the specified URI
var response = await httpClient.PutAsync($"https://api.example.com/users/{userId}", httpContent);
return response.IsSuccessStatusCode;
}
PUT is idempotent - multiple identical requests have the same effect as a single request.
DELETE
Explanation: Removes the specified resource. DELETE operations are idempotent - multiple identical requests should have the same effect as a single request.
public async Task<bool> DeleteUserAsync(int userId)
{
using var httpClient = new HttpClient();
// DELETE removes the resource at the specified URI
var response = await httpClient.DeleteAsync($"https://api.example.com/users/{userId}");
return response.IsSuccessStatusCode;
}
PATCH
Explanation: Applies partial modifications to a resource. Unlike PUT which replaces the entire resource, PATCH only updates the fields provided in the request.
public async Task<bool> PatchUserEmailAsync(int userId, string newEmail)
{
using var httpClient = new HttpClient();
// PATCH uses JSON Patch format or custom partial update DTO
var patchData = new { email = newEmail };
var jsonContent = JsonSerializer.Serialize(patchData);
var httpContent = new StringContent(jsonContent, Encoding.UTF8, "application/json");
// Using PatchAsync extension method (not built into HttpClient)
var response = await httpClient.PatchAsync($"https://api.example.com/users/{userId}", httpContent);
return response.IsSuccessStatusCode;
}
// Extension method for PATCH since it's not natively supported
public static Task<HttpResponseMessage> PatchAsync(this HttpClient client, string requestUri, HttpContent content)
{
return client.SendAsync(new HttpRequestMessage(new HttpMethod("PATCH"), requestUri)
{
Content = content
});
}
HEAD
Explanation: Identical to GET but transfers only the status line and header section. Useful for checking resource existence, metadata, or caching information without transferring the entire body.
public async Task<bool> CheckUserExistsAsync(int userId)
{
using var httpClient = new HttpClient();
// HEAD request - same as GET but returns headers only
var response = await httpClient.SendAsync(new HttpRequestMessage(HttpMethod.Head,
$"https://api.example.com/users/{userId}"));
return response.IsSuccessStatusCode;
}
OPTIONS
Explanation: Describes the communication options for the target resource. Used to determine supported methods, CORS policies, and other capabilities.
public async Task<IEnumerable<string>> GetSupportedMethodsAsync()
{
using var httpClient = new HttpClient();
var response = await httpClient.SendAsync(new HttpRequestMessage(HttpMethod.Options,
"https://api.example.com/users"));
if (response.Content.Headers.Allow != null)
{
return response.Content.Headers.Allow;
}
// Fallback to checking Allow header
if (response.Headers.TryGetValues("Allow", out var allowedMethods))
{
return allowedMethods;
}
return Enumerable.Empty<string>();
}
OPTIONS is commonly used in CORS preflight requests to verify what operations are allowed.
Idempotency Principle
Explanation: An operation is idempotent if performing it multiple times produces the same result as performing it once. GET, PUT, DELETE, and HEAD are idempotent, while POST and PATCH are typically not.
public class IdempotencyService
{
private readonly HttpClient _httpClient;
public async Task ProcessIdempotentOperation()
{
// Idempotent operations can be safely retried
var user = new User { Id = 1, Name = "John" };
// PUT is idempotent - multiple calls yield same result
await _httpClient.PutAsync($"/users/1", Serialize(user));
await _httpClient.PutAsync($"/users/1", Serialize(user)); // Safe to retry
// DELETE is idempotent
await _httpClient.DeleteAsync("/users/1");
await _httpClient.DeleteAsync("/users/1"); // Still returns 404 - same result
}
private HttpContent Serialize(object obj) =>
new StringContent(JsonSerializer.Serialize(obj), Encoding.UTF8, "application/json");
}
Why is HTTP Request Methods Important?
RESTful Design Principle Compliance - Enables clean, predictable API design following Richardson Maturity Model Level 2, making APIs intuitive and self-documenting.
Idempotency and Safety - Provides built-in reliability through idempotent operations (PUT, DELETE) allowing safe retries without side effects, crucial for distributed systems.
Separation of Concerns (SOLID) - Each method has a single responsibility (SRP principle), clearly separating data retrieval (GET) from modification (POST/PUT/DELETE) operations.
Advanced Nuances
Conditional Requests and ETag Handling
public async Task<string> ConditionalGetAsync(string resourceUrl, string etag)
{
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, resourceUrl);
// Include ETag for conditional GET - server returns 304 if unchanged
if (!string.IsNullOrEmpty(etag))
{
request.Headers.IfNoneMatch.Add(new System.Net.Http.Headers.EntityTagHeaderValue(etag));
}
var response = await client.SendAsync(request);
if (response.StatusCode == HttpStatusCode.NotModified)
{
return null; // Use cached version
}
return await response.Content.ReadAsStringAsync();
}
Use conditional requests with ETags to reduce bandwidth and improve performance for frequently accessed resources.
HTTP Method Override for Legacy Systems
public class MethodOverrideHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{
// Support for X-HTTP-Method-Override header for clients that don't support all methods
if (request.Method == HttpMethod.Post &&
request.Headers.Contains("X-HTTP-Method-Override"))
{
var method = request.Headers.GetValues("X-HTTP-Method-Override").FirstOrDefault();
request.Method = new HttpMethod(method?.ToUpper() ?? "POST");
}
return await base.SendAsync(request, cancellationToken);
}
}
Bulk Operations with POST and Custom Semantics
public async Task<bool> BatchUpdateAsync(IEnumerable<User> users)
{
// While PUT is for single resource updates, batch operations often use POST
// with custom semantic meaning beyond standard CRUD
var batchOperation = new
{
operation = "batch-update",
users = users
};
var content = new StringContent(JsonSerializer.Serialize(batchOperation),
Encoding.UTF8, "application/json");
var response = await _httpClient.PostAsync("/users/batch", content);
return response.IsSuccessStatusCode;
}
Be cautious when using POST for operations other than resource creation - ensure your API documentation clearly explains custom semantics.