Nessun risultato. Prova con un altro termine.
Guide
Notizie
Software
Tutorial

ASP.NET 4 e ClientIDMode, migliora il rapporto tra Web Forms e JavaScript

Predire l'identificativo dei web server control negli script lato client con ASP.NET 4
Predire l'identificativo dei web server control negli script lato client con ASP.NET 4
Link copiato negli appunti

Presentando le novità di ASP.NET 4.0, abbiamo accennato al fatto che è stata introdotta una nuova proprietà per i web server control che prende il nome di ClientIDMode. Questo articolo ha lo scopo di descrivere la proprietà ClientIDMode e di fornire degli esempi che servano a chiarirne l'utilizzo, con il fine di mostrare come identificare, finalmente senza ambiguità, i web server control negli script lato client.

In ASP.NET ogni web server control è rappresentato da un proprio markup, ad esempio, una casella di testo è rappresentata dal markup asp:TextBox. Affinché sia possibile rappresentare visivamente un qualsiasi controllo server in un browser è necessario trasformare il markup del controllo in un markup HTML comprensibile al browser. Ad esempio, l'elemento asp:TextBox viene trasformato nell'elemento input.

Ogni controllo server incluso in una web form deve possedere un identificatore che lo individua univocamente in modo tale che nel momento in qui questi viene rappresentato visivamente nella pagina non si vengono a creare conflitti con altri controlli presenti nella pagina stessa.

ASP.NET mette a disposizione un meccanismo per assicurarsi che questo avvenga ma fino alla versione 3.5 poteva risultare assai complicato predire il valore che avrebbe assunto l'identificativo di un controllo sul lato client e per riuscirvi si doveva ricorrere a stratagemmi vari. Con la versione 4 è stata introdotta una nuova proprietà che prende il nome di ClientIDMode che consente di indicare l'algoritmo di generazione dell'identificativo del controllo e conseguentemente di predire con estrema semplicità il valore che esso assumerà sul lato client.

Un po' di teoria

I web server control hanno tre proprietà che aiutano a identificarli quali:

Proprietà Descrizione
ID rappresenta l'identificativo del controllo che viene specificato dal programmatore dichiarativamente nel markup del controllo o da codice
ClientID non può essere specificata dal programmatore ma viene generata a runtime dal .NetFramework. Serve a identificare univocamente la rappresentazione html del controllo server. Viene usata negli script lato client. Dalla versione 4 di ASP.NET possiamo decidere, attraverso la proprietà ClientIDMode, l'algoritmo di generazione di questa proprietà e quindi predire il suo valore
UniqueID anche questa non può essere specificata dal programmatore ma viene generata a runtime dal .NetFramework. Serve a identificare univocamente il controllo lato server. Viene usata raramente e solo lato server

Queste tre proprietà a volte assumono lo stesso valore, altre volte assumono valori diversi e si può fare confusione su quando e come usarle.

Quando il controllo non si trova dentro ad altri controlli, allora ASP.NET usa il valore della proprietà ID per valorizzare anche le proprietà UniqueID e ClientID. In questo contesto la proprietà ClientIDMode non avrà alcun effetto. Pensiamo, ad esempio, ad una casella di testo dentro una pagina Web:

<asp:TextBox ID="TextBox1" runat="server" Text="Testo" />

Quando la pagina sarà visualizzata nel browser le tre proprietà assumeranno i seguenti identici valori:

Proprietà Vaolre
ID TextBox1
ClientID TextBox1
UniqueID TextBox1

La faccenda si fa più complicata quando il controllo si trova all'interno di una gerarchia di controlli. Vediamo le differenze.

UniqueID

Nell'esempio precedente abbiamo visto che questa proprietà coincide con la proprietà ID quando il controllo non si trova all'interno di altri contenitori. Per contenitore si intende ogni elemento della pagina che dispone della proprietà ID.

Gli ID possono essere forniti dal programmatore ma possono anche essere assegnati automaticamente dal .Net Framework per quei controlli in cui non sono stati esplicitamente assegnati. Caso a parte sono le Master Page e le Content Page che non hanno una proprietà ID esplicita ma ASP.NET la assegna loro in fase di esecuzione.

Quando l'ID viene generato da ASP.NET sarà composti dall'alfanumerico ctl seguito da un numero a due cifre che indica l'ordine di visualizzazione dell'elemento all'interno della pagina HTML. Non è possibile che nella pagina Web ci siano più controlli con lo stesso UniqueID.

