Recuperare tutti gli oggetti di un certo tipo di una pagina ASP.NET con LINQ

di Stefano Mostarda, in LINQ, ASP.NET 3.5,

Molto spesso capita di dover recuperare gli oggetti di un certo tipo non da una lista piatta, ma da una struttura ad albero. Un classico esempio è una pagina ASP.NET dove i vari oggetti sono annidati all'interno delle MasterPage, della Form, di UserControl, ecc ecc.
In questi casi, la soluzione più semplice è appiattire l'albero trasformandolo facilmente in una lista tramite LINQ.
Questa caratteristica non è disponibile in nativo con LINQ, ma la si può costruire definendo un Extension Method personalizzato. Una volta ottenuta l'intera lista degli oggetti si può utilizzare il metodo OfType per recuperare solo le istanze di oggetti del tipo voluto.

Il primo passo consiste nel creare una classe ed inserirvi l'Extension Method.

public static IEnumerable<T> MakeFlat<T>(this IEnumerable<T> source, Func<T, IEnumerable<T>> recurse){
  List<T> result = new List<T>();
  foreach (T item in source)
  {
    IEnumerable<T> subItems = recurse(item);
    result.AddRange(subItems);
    MakeFlat<T>(subItems, recurse);
  }
  return result;
}

Come si vede, l'Extension Method non fa altro che prendere in input una lista generica e iterarla ricorsivamente popolando una lista. Molto importante è il secondo parametro che altro non è che un delegato che punta ad una funzione che dato un oggetto ne restituisce i figli.

Ora che si ha a disposizione il metodo per appiattire il grafo di oggetti, si può passare a scrivere una query che interroghi una pagina come questa:

<form id="form1" runat="server"><div>
  <asp:TextBox runat="server" ID="TextBox1"></asp:TextBox>
  <asp:TextBox runat="server" ID="txt"></asp:TextBox>
  <asp:Button runat="server" ID="Button2"></asp:Button>
  <asp:Button runat="server" ID="Button1"></asp:Button>
  <asp:LinkButton runat="server" ID="LinkButton2"></asp:LinkButton>
  <asp:LinkButton runat="server" ID="LinkButton1"></asp:LinkButton>
  <asp:ListView runat="server" ID="lv" ItemContainerID="container">
    <LayoutTemplate>
      <ul>        <asp:PlaceHolder runat="server" ID="container"></asp:PlaceHolder> 
      </ul>
    </LayoutTemplate>
    <ItemTemplate>
      <li><%# Eval("ID") %></li>
    </ItemTemplate>
  </asp:ListView>
</div>
</form>

L'Extension Method estende una lista generica, cosa che la proprietà Controls della classe Control non è, quindi bisogna eseguire una trasformazione da lista non generica a lista generica tramite il metodo Cast(). Successivamente, si richiama il metodo MakeFlat per recuperare la lista di oggetti ed infine si usa OfType per filtrare la lista con solo quelli del tipo richiesto.

var q = from p in Page.Controls.Cast<Control>().MakeFlat(GetChildren).OfType<Button>() select p;
        
lv.DataSource = q;
lv.DataBind();

L'ultimo passo è costruire il metodo GetChildren, che è passato all'ExtensionMethod per recuperare i figli di un oggetto. Nel caso in esame, l'oggetto è di tipo Control e restituisce la proprietà Controls.

IEnumerable<T> GetChildren<T>(T input) where T : Control{
  return input.Controls.Cast<T>();
}

A prima vista, questo approccio può sembrare più complesso rispetto ad una semplice funzione ricorsiva, ma va tenuto conto di un fattore importante: l'Extension Method è generico e può ciclare qualsiasi grafo di oggetti, associando questo al fatto che il metodo che torna i figli è definito dal chiamante, basta costruire una piccola libreria di metodi che tornano i figli di oggetti diversi per avere una casistica che copre gran parte delle esigenze mantenendo sempre lo stesso codice.

Per approfondimenti si veda:

Introduzione a C# 3.0
https://www.winfxitalia.com/articoli/netfx3.5/csharp3.aspx

#24 - Eliminare elementi in comune tra più liste con la clausola Except di LINQ
https://www.winfxitalia.com/script/24/Eliminare-Elementi-Comune-Liste-Clausola-Except-LINQ.aspx

#26 - Rendere univoci gli elementi di una lista con la clausola Distinct di LINQ
https://www.winfxitalia.com/script/26/Rendere-Univoci-Elementi-Lista-Clausola-Distinct-LINQ.aspx

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