Se è vero che i nuovi Dynamic Data hanno il potere di far risparmiare parecchio tempo, è anche vero l'approccio che utilizzano spesso e volentieri è, per così dire, un po' "ottimistico". Con questo intendo che la molte volte le condizioni "ideali" che essi presuppongono non si verificano perciò bisogna lavorare un po' per piegarli alla propria volontà.

E' il caso ad esempio di tabelle che contengono righe che devono essere filtrate in base al contesto in cui ci si trova. Pensiamo a una tabella che contenga i record di diversi utenti oppure di diverse aziende. E' obbiettivamente improbabile che chiunque possa vedere i record di un altro utente, ivi compresi magari quelli dell'amministratore delegato. E così anche nel caso di record di diverse aziende, in un sistema in ASP (Application Service Provider) questo modo di lavorare è inammissibile.

Il fatto è che queste condizioni non sono proprio previste. Non c'è un modo ufficiale per ottenere un funzionamento adeguato a casi del genere. Bisogna invece scendere un po' più in pofondità nei template ed agire in almeno quattro punti

  1. Il template di pagina List.aspx ed eventualmente ListDetails.aspx
  2. Il template di pagina Insert.aspx
  3. Il template di field ForeignKey_Edit.ascx
  4. Lo user control FilterUserControl.ascx

Poniamo ad esempio di voler introdurre un filtro per un ipotetico campo OrganizationID che identifica l'ente cui è associato il record. Dato per acquisito che esista una variabile in Sessione che indica la CurrentOrganization (incapsulata in un opportuno SessionManager), ovvero l'ente cui è associato l'utente loggato, ecco come dovremmo procedere.

Template di Pagina List.aspx

Il punto migliore dove intervenire in questo caso è l'evento OnSelecting della LinqDataSource. Questo evento viene sollevato un attimo prima di eseguire la query che restituirà i risultati da mostrare nella lista. Ecco il codice da inserire:

   1: protected void GridDataSource_Selecting(object sender, LinqDataSourceSelectEventArgs e)
   2: {
   3:     if (e.WhereParameters.ContainsKey("OrganizationID"))
   4:         e.WhereParameters["OrganizationID"] = 
   5:             SessionManager.CurrentOrganization;
   6:     else
   7:         e.WhereParameters.Add(
   8:             "OrganizationID", 
   9:             SessionManager.CurrentOrganization);
  10: }

Questo codice non fa altro che cercare un parametro OrganizationID e valorizzarlo con la sessione. Qualora nei parametri di selezione questo attributo sia già presente andiamo a sostituire il valore con la session. Questo fa si che il criterio venga applicato arbitrariamente a tutte le query.

Il template di pagina Insert.aspx

Per questo template la soluzione è ancora più semplice. In effetti è sufficiente inserire nel markup un Parameter che aggiunga il valore nella Insert:

   1: <asp:LinqDataSource ID="DetailsDataSource" runat="server" EnableInsert="true">
   2:     <InsertParameters>
   3:         <asp:SessionParameter Name="OrganizationID" SessionField="CurrentOrganization" Type="Int32" />
   4:     </InsertParameters>
   5: </asp:LinqDataSource>

Il template di field ForeignKey

Questa è probabilmente la parte più complessa. Tra i field template ne esite uno che è deputato a mostrare gli elementi che fanno parte di una Foreign Key. Si tratta di una DropDownList che viene bindata ai valori della tabella relazionata. Purtroppo anche in una soluzione di questo tipo esiste sempre una situazione mista. Alcune chiavi devono subire il filtro per OrganizationID mentre altre no. Occorre quindi creare un field nuovo che io ho chiamato FilteredForeignKey. In particolare quello da sistemare è il FilteredForeignKey_Edit. Per creare il field bisogna duplicare e rinominare quello già esistente.

   1: /// <summary>
   2: /// Provides dictionary access to all data in the current row.
   3: /// </summary>
   4: /// <param name="dictionary">The dictionary that contains all the new values.</param>
   5: protected override void ExtractValues(IOrderedDictionary dictionary)
   6: {
   7:     // If it's an empty string, change it to null
   8:     string val = DropDownList1.SelectedValue;
   9:     if (val == String.Empty)
  10:         val = null;
  11:  
  12:     ExtractForeignKey(dictionary, val);
  13:  
  14:     if (dictionary.Contains("OrganizationID"))
  15:         dictionary["OrganizationID"] = SessionManager.CurrentOrganization;
  16: }
  17:  
  18: /// <summary>
  19: /// Populates the list control filtered.
  20: /// </summary>
  21: /// <param name="listControl">The list control.</param>
  22: protected void PopulateListControlFiltered(ListControl listControl)
  23: {
  24:     MetaTable table = this.ForeignKeyColumn.ParentTable;
  25:     IQueryable query = table.GetQuery();
  26:     query = query.Where("OrganizationID == @0", SessionManager.CurrentOrganization);
  27:  
  28:     if (table.SortColumn != null)
  29:         query = query.OrderBy(table.SortColumn.Name);
  30:  
  31:     foreach (object obj2 in query)
  32:     {
  33:         string displayString = table.GetDisplayString(obj2);
  34:         string primaryKeyString = table.GetPrimaryKeyString(obj2);
  35:         listControl.Items.Add(new ListItem(displayString, primaryKeyString.TrimEnd(new char[0])));
  36:     }
  37: }