Quando il controllo si trova all'interno di un altro controllo lo UniqueID è composto dal valore della sua proprietà ID preceduta dal valore dello UniqueID del controllo padre. Le varie parti di uno UniqueID sono separate dal carattere dollaro ($).

Il controllo padre prende il ruolo di Naming Container perché genera uno spazio dei nomi per i suoi controlli figli il cui scopo è permettere la loro identificazione univoca.

Esempi di controlli che possono contenere altri controlli sono la GridView, la ListView, la FormView, il DetailView, il DataList ed il Repeater. Ovviamente possiamo avere situazioni in cui un controllo si trova all'interno di una gerarchia di controlli padre, in questo caso avremo degli UniqueID di grandi dimensioni. Come esempio pensiamo ad una casella di testo contenuta in una Content Page:

<%@ Page Title="" MasterPageFile="~/master1.master" AutoEventWireup="true" %>
<asp:Content ID="Content2" runat="Server"
             ContentPlaceHolderID="ContentPlaceHolder1">
  <asp:TextBox ID="TextBox1" runat="server" Text="Testo" />
</asp:Content>

Quando la pagina sarà visualizzata nel browser le tre proprietà assumeranno i seguenti valori:

Proprietà Vaolre
ID TextBox1
ClientID ctl00_ContentPlaceHolder1_ TextBox1
UniqueID ctl00$ContentPlaceHolder1$ TextBox1

Notiamo che la proprietà ClientID differisce dalla UniqueID per il solo carattere di separazione che invece di essere un dollaro ($)è un underscore (_).

Quando un controllo si trova in un data-bound control, ovvero in un controllo al quale è possibile associare una sorgente di dati, vengono create più istanze del controllo a runtime. Per distinguerle lo UniqueID sarà composto dallo UniqueID del data-bound control concatenato con l'ID del controllo figlio concatenato a sua volta da un valore numerico incrementale. Questo valore numerico ha lo scopo di fare distinzione tra le varie istanze del controllo.

Tipicamente non si usa mai la proprietà UniqueID perché per riferirsi ad un controllo lato server si può usare direttamente la proprietà ID qualora il controllo non si trovi in controlli padre diversamente si può usare il metodo FindControl applicato al controllo padre al quale passare il valore della proprietà ID del controllo figlio. Facciamo un esempio:

<asp:GridView ID="grvCustomer" runat="server" AutoGenerateColumns="False">
  <Columns>
    <asp:BoundField DataField="LastName" HeaderText="Cognome" />
    <asp:TemplateField HeaderText="Email">
      <ItemTemplate>
        <asp:TextBox ID="txtEmail" runat="server"
                     Text='<%# Bind("EmailAddress") %>' />
      </ItemTemplate>
    </asp:TemplateField>
  </Columns>
</asp:GridView>

Supponiamo di voler accedere alla casella di testo contenuta nella i-esima riga della GridView. Il codice che ci permette di raggiungere questo obiettivo è il seguente:

TextBox text1 = (TextBox) grvCustomer.items[i].FindControl("txtEmail");

Abbiamo usato il metodo FindControl della gridView e come parametro gli abbiamo passato la proprietà ID della casella di testo.

ClientID

Come precedentemente menzionato, questa proprietà serve a identificare univocamente la rappresentazione HTML del controllo server. Viene usata all'interno di script lato client realizzati con JavaScript.

Non è possibile assegnare un valore a questa proprietà, ma dalla versione 4 di ASP.NET è possibile specificare la modalità di generazione di questa proprietà e questo consente ai programmatori di predirne il valore. La proprietà ClientIDMode è definita nella classe Control, quindi si può applicare a tutti i web control compresa la pagina web stessa.

La proprietà ClientIDMode può assumere quattro valori distinti: AutoID, Static, Predictable, Inherit. Esaminiamoli nel dettaglio.

AutoID

Il ClientID viene generato come accadeva nelle versioni del .NET Framework precedenti alla 4 ovvero vengono concatenate la proprietà ClientID del controllo padre con la proprietà ID del controllo stesso e si ottiene una sintassi molto simile a quella dello UniqueID; l'unica differenza è rappresentata dal carattere usato per la concatenazione che non è il simobolo del dollaro ($) come per lo UniqueID ma l'underscore (_).

Quando i controlli server sono contenuti in naming container l'algoritmo AutoID può generare valori molto lunghi per il ClientID.

