C# HTTP Proxy mit .NET 8+: Der ultimative Guide für Entwickler

Lernen Sie, wie Sie HTTP-Proxys in C# und .NET 8+ konfigurieren: von HttpClient über SocketsHttpHandler bis hin zu rotierenden Proxy-Pools mit Polly und Dependency Injection.

C# HTTP Proxy mit .NET 8+: Der ultimative Guide für Entwickler

Warum C# HTTP Proxy-Konfiguration wichtig ist

Wenn Sie als .NET-Entwickler Web-Scraping, API-Automatisierung oder Preisüberwachung betreiben, kommen Sie an Proxys nicht vorbei. Websites erkennen und blockieren verdächtige Anfragemuster – und eine einzelne IP-Adresse, die hunderte Requests pro Minute sendet, ist definitiv verdächtig.

Die Verwendung von C# HTTP Proxys mit HttpClient ist nicht trivial. Es gibt Fallstricke bei Connection-Pooling, DNS-Resolution, TLS-Zertifikaten und Proxy-Rotation. Dieser Guide zeigt Ihnen, wie Sie .NET HttpClient mit Proxy korrekt konfigurieren und produktionsreife Lösungen für C# residential proxies implementieren.

Grundlagen: HttpClient mit WebProxy

Der klassische Weg in .NET verwendet HttpClientHandler mit einem WebProxy-Objekt. Das funktioniert für einfache Szenarien, hat aber einige Eigenheiten, die Sie kennen müssen.

using System.Net;
using System.Net.Http;

// Proxy mit Credentials konfigurieren
var proxy = new WebProxy("http://gate.proxyhat.com:8080")
{
    Credentials = new NetworkCredential("user-country-US", "your-password"),
    BypassProxyOnLocal = true,
    BypassList = new[] { "localhost", "127.0.0.1", "*.internal.local" }
};

// HttpClientHandler mit Proxy erstellen
var handler = new HttpClientHandler
{
    Proxy = proxy,
    UseProxy = true,
    UseDefaultCredentials = false,
    AllowAutoRedirect = true,
    MaxAutomaticRedirections = 5
};

// HttpClient instanziieren (als Singleton verwenden!)
var client = new HttpClient(handler)
{
    Timeout = TimeSpan.FromSeconds(30)
};

// Request senden
var response = await client.GetAsync("https://httpbin.org/ip");
var content = await response.Content.ReadAsStringAsync();
Console.WriteLine($"Response: {content}");

Wichtig: Instanziieren Sie HttpClient nicht pro Request. Das führt zu Socket-Exhaustion. Verwenden Sie IHttpClientFactory oder einen Singleton.

Proxy-URL-Format mit ProxyHat

Bei ProxyHat können Sie Geo-Targeting und Session-Flags direkt im Benutzernamen übergeben:

// US-IP mit Session-Sticky
var proxy = new WebProxy("http://gate.proxyhat.com:8080")
{
    Credentials = new NetworkCredential("user-country-US-session-abc123", "your-password")
};

// Deutsche IP aus Berlin
var proxyDe = new WebProxy("http://gate.proxyhat.com:8080")
{
    Credentials = new NetworkCredential("user-country-DE-city-berlin", "your-password")
};

SocketsHttpHandler: Der moderne Ansatz

Seit .NET Core 2.1 ist SocketsHttpHandler der empfohlene Handler. Er bietet mehr Kontrolle über Connection-Pooling, DNS-Refresh und Proxy-Selection.

using System.Net;
using System.Net.Http;

var proxy = new WebProxy("http://gate.proxyhat.com:8080")
{
    Credentials = new NetworkCredential("user-country-US", "your-password")
};

var handler = new SocketsHttpHandler
{
    Proxy = proxy,
    UseProxy = true,
    
    // Connection-Pool-Tuning für produktive Systeme
    PooledConnectionLifetime = TimeSpan.FromMinutes(5),
    PooledConnectionIdleTimeout = TimeSpan.FromMinutes(2),
    MaxConnectionsPerServer = 100,
    
    // DNS-Refresh für rotierende Proxys
    EnableMultipleHttp2Connections = true,
    AutomaticDecompression = DecompressionMethods.All
};

var client = new HttpClient(handler);

// Per-Request Proxy-Override über HttpRequestMessage
var request = new HttpRequestMessage(HttpMethod.Get, "https://api.ipify.org?format=json");
// Proxy kann auch per request.Properties gesetzt werden (fortgeschritten)

