Abbreviare un URL in C# significa fare un solo POST. Invia la destinazione all'API dell'URL shortener con HttpClient, passa la tua chiave API come token Bearer e deserializza short_url dalla risposta. PostAsJsonAsync gestisce la serializzazione, quindi, una volta registrato il client, la chiamata occupa una sola riga.
La struttura dell'endpoint, il modello di autenticazione e i limiti del piano gratuito considerati qui sono documentati nella panoramica dell'API gratuita per abbreviare gli URL. Questo è l'articolo dedicato a C# di una serie che tratta anche Python, JavaScript, Go e Java.
Il modo più rapido: PostAsJsonAsync
using System.Net.Http.Json;
record LinkRequest(string destination_url);
record LinkResponse(string id, string short_url);
var http = new HttpClient { BaseAddress = new Uri("https://api.elido.app") };
http.DefaultRequestHeaders.Authorization =
new("Bearer", Environment.GetEnvironmentVariable("ELIDO_API_KEY"));
http.Timeout = TimeSpan.FromSeconds(10);
var response = await http.PostAsJsonAsync("/v1/links",
new LinkRequest("https://example.com/spring-sale?utm_source=newsletter"));
response.EnsureSuccessStatusCode();
var link = await response.Content.ReadFromJsonAsync<LinkResponse>();
Console.WriteLine(link!.short_url); // https://s.elido.me/ab12cd
I nomi delle proprietà del record corrispondono intenzionalmente allo snake_case dell'API, così l'esempio non richiede dipendenze. In un progetto reale aggiungi [JsonPropertyName("short_url")] e assegna alla proprietà un nome C#, oppure imposta PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower nelle opzioni una volta sola.
EnsureSuccessStatusCode è la riga che conta. Senza di essa un 401 termina tranquillamente, ReadFromJsonAsync restituisce un record con valori null e l'errore riemerge tre livelli più avanti come una NullReferenceException su qualcosa di non correlato.
Registra il client invece di crearne uno ogni volta
Lo snippet sopra va bene per una prova veloce in una console, ma è sbagliato in un servizio. Un HttpClient creato a ogni chiamata apre ogni volta un nuovo pool di connessioni e smaltirlo lascia il socket in TIME_WAIT; fallo in un ciclo e finirai le porte. IHttpClientFactory riutilizza i gestori e ruota correttamente il DNS.
builder.Services.AddHttpClient<ElidoClient>(client =>
{
client.BaseAddress = new Uri("https://api.elido.app");
client.DefaultRequestHeaders.Authorization =
new("Bearer", builder.Configuration["Elido:ApiKey"]);
client.Timeout = TimeSpan.FromSeconds(10);
})
.AddStandardResilienceHandler(); // retries, timeout, circuit breaker
AddStandardResilienceHandler proviene da Microsoft.Extensions.Http.Resilience e offre retry con jitter sugli errori transitori, un timeout per la richiesta completa e un circuit breaker senza dover scrivere una policy. In un progetto più vecchio, AddTransientHttpErrorPolicy di Polly copre lo stesso caso.
Il client tipizzato resta piccolo:
public sealed class ElidoClient(HttpClient http)
{
public async Task<string> ShortenAsync(string destination, CancellationToken ct = default)
{
var request = new HttpRequestMessage(HttpMethod.Post, "/v1/links")
{
Content = JsonContent.Create(new LinkRequest(destination)),
};
// stable across retries and across re-runs of the same batch
request.Headers.Add("Idempotency-Key",
Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(destination))));
using var response = await http.SendAsync(request, ct);
response.EnsureSuccessStatusCode();
var link = await response.Content.ReadFromJsonAsync<LinkResponse>(ct);
return link!.short_url;
}
}
Quell'header è il motivo per cui è sicuro attivare il gestore di resilienza. Senza di esso, un retry dopo una risposta persa crea un secondo link per la stessa destinazione; con l'header, l'API restituisce quello originale. L'approfondimento su limiti di velocità e idempotenza spiega perché, per il lavoro in blocco, derivare la chiave dalla destinazione è meglio di un GUID nuovo.
Vuoi provarlo con un endpoint attivo? Crea una chiave nel piano gratuito, inseriscila negli user secrets come Elido:ApiKey e la registrazione qui sopra funzionerà senza modifiche.
Leggi il body dell'errore invece di tirare a indovinare
EnsureSuccessStatusCode è il default giusto, ma scarta la parte più utile di una chiamata fallita. Il messaggio dell'eccezione contiene il codice di stato e nient'altro, quindi un 422 ti dice che la richiesta è stata rifiutata senza dirti quale campo non è piaciuto all'API.
Per tutto ciò che dovrai debuggare alle tre di notte, leggi prima il body:
using var response = await http.SendAsync(request, ct);
if (!response.IsSuccessStatusCode)
{
var problem = await response.Content.ReadAsStringAsync(ct);
logger.LogWarning("shorten failed {Status}: {Body}", (int)response.StatusCode, problem);
throw response.StatusCode switch
{
HttpStatusCode.Unauthorized => new InvalidOperationException("API key rejected"),
HttpStatusCode.TooManyRequests => new HttpRequestException("rate limited"),
_ => new HttpRequestException($"shorten failed: {problem}"),
};
}
Da qui emergono due cose. La riga di log indica il campo dell'errore di validazione invece di lasciarti ricostruire la richiesta, e lo switch separa gli errori per cui vale la pena fare retry da quelli che non andranno mai a buon fine. Ripetere tre volte un 401 significa fare tre chiamate sprecate e ottenere un errore più lento.
Il gestore di resilienza standard tratta già 5xx e 429 come transitori e lascia stare i 4xx, quindi i due livelli concordano: ripete ciò che vale la pena ripetere e il tuo codice spiega ciò che rimane.
In blocco senza superare il limite di velocità
Task.WhenAll applicato a una lista di 3.000 URL avvia 3.000 richieste. Parallel.ForEachAsync limita il numero di richieste in corso ed è più leggibile di un semaforo scritto a mano:
var results = new ConcurrentDictionary<string, string>();
await Parallel.ForEachAsync(
urls,
new ParallelOptions { MaxDegreeOfParallelism = 8 },
async (url, ct) =>
{
try
{
results[url] = await client.ShortenAsync(url, ct);
}
catch (Exception ex)
{
results[url] = $"ERROR: {ex.Message}"; // one bad URL must not sink the batch
}
});
Otto è un punto di partenza, non una costante. Controlla i 429 e l'header Retry-After inviato dall'API e riduci il valore invece di aumentarlo. Usare l'URL originale come chiave significa che un'esecuzione lasciata a metà ti indica esattamente quali righe devi rifare.
Dove deve stare il codice
Uno strumento da console che abbrevia un CSV, un servizio ospitato che crea un link quando viene spedito un ordine, una Azure Function attivata da una coda: stesso client tipizzato, tre registrazioni diverse. L'unica cosa che non deve cambiare è l'origine della chiave. User secrets in sviluppo, variabili d'ambiente o un vault in produzione, IOptions nel mezzo e niente in appsettings.json che finisca nel repository.
Una volta che i link vengono creati da un servizio, cos'altro espone l'API agli sviluppatori diventa la parte interessante, e i webhook per gli eventi dei link sono meglio del polling per i dati su click e scansioni. Se preferisci non mantenere affatto il client, la pagina API e SDK elenca quelli generati.
Leggi la serie cornerstone
Questo articolo fa parte del cluster engineering. Inizia con la guida all'API gratuita per abbreviare gli URL per la struttura dell'endpoint e l'autenticazione, poi passa a limiti di velocità e idempotenza per il comportamento sotto carico. Il riferimento aggiornato è la documentazione dell'API.
Articoli correlati sul blog
Domande frequenti
Come posso abbreviare un URL in C#?
Invia l'URL lungo all'API di un URL shortener con HttpClient, usando PostAsJsonAsync per fare serializzare il body automaticamente, poi deserializza la risposta in un record semplice e leggi ShortUrl. Con l'header Authorization impostato sul client, la chiamata in sé occupa una sola riga.
Perché non dovrei usare new HttpClient() in un ciclo?
Ogni istanza apre il proprio pool di connessioni e smaltirne una lascia il socket in TIME_WAIT, quindi sotto carico un ciclo può esaurire le porte disponibili. Registra il client con IHttpClientFactory e iniettalo: in questo modo i gestori vengono riutilizzati e il DNS viene ruotato correttamente.
HttpClient genera un'eccezione per una risposta 401 o 500?
Non per impostazione predefinita. Il task termina correttamente e IsSuccessStatusCode è false, quindi una chiamata fallita sembra funzionare se non fai un controllo. Chiama EnsureSuccessStatusCode per trasformarla in un HttpRequestException, oppure verifica tu lo status prima di leggere il body.
Come aggiungo dei retry a HttpClient in .NET?
Aggiungi il gestore di resilienza standard di Microsoft.Extensions.Http.Resilience alla registrazione del client. Include retry con backoff con jitter, un timeout totale della richiesta e un circuit breaker senza codice per ogni chiamata. Nei progetti più vecchi, AddTransientHttpErrorPolicy di Polly svolge lo stesso lavoro.
Come posso abbreviare molti URL contemporaneamente in C#?
Usa Parallel.ForEachAsync con MaxDegreeOfParallelism impostato intorno a otto: limita il numero di richieste in corso indipendentemente dalla lunghezza della lista. Raccogli i risultati in un ConcurrentDictionary indicizzato dall'URL originale, così un errore parziale resta visibile e il batch può essere rieseguito.
Dove deve stare la chiave API in un'app .NET?
In una configurazione che non viene sottoposta a commit: user secrets in sviluppo, variabili d'ambiente o un key vault in produzione. Associala con IOptions e leggila durante la registrazione del client, così ruotare la chiave richiede una modifica alla configurazione e non una nuova build.
Prova Elido
Incolla un URL, ottieni un link breve
Senza registrazione. Il link vive 30 giorni. Iscriviti per conservarlo.
Gratis, nessuna registrazione richiesta · 2 al giorno