Gestire query string lunghe in ASP.NET Core

di Marco De Sanctis, in ASP.NET Core,

Come sappiamo, esiste un limite al numero di caratteri di cui può essere composto un URL, che dipende da una serie di fattori: il browser utilizzato dall'utente, il server su cui è in hosting l'applicazione ed eventuali infrastrutture di routing presenti tra l'endpoint pubblico e il server stesso.

Per esempio, con IIS abbiamo una lunghezza massima di default della query string di 2048 caratteri, che però possiamo aumentare tramite il parametro maxQueryString di web.config. Tuttavia, questo è solo uno dei limiti con cui abbiamo a che fare: entrano in gioco altri parametri, quali per esempio la dimensione massima dell'header della request, sui quali spesso non abbiamo controllo - soprattutto se siamo su una piattaforma PaaS come Azure App Service.

Volendo, però, possiamo sfruttare le peculiarità di ASP.NET Core per implementare un workaround a livello applicativo. L'idea è di esporre un endpoint che il client possa chiamare, in POST, per "salvare" una serie di parametri di query string, con lo scopo di recuperarli in un secondo momento:

private IQueriesService _service;

[HttpPost]
public async Task<IActionResult> CreateStoredQueryAsync(
    [FromBody] List<KeyValuePair<string, string>> queryParams)
{
    int result = await _service.SaveQueryAsync(queryParams);

    return this.Ok(result);
}

Essendo la chiamata in POST, ci sono molti meno vincoli sulla sua lunghezza, visto che i parametri saranno contenuti nel body. Questo endpoint prima memorizza la serie di coppie chiave valore nel database tramite un servizio IQueriesService, di cui non mostriamo l'implementazione per brevità.

Successivamente, restituisce un identificativo che il client potrà inviare in una successiva richiesta:

Come possiamo notare, vogliamo fare in modo che sia anche possibile combinare storedQueryId con parametri "discreti" della queryString, così che il client non debba salvarne una nuova a ogni minima variazione di questi.

Ma come possiamo poi "leggere" questi parametri da backend, senza dover riscrivere tutti i nostri controller? In questo caso l'infrastruttura di middleware di ASP.NET Core ci viene in aiuto. Ciò che possiamo fare, infatti, è creare un middleware che abbia lo scopo di re-idratare i parametri reali, così che i controller non si accorgano che in realtà provengono da una stored query string.

internal class StoredQueryMiddleware
{
    private readonly RequestDelegate _next;

    public StoredQueryMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task Invoke(HttpContext context, IQueriesService service)
    {
        if (context.Request.Query.ContainsKey("storedQueryId") 
          && int.TryParse(context.Request.Query["storedQueryId"], out int storedQueryId))
        {
            List<KeyValuePair<string, string>> contextParams = 
               await service.GetStoredQueryAsync(storedQueryId);
            
            List<KeyValuePair<string, string>> queryParams = 
               context.Request.Query
                .SelectMany(x => x.Value.Select(y=>new KeyValuePair<string, string>(x.Key, y)))
                .ToList();
            queryParams.AddRange(contextParams);

            QueryBuilder qb = new QueryBuilder(queryParams);
            context.Request.QueryString = qb.ToQueryString();
        }

        await _next(context);
    }
}

Nel metodo Invoke, iniettiamo ancora il nostro IQueriesService, che abbiamo già utilizzato in precedenza per salvare la query. Ne recuperiamo il contenuto dal database, e nelle righe successive, lo uniamo ai parametri già presenti nell'oggetto Request dell'HttpContext corrente.

A questo punto, non dobbiamo far altro che registrare il nostro middleware nella pipeline di ASP.NET Core prima dell'Endpoint middleware, così che venga eseguito prima del controller invocato, facendo sì che quest'ultimo non si accorga minimamente della reidratazione della query string a partire dall'identificativo fornito.

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    ...

    app.UseMiddleware<StoredQueryMiddleware>();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllers();
    });
}

Commenti

Visualizza/aggiungi commenti

| Condividi su: Twitter, Facebook, LinkedIn

Per inserire un commento, devi avere un account.

Fai il login e torna a questa pagina, oppure registrati alla nostra community.

Approfondimenti

I più letti di oggi