Dobbiamo agire in due punti: Il primo dove i controlli sono filtrati. Occorre qui sostituire il metodo di default (PopulateListControl) e crearne uno proprio che ho chiamato PopulateListControlFiltered. In secondo luogo bisogna assicurarsi che quando il valore viene estratto l'id dell'ente sia correttamente imputato. Lo user control dovrà poi essere associato ad una proprietà per mezzo dell'attributo UIHint.

Lo user control FilterUserControl.ascx

L'ultimo passo serve ad assicurarsi che nemmeno le DropDownList che filtrano le liste siano un punto in cui trovare record che non si dovrebbero vedere. Qui l'intervento è sul controllo FilterUserControl.ascx. Il metodo da applicare è esattamente lo stesso del precedente esempio salvo che dato che non dobbiamo editare allora il corrispondente ExtractValues non è necessario.

Arrivato a questo punto devo far notare che i miei esempi si appoggiano alla libreria System.Linq.Dynamic che trovate all'interno degli esempi di Linq2SQL. Una volta apportate le modifiche che ho appena illustrato sarete in grado di aprire qualche nuova porta all'uso di questi comodi controlli. Chiaro che detto questo va precisato che per poterlo fare bisogna in qualche modo piegare il data model a questa esigenza. Infatti se non avete il campo OrganizationID su ogni tabella allora siete punto e a capo...


Commenti (8) -

# | Elisa | 25.06.2009 - 20.22

Ciao Andrea,
ho seguito i tuoi video sui Dynamic Data, utilissimi! Smile
Così ho iniziato a lavorarci su, ma adesso mi sono un attimo bloccata perchè mi sono trovata nel caso qui sopra.
Ho seguito i tuoi step, tutto bene, a parte il fatto che non ho capito la libreria System.Linq.Dinamic dove si può trovare...gli esempi di Linq2SQL a cui ti riferisci dove si trovano?
grazie mille

# | Andrea Boschin | 26.06.2009 - 09.07

In questo post trovi i linq che ti permettono di scaricare gli esempi che contengono il sorgente. Dovrai includerlo nel tuo progetto e compilarlo.

http://weblogs.asp.net/scottgu/archive/2008/01/07/dynamic-linq-part-1-using-the-linq-dynamic-query-library.aspx

# | Elisa | 07.07.2009 - 19.54

Ciao Andrea,
grazie mille, infine ero riuscita anch'io a trovarli proprio su quel sito cercando in internet.
Ti chiedo l'ultima cosa...ho un problema sulla modifica al FilterUserControl: nel codice ho aggiunto la funzione PopulateListControlFiltered, ma non viene visto il this.ForeignKeyColumn perchè nel System.Web.DynamicData.FilterUserControlBase non c'è...Ho sbagliato qualcosa?
grazie ancora

# | Around and About .NET World | 14.07.2009 - 20.22

Filtri sulle pagine Dynamic Data

# | Around and About .NET World | 14.07.2009 - 20.22

Per impostazione predefinita, un sito Dynamic Data mostra tutte le informazioni contenute nelle tabelle

# | Francesco | 16.07.2009 - 02.05

if String.IsNullOrEmpty(val) {}

# | Davide | 21.08.2009 - 03.53

Ciao Andrea,

ho letto con molta curiosit&#224; questo tuo post.
Nel provare a seguire le tue indicazioni ho riscontrato un problema.
La funzione PopulateListControlFiltered che tu hai scritto mi da degli errori in compilazione.
Il metodo Where non sembra esistere per l'oggetto query e nemmeno l'OrderBy.

Mi sono perso dei pezzi per strada?

Grazie mille,
Davide

# | Andrea Boschin | 21.08.2009 - 04.00

Devi includere la libreria System.Linq.Dynamic come spiegato in coda all'articolo.

Aggiungi Commento