[C#] Detailed Control over HTTP Methods and Headers using SendAsync

目次

Overview

While HttpClient provides convenient shorthand methods like GetAsync and PostAsync, the SendAsync method is the preferred choice for granular control. By utilizing the HttpRequestMessage class, you can set individual headers per request, utilize non-standard methods (such as HEAD, OPTIONS, or PATCH), and dynamically switch HTTP methods.


Specifications (Input/Output)

  • Input: Web API URL and a search keyword.
  • Output: Extracts items matching the criteria from the retrieved JSON data and displays them in the console.
  • Prerequisites: .NET 6.0 or higher. Uses System.Net.Http.Json.

Basic Usage

  1. Create an instance of HttpRequestMessage, specifying the HTTP method and the URL.
  2. Configure request-specific headers or content as needed.
  3. Pass the message to HttpClient.SendAsync to transmit.
var request = new HttpRequestMessage(HttpMethod.Get, "https://api.example.com/data");

// Add a custom header specific to this request
request.Headers.Add("X-Custom-Header", "Value");

// Execute the request
using var response = await client.SendAsync(request);

Full Code Example

The following console application retrieves a list of posts from a test API (JSONPlaceholder) and displays only those whose titles contain a specific keyword. It demonstrates using SendAsync to attach a unique “Request ID” header to each call.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading.Tasks;

class Program
{
    // Reuse HttpClient as a singleton
    private static readonly HttpClient _httpClient = new HttpClient();

    static async Task Main()
    {
        // 1. Configure common headers for all requests
        _httpClient.DefaultRequestHeaders.Accept.Clear();
        _httpClient.DefaultRequestHeaders.Accept.Add(
            new MediaTypeWithQualityHeaderValue("application/json"));
        _httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("MyTechBlogSearcher/1.0");

        // API Endpoint
        string url = "https://jsonplaceholder.typicode.com/posts";
        string filterKeyword = "optio"; 

        Console.WriteLine($"Building request for: {url}");

        try
        {
            // 2. Construct the Request Message
            using var request = new HttpRequestMessage(HttpMethod.Get, url);

            // [Important] Add a unique header for this specific request
            // This is added to request.Headers, not DefaultRequestHeaders
            request.Headers.Add("X-Request-ID", Guid.NewGuid().ToString());

            // 3. Send via SendAsync
            using var response = await _httpClient.SendAsync(request);

            if (response.StatusCode == HttpStatusCode.OK)
            {
                // Retrieve the response stream
                using var stream = await response.Content.ReadAsStreamAsync();

                // Configure JSON options for case-insensitive mapping
                var options = new JsonSerializerOptions
                {
                    PropertyNameCaseInsensitive = true
                };
                
                var articles = await JsonSerializer.DeserializeAsync<List<Article>>(stream, options);

                Console.WriteLine($"--- Articles containing '{filterKeyword}' ---");

                // Filter and display using LINQ
                if (articles != null)
                {
                    var filtered = articles.Where(a => a.Title.Contains(filterKeyword));
                    foreach (var item in filtered)
                    {
                        Console.WriteLine($"[ID:{item.Id}] {item.Title}");
                        Console.WriteLine($"URL: https://example.com/posts/{item.Id}"); 
                        Console.WriteLine("------------------------------------------");
                    }
                }
            }
            else
            {
                Console.WriteLine($"Error: {response.StatusCode}");
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Exception: {ex.Message}");
        }
    }
}

// Data model class
public class Article
{
    [JsonPropertyName("id")]
    public int Id { get; set; }

    [JsonPropertyName("title")]
    public string Title { get; set; } = string.Empty;

    [JsonPropertyName("body")]
    public string Body { get; set; } = string.Empty;

    [JsonPropertyName("userId")]
    public int UserId { get; set; }
}

Customization Points

  • Changing HTTP Methods: Replace HttpMethod.Get with Post, Put, or Delete. For non-standard methods like PATCH, use new HttpMethod("PATCH").
  • Setting Body Content: When performing POST or PUT, assign data to request.Content.request.Content = new StringContent(jsonString, Encoding.UTF8, "application/json");
  • Property Mapping: Use the [JsonPropertyName("...")] attribute as shown in the code to map JSON names (e.g., user_id) to C# properties.

Important Notes

  • Disposable Nature: An HttpRequestMessage instance cannot be reused once it has been sent. To resend the same content, you must instantiate a new message.
  • Using Statement: Both HttpRequestMessage and HttpResponseMessage implement IDisposable. Always wrap them in using statements to prevent resource leaks.
  • Header Merging: Headers set in HttpClient.DefaultRequestHeaders and HttpRequestMessage.Headers are merged. Be careful of duplicate values.

Advanced Application

Checking Existence with the HEAD Method

To check if a file exists or to retrieve metadata (like file size) without downloading the entire body, use the HEAD method.

var headRequest = new HttpRequestMessage(HttpMethod.Head, "https://example.com/large-file.zip");
using var response = await _httpClient.SendAsync(headRequest);

if (response.StatusCode == HttpStatusCode.OK)
{
    Console.WriteLine($"File Size: {response.Content.Headers.ContentLength} bytes");
}

Conclusion

Combining HttpClient.SendAsync with HttpRequestMessage is the most flexible approach for interacting with REST APIs. It is essential for scenarios requiring per-request authentication tokens or specialized HTTP methods like HEAD and OPTIONS. Transitioning to this pattern provides complete control over the details of your HTTP communications.

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

私が勉強したこと、実践したこと、してることを書いているブログです。
主に資産運用について書いていたのですが、
最近はプログラミングに興味があるので、今はそればっかりです。

目次