Loading...

Networking in C# (TcpClient, HttpClient, REST API Calls)

Learn networking in C# using TcpClient, HttpClient, and REST API calls to handle communication and external data exchange.

In modern applications, networking plays a major role. In C#, communication can be achieved both via low-level TCP sockets and high-level HTTP/REST API calls. .NET provides powerful classes for these operations: TcpClient, TcpListener, and HttpClient. In this article, we’ll explore step-by-step examples from basic network programming to REST API calls.


1. What is TCP?

TCP (Transmission Control Protocol) is a connection-oriented and reliable communication protocol. Data packets are sent in order, and any lost or corrupted packets are retransmitted. TCP operates on a client–server model: one side listens (TcpListener), while the other side connects (TcpClient).


2. A Simple Server with TcpListener

The following example shows a TCP server that listens for incoming connections on port 5000.


using System;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;

class TcpServer
{
    public static async Task StartAsync()
    {
        var listener = new TcpListener(IPAddress.Loopback, 5000);
        listener.Start();
        Console.WriteLine("Server started. Listening on port 5000...");

        while (true)
        {
            using TcpClient client = await listener.AcceptTcpClientAsync();
            Console.WriteLine("New client connected!");

            using var stream = client.GetStream();
            using var reader = new StreamReader(stream, Encoding.UTF8);
            using var writer = new StreamWriter(stream, Encoding.UTF8) { AutoFlush = true };

            string? message = await reader.ReadLineAsync();
            Console.WriteLine($"Received from client: {message}");

            await writer.WriteLineAsync($"Server response: {message?.ToUpper()}");
        }
    }

    static async Task Main() => await StartAsync();
}

Each time the server receives a new connection, it reads the message, converts it to uppercase, and sends it back. IPAddress.Loopback represents the local machine only.


3. Connecting a Client with TcpClient

On the client side, we can use TcpClient to connect to the server:


using System;
using System.IO;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;

class TcpClientApp
{
    static async Task Main()
    {
        using var client = new TcpClient();
        await client.ConnectAsync("127.0.0.1", 5000);

        using var stream = client.GetStream();
        using var writer = new StreamWriter(stream, Encoding.UTF8) { AutoFlush = true };
        using var reader = new StreamReader(stream, Encoding.UTF8);

        Console.Write("Message: ");
        string message = Console.ReadLine()!;
        await writer.WriteLineAsync(message);

        string response = await reader.ReadLineAsync() ?? "";
        Console.WriteLine($"Response from server: {response}");
    }
}

After starting the server, run this client. The message you send will be returned in uppercase.


4. Web and REST API Calls with HttpClient

The most common form of networking today is HTTP-based REST services. In .NET, the HttpClient class is used for these operations. It works asynchronously, is reusable, and ideal for JSON-based data exchange.


using System;
using System.Net.Http;
using System.Text.Json;
using System.Threading.Tasks;

class HttpClientExample
{
    static readonly HttpClient http = new HttpClient();

    static async Task Main()
    {
        var url = "https://jsonplaceholder.typicode.com/posts/1";
        Console.WriteLine($"GET {url}");

        var response = await http.GetAsync(url);
        string json = await response.Content.ReadAsStringAsync();

        var post = JsonSerializer.Deserialize<Post>(json);
        Console.WriteLine($"Title: {post?.Title}");
    }

    public class Post
    {
        public int Id { get; set; }
        public string? Title { get; set; }
        public string? Body { get; set; }
    }
}

This example fetches JSON data from a REST API and deserializes it into a C# class (Post). JsonSerializer is the built-in JSON library in .NET.


5. Sending a POST Request

With HttpClient, you can send POST requests by manually creating JSON content or using serialization.


using System.Net.Http;
using System.Text;
using System.Text.Json;

var http = new HttpClient();

var newPost = new
{
    title = "New Post",
    body = "Hello world!",
    userId = 1
};

var json = JsonSerializer.Serialize(newPost);
var content = new StringContent(json, Encoding.UTF8, "application/json");

var response = await http.PostAsync("https://jsonplaceholder.typicode.com/posts", content);
Console.WriteLine($"Status: {response.StatusCode}");

string result = await response.Content.ReadAsStringAsync();
Console.WriteLine($"Response: {result}");

