Personalizzazioni avanzate del parsing della richiesta in ASP.NET Core

di Marco De Sanctis, in ASP.NET Core,

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

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