Warum PooledConnectionLifetime wichtig ist

Bei rotierenden Residential Proxys ändert sich die IP-Adresse regelmäßig. Wenn Sie eine Connection länger als die Rotationszeit offen halten, verwenden Sie möglicherweise eine veraltete IP. Setzen Sie PooledConnectionLifetime auf einen Wert unterhalb der Rotationszeit:

// Bei ProxyHat: typische Rotation alle 5-15 Minuten
var handler = new SocketsHttpHandler
{
    PooledConnectionLifetime = TimeSpan.FromMinutes(3),
    PooledConnectionIdleTimeout = TimeSpan.FromMinutes(1)
};

Polly: Retry und Circuit Breaker

Proxy-Requests scheitern häufiger als direkte Requests – Rate Limits, Timeouts, IP-Blöcke. Polly bietet eine elegante Lösung für Retry-Logik und Circuit Breaker.

using Polly;
using Polly.Retry;
using Polly.CircuitBreaker;

// Retry-Strategie: 3 Versuche mit exponentiellem Backoff
var retryStrategy = RetryStrategyBuilder<HttpResponseMessage>.Create()
    .HandleResult(r => !r.IsSuccessStatusCode)
    .Handle<HttpRequestException>()
    .Handle<TaskCanceledException>() // Timeouts
    .WaitAndRetryAsync(
        retryCount: 3,
        sleepDurationProvider: retryAttempt => 
            TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)),
        onRetry: (outcome, delay, retryCount, context) =>
        {
            Console.WriteLine($"Retry {retryCount} after {delay.TotalSeconds}s: {outcome.Exception?.Message ?? outcome.Result.StatusCode.ToString()}");
        });

// Circuit Breaker: Nach 5 konsekutiven Fehlern für 30s öffnen
var circuitBreaker = new CircuitBreakerStrategyBuilder<HttpResponseMessage>()
    .HandleResult(r => !r.IsSuccessStatusCode)
    .Handle<HttpRequestException>()
    .Handle<TaskCanceledException>()
    .AdvancedCircuitBreakerAsync(
        failureThreshold: 0.5, // 50% Fehlerquote
        samplingDuration: TimeSpan.FromSeconds(30),
        minimumThroughput: 5,
        durationOfBreak: TimeSpan.FromSeconds(30),
        onOpened: args => Console.WriteLine("Circuit breaker opened"),
        onClosed: args => Console.WriteLine("Circuit breaker closed"))
    .Build();

// Kombinierte Strategie
var resiliencePipeline = new ResiliencePipelineBuilder<HttpResponseMessage>()
    .AddStrategy(retryStrategy)
    .AddStrategy(circuitBreaker)
    .Build();

// Verwendung
var handler = new SocketsHttpHandler
{
    Proxy = new WebProxy("http://gate.proxyhat.com:8080")
    {
        Credentials = new NetworkCredential("user-country-US", "your-password")
    },
    UseProxy = true
};

var client = new HttpClient(handler);

var response = await resiliencePipeline.ExecuteAsync(
    async ct => await client.GetAsync("https://httpbin.org/status/500", ct),
    CancellationToken.None);

Parallel.ForEachAsync: Concurrent Scraping

Für produktives Web-Scraping müssen Sie mehrere URLs parallel abrufen. Parallel.ForEachAsync in .NET 8 bietet eine saubere, effiziente Lösung mit integriertem Throttling.

using System.Net;
using System.Net.Http;

var urls = new[]
{
    "https://httpbin.org/ip",
    "https://httpbin.org/headers",
    "https://httpbin.org/user-agent",
    "https://api.ipify.org?format=json",
    "https://ifconfig.me/ip"
};

// Shared HttpClient (thread-safe)
var proxy = new WebProxy("http://gate.proxyhat.com:8080")
{
    Credentials = new NetworkCredential("user-country-US", "your-password")
};

var handler = new SocketsHttpHandler
{
    Proxy = proxy,
    UseProxy = true,
    MaxConnectionsPerServer = 50, // Concurrent connections
    PooledConnectionLifetime = TimeSpan.FromMinutes(3)
};

using var client = new HttpClient(handler);

// Ergebnisse sammeln
var results = new System.Collections.Concurrent.ConcurrentBag<(string Url, string Content, TimeSpan Duration)>();
var stopwatch = System.Diagnostics.Stopwatch.StartNew();