This example demonstrates the basic way of sending data to a REST API. In real-world applications, using the async/await pattern is crucial for responsiveness.


6. HttpClient Lifecycle (Best Practice)

Recreating a new HttpClient instance for every request can cause unnecessary resource consumption. The correct approach is to use a single (singleton) instance:


// Program.cs
builder.Services.AddHttpClient<IMyApiService, MyApiService>();

// Service
public class MyApiService : IMyApiService
{
    private readonly HttpClient _http;
    public MyApiService(HttpClient http)
    {
        _http = http;
        _http.BaseAddress = new Uri("https://api.example.com/");
    }

    public async Task<User?> GetUserAsync(int id)
    {
        return await _http.GetFromJsonAsync<User>($"users/{id}");
    }
}

The HttpClientFactory in ASP.NET Core automatically manages connection pooling.


7. Error Handling and Timeout Settings

You should handle exceptions, timeouts, and unexpected errors when making HTTP requests.


var http = new HttpClient { Timeout = TimeSpan.FromSeconds(5) };

try
{
    var response = await http.GetAsync("https://api.github.com/repos/dotnet/runtime");
    response.EnsureSuccessStatusCode(); // Throws for non-200–299 responses

    string content = await response.Content.ReadAsStringAsync();
    Console.WriteLine("Successful request: " + content.Length + " bytes");
}
catch (HttpRequestException ex)
{
    Console.WriteLine($"HTTP error: {ex.Message}");
}
catch (TaskCanceledException)
{
    Console.WriteLine("Request timed out!");
}

8. SSL, Headers, and Token Usage

Most modern APIs use an Authorization header for authentication. You can easily add custom headers and settings:


var http = new HttpClient();
http.DefaultRequestHeaders.Add("Authorization", "Bearer <token>");
http.DefaultRequestHeaders.Add("User-Agent", "MyApp/1.0");

var res = await http.GetAsync("https://api.github.com/user");
Console.WriteLine(res.StatusCode);

HTTPS connections are secure by default. In development environments (e.g., self-signed certificates), you can configure a custom handler.


9. Handling Large Data Streams

For large files or data transfers, you can read directly from the Stream without loading everything into memory:


using var http = new HttpClient();
using var response = await http.GetAsync("https://example.com/bigfile.zip", HttpCompletionOption.ResponseHeadersRead);

await using var stream = await response.Content.ReadAsStreamAsync();
using var file = File.Create("downloaded.zip");

await stream.CopyToAsync(file);
Console.WriteLine("File downloaded.");

This approach uses memory efficiently, as the file is not fully loaded into RAM.


10. Example: Consuming a REST API (WeatherService)

The example below retrieves data from a weather API and maps it to a model.


public class WeatherInfo
{
    public string? City { get; set; }
    public float Temperature { get; set; }
    public string? Condition { get; set; }
}

public class WeatherService
{
    private readonly HttpClient _http;
    public WeatherService(HttpClient http)
    {
        _http = http;
        _http.BaseAddress = new Uri("https://api.weatherapi.com/v1/");
    }

    public async Task<WeatherInfo?> GetAsync(string city)
    {
        var key = "<your-api-key>";
        string url = $"current.json?key={key}&q={city}&aqi=no";

        var res = await _http.GetAsync(url);
        res.EnsureSuccessStatusCode();

        using var s = await res.Content.ReadAsStreamAsync();
        using var doc = await JsonDocument.ParseAsync(s);

        return new WeatherInfo
        {
            City = city,
            Temperature = doc.RootElement.GetProperty("current").GetProperty("temp_c").GetSingle(),
            Condition = doc.RootElement.GetProperty("current").GetProperty("condition").GetProperty("text").GetString()
        };
    }
}

This structure aligns with Clean Architecture principles and leverages Dependency Injection via HttpClientFactory.


11. Performance and Best Practices


TL;DR

  • TcpClient/TcpListener: Provide low-level, connection-based TCP communication.
  • HttpClient: High-level client for communicating with REST APIs.
  • GetAsync, PostAsync: Used for HTTP requests; JSON parsing is done with JsonSerializer.
  • HttpClientFactory: Recommended for performance and singleton management.
  • Asynchronous operations: Use await, manage Timeout and exceptions carefully.

Related Articles