Static

Il ClientID assume lo stesso valore della proprietà ID senza alcuna modifica. Viene usato quando si ha la necessità di avere lo stesso valore per il ClientID in ogni possibile scenario in cui si può trovare il controllo.

Si può applicare solo quando c'è una sola istanza del controllo in tutta la pagina per evitare ambiguità, in quanto più elementi potrebbero assumenre lo stesso identificativo. Ciò può causare errori importanti di negli script lato client che accedono agli oggetti della pagina contando sull'univocità del ClientID.

Il valore Static è tipicamente usato per i controlli che si trovano dentro gli user control. Per chiarirci le idee costruiamo uno user control il cui scopo è valorizzare una casella di testo con il nome del capitano della squadra di calcio selezionata dall'utente in un menu a discesa.

Vediamo lo user control:

<%@ Control AutoEventWireup="true" %>
<script type="text/javascript">
  var capitani = new Array("Nessuno", "Totti", "Del Piero", "Zanetti", "Rocchi");
  function VisualizzaCapitano(x) {
    document.getElementById("lblCapitano").innerHTML = capitani[x];
  }
</script>
<asp:DropDownList ID="DropDownList1" runat="server"
                  onchange="VisualizzaCapitano(this.selectedIndex);">
  <asp:ListItem Value="Seleziona una squadra" />
  <asp:ListItem Value="Roma" />
  <asp:ListItem Value="Juve" />
  <asp:ListItem Value="Inter" />
  <asp:ListItem Value="Lazio" />
</asp:DropDownList>
<br />
<asp:Label ID="lblCapitano" runat="server" ClientIDMode="Static" />

Lo user control è composto da un menu a discesa, un'etichetta e una funzione JavaScript che valorizza l'etichetta con il nome del capitano della squadra selezionata nel menu a discesa. Notiamo l'uso del metodo getElementByID nella funzione e soprattutto notiamo che in tale metodo passiamo il valore "lblCapitano" che rappresenta il valore della proprietà ID della casella di testo. Siamo sicuri che ovunque verrà collocato lo user control il valore dell'ID della casella di testo sarà sempre "lblCapitano". Abbiamo tale certezza perché abbiamo impostato la proprietà ClientIDMode della casella di testo a Static.

Adesso mettiamo lo user control dentro una content page:

<%@ Page Title="" MasterPageFile="~/master1.master" AutoEventWireup="true" %>
<%@ Register Src="Capitani.ascx" TagName="Capitani" TagPrefix="uc1" %>
<asp:Content ID="Content1" runat="Server"
             ContentPlaceHolderID="ContentPlaceHolder1">
  <uc1:Capitani ID="Capitani1" runat="server" />
</asp:Content>

Se visualizziamo la pagina in un browser possiamo constatare che la proprietà ClientID dell'etichetta assume il valore "lblCapitano"; se non avessimo valorizzato la sua proprietà ClientIDMode allora la sua proprietà ClientID avrebbe assunto il valore ctl00_ContentPlaceHolder1_Capitani1_lblCapitano che non avremmo potuto predire al momento di implementazione dello user control.

Predictable

Questa opzione va usata su controlli contenuti in data-bound control che, come sappiamo, possono ospitare dei controlli figli che assumono molteplici istanze in fase di esecuzione. Per distinguere le varie istanze si usa la proprietà ClientIDRowSuffix alla quale viene assegnato uno o più campi della sorgente dati del controllo.

Se la proprietà clientIDRowSuffix non viene valorizzata dal programmatore, ASP.NET la valorizzerà con un valore sequenziale pari al numero della riga in cui si trova il controllo figlio. Riprendiamo l'esempio proposto in precedenza sulla gridView e aggiungiamo le proprietà ClientIDMode e ClientIDRowSuffix:

<asp:Content ID="Content1" runat="Server"
             ContentPlaceHolderID="ContentPlaceHolder1">
  <asp:GridView ID="grvCustomer" runat="server" AutoGenerateColumns="False"
                ClientIDMode="Predictable" ClientIDRowSuffix="CustomerID" > 
    <Columns>
      <asp:BoundField DataField="LastName" HeaderText="Cognome" />
      <asp:TemplateField HeaderText="Email">
        <ItemTemplate>
          <asp:TextBox ID="txtEmail" runat="server"
                       Text='<%# Bind("EmailAddress") %>' />
        </ItemTemplate>
      </asp:TemplateField>
    </Columns>
  </asp:GridView>