// Parallel mit MaxDegreeOfParallelism
await Parallel.ForEachAsync(urls, new ParallelOptions
{
    MaxDegreeOfParallelism = 10 // Limit concurrent requests
}, async (url, ct) =>
{
    var sw = System.Diagnostics.Stopwatch.StartNew();
    try
    {
        var response = await client.GetAsync(url, ct);
        var content = await response.Content.ReadAsStringAsync(ct);
        sw.Stop();
        
        results.Add((url, content, sw.Elapsed));
        Console.WriteLine($"[{sw.ElapsedMilliseconds}ms] {url}: {content[..Math.Min(100, content.Length)]}...");
    }
    catch (Exception ex)
    {
        Console.WriteLine($"Error fetching {url}: {ex.Message}");
    }
});

stopwatch.Stop();
Console.WriteLine($"\nTotal: {stopwatch.ElapsedMilliseconds}ms for {urls.Length} URLs");
Console.WriteLine($"Average: {results.Average(r => r.Duration.TotalMilliseconds):0.00}ms per request");

Rotating Proxy Pool Service mit DI

Für produktive Anwendungen benötigen Sie einen Proxy-Pool-Service, der IPs automatisch rotiert und mit Dependency Injection funktioniert.

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System.Net;
using System.Collections.Concurrent;

// Proxy-Konfiguration
public class ProxyConfig
{
    public string GatewayHost { get; set; } = "gate.proxyhat.com";
    public int HttpPort { get; set; } = 8080;
    public int Socks5Port { get; set; } = 1080;
    public string Username { get; set; } = "user";
    public string Password { get; set; } = "password";
    public string[] Countries { get; set; } = ["US", "DE", "GB"];
    public int RotationIntervalSeconds { get; set; } = 300; // 5 Minuten
}

// Proxy-Info
public record ProxyInfo(string SessionId, string Country, WebProxy Proxy, DateTime CreatedAt);

// Proxy-Pool Service Interface
public interface IProxyPoolService
{
    Task<ProxyInfo> GetProxyAsync(string? country = null, CancellationToken ct = default);
    Task ReleaseProxyAsync(ProxyInfo proxy, CancellationToken ct = default);
    Task RotateAllAsync(CancellationToken ct = default);
}

// Proxy-Pool Implementation
public class ProxyPoolService : IProxyPoolService, IDisposable
{
    private readonly ProxyConfig _config;
    private readonly ConcurrentBag<ProxyInfo> _availableProxies = new();
    private readonly ConcurrentDictionary<string, ProxyInfo> _inUseProxies = new();
    private readonly Timer _rotationTimer;
    private readonly Random _random = new();

    public ProxyPoolService(ProxyConfig config)
    {
        _config = config;
        
        // Initiale Proxies erstellen
        InitializeProxies();
        
        // Automatische Rotation
        _rotationTimer = new Timer(
            _ => RotateAllAsync().GetAwaiter().GetResult(),
            null,
            TimeSpan.FromSeconds(_config.RotationIntervalSeconds),
            TimeSpan.FromSeconds(_config.RotationIntervalSeconds));
    }

    private void InitializeProxies()
    {
        foreach (var country in _config.Countries)
        {
            for (int i = 0; i < 5; i++) // 5 Proxies pro Land
            {
                var proxy = CreateProxy(country);
                _availableProxies.Add(proxy);
            }
        }
    }

    private ProxyInfo CreateProxy(string country)
    {
        var sessionId = Guid.NewGuid().ToString("N")[..8];
        var username = $"{_config.Username}-country-{country}-session-{sessionId}";
        
        var proxy = new WebProxy($"http://{_config.GatewayHost}:{_config.HttpPort}")
        {
            Credentials = new NetworkCredential(username, _config.Password)
        };
        
        return new ProxyInfo(sessionId, country, proxy, DateTime.UtcNow);
    }

    public Task<ProxyInfo> GetProxyAsync(string? country = null, CancellationToken ct = default)
    {
        ProxyInfo? proxy;
        
        if (country != null)
        {
            // Spezifisches Land suchen
            proxy = _availableProxies.FirstOrDefault(p => p.Country == country);
            if (proxy != null)
            {
                _availableProxies.TryTake(out _);
                _inUseProxies[proxy.SessionId] = proxy;
            }
        }
        else
        {
            // Beliebigen Proxy nehmen
            if (_availableProxies.TryTake(out proxy!))
            {
                _inUseProxies[proxy.SessionId] = proxy;
            }
        }
        
        if (proxy == null)
        {
            // Neuen Proxy erstellen falls Pool leer
            proxy = CreateProxy(country ?? _config.Countries[_random.Next(_config.Countries.Length)]);
            _inUseProxies[proxy.SessionId] = proxy;
        }
        
        return Task.FromResult(proxy);
    }

