Nello script precedente (https://www.aspitalia.com/script/1330/Semplificare-Gestione-Array-Querystring-ASP.NET-Core.aspx) abbiamo visto come, tramite un custom value provider, possiamo modificare il modo in cui rappresentare gli array in querystring sfruttando, per esempio, una sintassi più compatta basata su valori separati da virgole.
Ovviamente questo approccio può risultare non privo di effetti collaterali: se per esempio accettiamo dei valori decimali in querystring, il nostro Value Provider potrebbe erroneamente considerare un numero decimale come un array di stringhe.
Ci sono un paio di accorgimenti che possiamo adottare per migliorare il nostro custom value provider.
Attivazione tramite Resource Filter
Il primo è quello di sfruttare un Resource Filter così da attivarlo solo per le action dove sia effettivamente necessario. Innanzitutto dobbiamo creare una classe che implementi IResourceFilter come la seguente:
public class CommaSeparatedAttribute : Attribute, IResourceFilter { public void OnResourceExecuted(ResourceExecutedContext context) { } public void OnResourceExecuting(ResourceExecutingContext context) { context.ValueProviderFactories.Insert(0, new QueryStringCommaSeparatedValueProviderFactory()); } }
Essa ha il compito di registrare la nostra factory appena prima che la richiesta venga processata dalla action. In questo modo, possiamo disattivare il nostro value provider dalla classe Startup:
public void ConfigureServices(IServiceCollection services) { // non è più necessario configurare QueryStringCommaSeparatedValueProvider services.AddMvc(); // altro codice qui.. }
Solo nelle action in cui ne abbiamo bisogno, possiamo riattivarlo decorandole con l'attributo CommaSeparated che abbiamo appena definito:
[HttpGet] [CommaSeparated] public string Get([FromQuery]IEnumerable<int> values) { return string.Join(" - ", values); }
Attivazione tramite BindingSource
Il resource filter appena creato ha il problema di agire sull'intera richiesta invece che sul singolo parametro, quindi potrebbe non essere sufficientemente preciso per alcuni scenari. Un altro approccio è quello di configurare una BindingSource completamente separata. Quando specifichiamo FromQuery nei parametri di una action, infatti, stiamo indicando ad ASP.NET Core che la BindingSource per quel parametro è di tipo Query. Ad ogni BindingSource possono poi essere associati uno o più ValueProvider che il runtime utilizzerà per leggere questa informazione dalla richiesta.
Nel nostro caso, l'idea è quella di definire una BindingSource completamente nuova, creando un attribute come il seguente:
public class FromCommaSeparatedArrayAttribute : Attribute, IBindingSourceMetadata { public static BindingSource CommaSeparated => new BindingSource( id: "commaSeparated", displayName: "Comma separated array", isGreedy: false, isFromRequest: true); public BindingSource BindingSource => FromCommaSeparatedArrayAttribute.CommaSeparated; }
Il nostro FromCommaSeparatedArrayAttribute implementa IBindingSourceMetadata e restituisce un BindingSource denominato CommaSeparated. A questo punto dobbiamo effettuare una piccola modifica a QueryStringCommaSeparatedValueProviderFactory che abbiamo creato nello script precedente:
public class QueryStringCommaSeparatedValueProviderFactory : IValueProviderFactory { public Task CreateValueProviderAsync(ValueProviderFactoryContext context) { if (context == null) { throw new ArgumentNullException(nameof(context)); } var query = context.ActionContext.HttpContext.Request.Query; if (query != null && query.Count > 0) { var valueProvider = new QueryStringCommaSeparatedValueProvider( // invece di BindingSource.Query FromCommaSeparatedArrayAttribute.CommaSeparated, query, CultureInfo.InvariantCulture); context.ValueProviders.Add(valueProvider); } return Task.CompletedTask; } }
Come possiamo vedere, questa volta quando aggiungiamo QueryStringCommaSeparatedValueProvider alla collezione di value provider disponibili, sfruttiamo il marcatore CommaSeparated invece che il BindingSource.Query standard. In questo modo, ASP.NET Core lo ignorerà per tutti i binding da querystring, a meno che non lo attiviamo in maniera esplicita sul singolo parametro:
public string Get([FromCommaSeparatedArray]IEnumerable<int> values) { return string.Join(" - ", values); }
Ovviamente, affinchè tutto funzioni, dobbiamo registrare la factory allo startup dell'applicazione:
public void ConfigureServices(IServiceCollection services) { services.AddMvc(options => { options.ValueProviderFactories.Add( new QueryStringCommaSeparatedValueProviderFactory()); }); // .. altro codice qui .. }
Conclusioni
Il sistema di model binding di ASP.NET Core, che traduce il contenuto della richiesta HTTP in parametri dei nostri metodi, è incredibilmente flessibile e personalizzabile. Nel corso di questo script e del precedente (https://www.aspitalia.com/script/1330/Semplificare-Gestione-Array-Querystring-ASP.NET-Core.aspx), abbiamo visto come modificare il modo in cui rappresentiamo Array in querystring pur continuando a sfruttare la complessa infrastruttura di model binding esistente. Abbiamo poi visto un paio di differenti approcci per limitare il più possibile gli effetti collaterali, indicanto in maniera puntuale dove la nostra logica deve essere attivata.
Commenti
Per inserire un commento, devi avere un account.
Fai il login e torna a questa pagina, oppure registrati alla nostra community.
Approfondimenti
Utilizzare la funzione EF.Parameter per forzare la parametrizzazione di una costante con Entity Framework
Gestire i dati con Azure Cosmos DB Data Explorer
Gestione degli stili CSS con le regole @layer
Creare una libreria CSS universale: Clip-path
Esporre i propri servizi applicativi con Semantic Kernel e ASP.NET Web API
Utilizzare il metodo IntersectBy per eseguire l'intersection di due liste
Path addizionali per gli asset in ASP.NET Core MVC
Anonimizzare i dati sensibili nei log di Azure Front Door
Garantire la provenienza e l'integrità degli artefatti prodotti su GitHub
Creare una custom property in GitHub
Generare HTML a runtime a partire da un componente Razor in ASP.NET Core
Aggiornare a .NET 9 su Azure App Service