</asp:Content>

Se visualizziamo la pagina in un browser possiamo verificare che la casella di testo assumerà i valori:

ContentPlaceHolder1_grvCustomer_txtEmail_235
ContentPlaceHolder1_grvCustomer_txtEmail_29873
ContentPlaceHolder1_grvCustomer_txtEmail_19
...

ASP.NET omette tutti i naming container auto-generati (ctlyy) quando si usa il valore predictable. Inoltre che la proprietà ClientIDRowSuffix è stata valorizzata con il campo chiave della sorgente dati con lo scopo di sincerarsi che le varie istanze avranno tutte identificativi univoci.

Inherit

Con questa opzione si eredita il ClientIDMode del controllo padre. Questo è il valore di default di un controllo. Il valore di default di una pagina invece è AutoID. Questo significa che se non viene indicata la proprietà ClientIDMode per la pagina allora di default viene assunto il valore AutoID e se non viene indicata la proprietà ClientIDMode per i controlli della pagina allora visto che per default assumono il valore inherit allora ereditano il valore AutoID della pagina che è il contenitore padre più alto nella gerarchia.

Possiamo impostare il valore della proprietà ClientIDMode sia a livello di sito che a livello di pagina che a livello di singolo controllo.

Per agire sulla proprietà a livello di sito dobbiamo intervenire nel Web.config:

<pages ClientIDMode="Static">

Per intervenire sulla proprietà a livello di pagina dobbiamo modificare la direttiva @Page della singola pagina:

<%@ Page Language="C#" CodeFile="Default.aspx.cs" Inherits="_Default"
         ClientIDMode = "Static" %>

I vantaggi della proprietà ClientIDMode

Prima della versione 4 di ASP.NET era molto complicato, se non impossibile in alcuni casi, predire il valore della proprietà ClientID quindi per poter identificare un controllo in script lato client era necessario ricorrere all'impiego di codice inline. Ecco due esempi:

#<%= Label1.ClientID %>
{
  font-size: 10px;
}
<script type="text/javascript">
  var txt = document.getElementById('<%=TextBox1.ClientID%>');
  txt.focus();
</script>

Nel primo esempio ci troviamo all'interno di una istruzione CSS contenuta nella sezione Head di una pagina web .L'istruzione ha lo scopo di impostare a 10 pixel il font della etichetta con ID uguale a Label1. Visto che ci troviamo lato client per poter identificare univocamente la l'etichetta dobbiamo usare la proprietà ClientID e possiamo farlo solo ricorrendo ad una riga di codice inline. L'alternativa sarebbe sostituire la linea di codice con il valore assunto dalla proprietà ClientID.

Nel secondo esempio ci troviamo nella sezione Head di una pagina web all'interno di una funzione javascript. La funzione dichiara una variabile alla quale assegna il valore della proprietà ClientID di una casella di testo con ID uguale a TextBox1. Vediamo che viene usato il metodo getElemetById al quale bisognerebbe passare il valore della proprietà ClientID. Purtroppo non conosciamo tale valore e quindi siamo costretti a ricorrere a del codice in linea che viene eseguito lato server e restituito alla pagina.

Chi si intende di ingegneria del software e più in particolare di architettura del software sa che l'uso di codice inline va contro molte linee guida sulla progettazione del software orientato agli oggetti.

Innanzi tutto il codice inline introduce lo svantaggio di accorpare la presentazione dei dati alla logica di accesso degli stessi e questo va contro il design pattern MVC che ci insegna a separare la presentazione dell'informazione dalla logica di accesso e trasformazione dei dati. Inoltre il codice inline va control il code-behind fortemente voluto in ASP.NET. La conseguenza è che la manutenzione del codice risulta più gravosa perché il programmatore si trova a dover operare sia sul sorgente associato alla pagina che sulla pagina stessa. Anche il debug diventa più problematico. In poche parole il codice inline è da evitare e adesso,con ASP.NET 4, possiamo evitarlo perchè possiamo predire il valore del ClientID.

Simone Moretti è un architetto ASP.NET Senior laureato in Informatica. Si dedica da quasi un decennio alla realizzazione di applicazioni Web con tecnologia ASP.NET/C#. Attualmente lavora presso la pubblica amministrazione per conto di Wizards Consulting SpA [www.wizardsgroup.it].


Ti consigliamo anche