    public Task ReleaseProxyAsync(ProxyInfo proxy, CancellationToken ct = default)
    {
        _inUseProxies.TryRemove(proxy.SessionId, out _);
        
        // Proxy recyceln falls nicht zu alt
        if ((DateTime.UtcNow - proxy.CreatedAt).TotalMinutes < 10)
        {
            _availableProxies.Add(proxy);
        }
        
        return Task.CompletedTask;
    }

    public Task RotateAllAsync(CancellationToken ct = default)
    {
        Console.WriteLine($"[{DateTime.UtcNow:HH:mm:ss}] Rotating proxy pool...");
        
        // Alle in Verwendung behalten, verfügbare rotieren
        var oldProxies = _availableProxies.ToArray();
        _availableProxies.Clear();
        
        foreach (var country in _config.Countries)
        {
            for (int i = 0; i < oldProxies.Count(p => p.Country == country); i++)
            {
                _availableProxies.Add(CreateProxy(country));
            }
        }
        
        return Task.CompletedTask;
    }

    public void Dispose()
    {
        _rotationTimer.Dispose();
    }
}

// HttpClient Factory Integration
public static class ProxyServiceExtensions
{
    public static IServiceCollection AddProxyPool(
        this IServiceCollection services,
        Action<ProxyConfig> configure)
    {
        var config = new ProxyConfig();
        configure(config);
        
        services.AddSingleton(config);
        services.AddSingleton<IProxyPoolService, ProxyPoolService>();
        
        // Named HttpClient mit Proxy-Support
        services.AddHttpClient("ProxiedClient")
            .ConfigurePrimaryHttpMessageHandler(sp =>
            {
                var proxyPool = sp.GetRequiredService<IProxyPoolService>();
                var proxy = proxyPool.GetProxyAsync().GetAwaiter().GetResult();
                
                return new SocketsHttpHandler
                {
                    Proxy = proxy.Proxy,
                    UseProxy = true,
                    PooledConnectionLifetime = TimeSpan.FromMinutes(3)
                };
            });
        
        return services;
    }
}

// Verwendung in Program.cs
var builder = Host.CreateApplicationBuilder(args);
builder.Services.AddProxyPool(config =>
{
    config.Username = "user";
    config.Password = "your-password";
    config.Countries = ["US", "DE", "GB", "FR"];
    config.RotationIntervalSeconds = 300;
});

var app = builder.Build();

// Usage
var proxyPool = app.Services.GetRequiredService<IProxyPoolService>();
var proxy = await proxyPool.GetProxyAsync("US");
Console.WriteLine($"Got proxy: Session={proxy.SessionId}, Country={proxy.Country}");

TLS: Zertifikats-Pinning und Custom Root CA

Manche Proxy-Anbieter verwenden TLS-Inspection oder Custom Certificates. Hier ist, wie Sie damit umgehen.

using System.Net;
using System.Net.Http;
using System.Net.Security;
using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;

// Custom Certificate Validation (für Development/Test)
var handler = new SocketsHttpHandler
{
    Proxy = new WebProxy("http://gate.proxyhat.com:8080")
    {
        Credentials = new NetworkCredential("user-country-US", "your-password")
    },
    UseProxy = true,
    
    // TLS-Konfiguration
    SslOptions = new SslClientAuthenticationOptions
    {
        EnabledSslProtocols = SslProtocols.Tls12 | SslProtocols.Tls13,
        
        // Custom Certificate Validation
        RemoteCertificateValidationCallback = (sender, cert, chain, errors) =>
        {
            // Für Production: Proper Certificate Pinning
            if (errors == SslPolicyErrors.None)
                return true;
                
            // Custom Root CA akzeptieren (nur für bekannte Proxies!)
            var expectedThumbprint = "YOUR_PROXY_CA_THUMBPRINT";
            if (cert is X509Certificate2 cert2 && 
                cert2.Thumbprint?.Equals(expectedThumbprint, StringComparison.OrdinalIgnoreCase) == true)
            {
                return true;
            }
            
            Console.WriteLine($"Certificate validation failed: {errors}");
            return false; // Strict für Production
        }
    },
    
    // Connection-Timeout für TLS-Handshake
    ConnectTimeout = TimeSpan.FromSeconds(30)
};

var client = new HttpClient(handler);

// Certificate Pinning mit bekanntem Public Key
public class CertificatePinner
{
    private static readonly HashSet<string> _allowedPublicKeys = new()
    {
        "sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", // Durch echten Key ersetzen
        "sha256/BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB="
    };
    
    public static bool ValidateCertificate(
        HttpRequestMessage request,
        X509Certificate2? certificate,
        X509Chain? chain,
        SslPolicyErrors errors)
    {
        if (certificate == null) return false;
        
        // Public Key extrahieren
        var publicKey = certificate.GetPublicKeyString();
        var thumbprint = certificate.Thumbprint;
        
        // Pinning-Check
        var pinnedKey = $"sha256/{Convert.ToBase64String(certificate.GetPublicKeyHash())}";
        
        return _allowedPublicKeys.Contains(pinnedKey) || errors == SslPolicyErrors.None;
    }
}

// Mit HttpClient verwenden
var secureHandler = new SocketsHttpHandler
{
    Proxy = new WebProxy("http://gate.proxyhat.com:8080")
    {
        Credentials = new NetworkCredential("user-country-US", "your-password")
    },
    UseProxy = true,
    SslOptions = new SslClientAuthenticationOptions
    {
        RemoteCertificateValidationCallback = CertificatePinner.ValidateCertificate
    }
};

Produktions-Tipps und Best Practices

1. HttpClient-Lebenszyklus

  • Singleton Pattern: Verwenden Sie IHttpClientFactory oder einen Singleton-HttpClient. Pro-Request-Instanziierung führt zu Socket-Exhaustion.
  • PooledConnectionLifetime: Setzen Sie dies auf 3-5 Minuten bei rotierenden Proxys.
  • Timeouts: Setzen Sie angemessene Timeouts (30-60 Sekunden für Proxys).

2. Proxy-Rotation

  • Session-basiert: Verwenden Sie Session-IDs für Sticky Sessions (gleiche IP für mehrere Requests).
  • Rotation: Rotieren Sie IPs regelmäßig, aber nicht zu häufig (Rate Limits).
  • Geo-Targeting: Nutzen Sie Land/Stadt-Targeting für lokale Inhalte.

3. Error Handling

  • Retry-Logic: Verwenden Sie Polly für automatische Retries mit Backoff.
  • Circuit Breaker: Schützen Sie Ihre Anwendung vor kaskadierenden Fehlern.
  • Logging: Loggen Sie Proxy-IP, Response-Status und Latenz für Debugging.

Proxy-Typen im Vergleich

Proxy-TypGeschwindigkeitErkennungsrisikoUse Case
DatacenterSehr schnellHochHigh-Volume Scraping, nicht-kritische Targets
ResidentialMittelNiedrigSERP-Scraping, E-Commerce, Social Media
MobileVariabelSehr niedrigMobile-spezifische Inhalte, Social Media

Für C# residential proxies bietet ProxyHat eine zuverlässige Lösung mit hoher Verfügbarkeit und Geo-Targeting-Optionen.

Key Takeaways

  • Verwenden Sie SocketsHttpHandler mit PooledConnectionLifetime für rotierende Proxys.
  • Integrieren Sie Polly für Retry-Logik und Circuit Breaker in produktiven Anwendungen.
  • Nutzen Sie Parallel.ForEachAsync für effizientes Concurrent Scraping mit Throttling.
  • Implementieren Sie einen Proxy-Pool-Service mit DI für saubere Architektur.
  • Konfigurieren Sie TLS-Validation für Custom Root CAs und Certificate Pinning.
  • Bei ProxyHat: Nutzen Sie Username-Flags für Geo-Targeting und Sticky Sessions.

Weiterführende Ressourcen

Weitere Informationen zu Proxy-Konfigurationen und Use Cases finden Sie in unserer umfassenden Proxy-Anleitung. Für spezifische Anwendungen lesen Sie unseren Guide zu Web-Scraping mit Proxys.

Die aktuellen ProxyHat-Tarife und verfügbaren Standorte finden Sie auf unserer Preisseite und der Standortübersicht.

Bereit loszulegen?

Zugang zu über 50 Mio. Residential-IPs in über 148 Ländern mit KI-gesteuerter Filterung.

Preise ansehenResidential Proxies
← Zurück zum Blog