Nomenclatura de los identificadores de control en las páginas de contenido (C#)

por Scott Mitchell

Descargar PDF

Muestra cómo los controles ContentPlaceHolder actúan como un contenedor de nomenclatura y, por tanto, hacen que el trabajo mediante programación con un control sea difícil (a través de FindControl). Examina este problema y soluciones alternativas. También se describe cómo acceder mediante programación al valor ClientID resultante.

Introducción

Todos los controles de servidor ASP.NET incluyen una propiedad ID que identifica de forma única el control y es el medio por el que se accede al control mediante programación en la clase de código subyacente. Del mismo modo, los elementos de un documento HTML pueden incluir un atributo id que identifica de forma única el elemento; estos valores id se suelen usar en el script del lado cliente para hacer referencia mediante programación a un elemento HTML determinado. Dado esto, puede suponer que cuando se representa un control de servidor ASP.NET en HTML, su valor ID se usa como valor id del elemento HTML representado. Esto no es necesariamente el caso porque, en determinadas circunstancias, un solo control con un solo valor ID puede aparecer varias veces en el marcado representado. Considere un control GridView que incluya un TemplateField con un control Web de etiqueta con un valor ID de ProductName. Cuando GridView está enlazado a su origen de datos en tiempo de ejecución, esta etiqueta se repite una vez por cada fila de GridView. Cada etiqueta representada necesita un valor único id.

Para controlar estos escenarios, ASP.NET permite que determinados controles se denoten como contenedores de nomenclatura. Un contenedor de nomenclatura actúa como un nuevo espacio de nombres ID. Los controles de servidor que aparecen dentro del contenedor de nomenclatura tienen su valor id representado prefijado con el ID del control de contenedor de nomenclatura. Por ejemplo, las clases GridView y GridViewRow son contenedores de nomenclatura. Por lo tanto, un control Label definido en un TemplateField GridView con ID ProductName recibe un valor representado id de GridViewID_GridViewRowID_ProductName. Dado que GridViewRowID es único para cada fila GridView, los valores resultantes id son únicos.

Nota:

La INamingContainer interfaz se usa para indicar que un control de servidor de ASP.NET determinado debe funcionar como un contenedor de nomenclatura. La INamingContainer interfaz no detalla ningún método que el control de servidor debe implementar; en su lugar, se usa como marcador. Al generar el marcado representado, si un control implementa esta interfaz, el motor de ASP.NET prefija automáticamente su valor ID a los valores de atributo id representados de sus descendientes. Este proceso se describe con más detalle en el paso 2.

Los contenedores de nomenclatura no solo cambian el valor del atributo id representado, sino que también afectan a cómo se puede hacer referencia al control mediante programación desde la clase de código subyacente de la página de ASP.NET. El método FindControl("controlID") se usa normalmente para hacer referencia mediante programación a un control web. Sin embargo, FindControl no penetra en los contenedores de nomenclatura. Por lo tanto, no puede usar directamente el método Page.FindControl para hacer referencia a controles dentro de GridView u otro contenedor de nomenclatura.

Como ya habrá supuesto, las páginas maestras y ContentPlaceHolders se implementan como contenedores de nomenclatura. En este tutorial se examina cómo afectan las páginas maestras a los valores id de elementos HTML y formas de hacer referencia mediante programación a controles web dentro de una página de contenido mediante FindControl.

Paso 1: Agregar nuevas páginas ASP.NET

Para demostrar los conceptos descritos en este tutorial, vamos a agregar una nueva página de ASP.NET a nuestro sitio web. Cree una nueva página de contenido denominada IDIssues.aspx en la carpeta raíz y enlazarla a la página maestra Site.master.

Add the Content Page IDIssues.aspx to the Root Folder

Ilustración 01: Agregar la página de contenido IDIssues.aspx a la carpeta raíz

Visual Studio crea automáticamente un control Content para cada uno de los cuatro ContentPlaceHolders de la página maestra. Como se indicó en el tutorial ContentPlaceHolders múltiples y contenido de predeterminado, si un control de contenido no está presente, se emite el contenido ContentPlaceHolder predeterminado de la página maestra en su lugar. Dado que QuickLoginUI y LeftColumnContent ContentPlaceHolders contienen marcado predeterminado adecuado para esta página, continúe y quite sus controles de contenido correspondientes de IDIssues.aspx. En este momento, el marcado declarativo de la página de contenido debe tener un aspecto similar al siguiente:

<%@ Page Language="C#" MasterPageFile="~/Site.master" AutoEventWireup="true" CodeFile="IDIssues.aspx.cs" Inherits="IDIssues" Title="Untitled Page" %>

<asp:Content ID="Content1" ContentPlaceHolderID="head" Runat="Server">
</asp:Content>

<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" Runat="Server">
</asp:Content>

Recuerde que, en el tutorial Especificar el título, las etiquetas meta y otros encabezados HTML en la página maestra, creamos una clase de página base personalizada (BasePage) que genera el título de la página si no se establece de manera explícita. Para que la página IDIssues.aspx utilice esta funcionalidad, la clase de código subyacente de la página debe derivar de la clase BasePage (en lugar de System.Web.UI.Page). Modifique la definición de la clase de código subyacente para que tenga el siguiente aspecto:

public partial class IDIssues : BasePage
{
}

Por último, actualice el archivo Web.sitemap para incluir una entrada para esta lección. Agregue un elemento <siteMapNode> y establezca sus atributos title y url en "Problemas de nomenclatura del identificador de control" y ~/IDIssues.aspx, respectivamente. Después de realizar esta adición, el marcado del archivo Web.sitemap debe ser similar al siguiente:

<?xml version="1.0" encoding="utf-8" ?>
<siteMap xmlns="http://schemas.microsoft.com/AspNet/SiteMap-File-1.0" >
 <siteMapNode url="~/Default.aspx" title="Home">
 <siteMapNode url="~/About.aspx" title="About the Author" />
 <siteMapNode url="~/MultipleContentPlaceHolders.aspx" title="Using Multiple ContentPlaceHolder Controls" />
 <siteMapNode url="~/Admin/Default.aspx" title="Rebasing URLs" />
 <siteMapNode url="~/IDIssues.aspx" title="Control ID Naming Issues" />
 </siteMapNode>
</siteMap>

Como se muestra en la ilustración 2, la nueva entrada de mapa de sitio de Web.sitemap se refleja inmediatamente en la sección Lecciones de la columna izquierda.

The Lessons Section Now Includes a Link to

Ilustración 02: la sección Lecciones ahora incluye un vínculo a "Problemas de nomenclatura del identificador de control"

Paso 2: Examinar los cambios representadosID

Para comprender mejor las modificaciones que realiza el motor de ASP.NET en los valores id representados de los controles de servidor, vamos a agregar algunos controles web a la página IDIssues.aspx y, a continuación, veremos el marcado representado enviado al explorador. En concreto, escriba el texto "Escriba su edad:" seguido de un control Web TextBox. Más abajo en la página, agregue un control Web Button y un control Web Label. Establezca las propiedades ID y Columns de TextBox en Age y 3, respectivamente. Establezca las propiedades Text y ID del botón en “Enviar” y SubmitButton. Desactive la propiedad Text de etiqueta y establezca su ID en Results.

En este momento, el marcado declarativo del control debe tener un aspecto similar al siguiente:

<p>
 Please enter your age:
 <asp:TextBox ID="Age" Columns="3" runat="server"></asp:TextBox>
</p>
<p>
 <asp:Button ID="SubmitButton" runat="server" Text="Submit" />
</p>
<p>
 <asp:Label ID="Results" runat="server"></asp:Label>
</p>

En la ilustración 3 se muestra la página cuando se ve a través del diseñador de Visual Studio.

The Page Includes Three Web Controls: a TextBox, Button, and Label

Ilustración 03: la página incluye tres controles web: un TextBox, un botón y una etiqueta (Haga clic para ver la imagen de tamaño completo)

Visite la página a través de un explorador y, a continuación, vea el origen HTML. Como se muestra en el marcado siguiente, los valores id de los elementos HTML de los controles Web TextBox, botón y etiqueta son una combinación de los valores ID de los controles Web y los valores ID de los contenedores de nomenclatura de la página.

<p>
 Please enter your age:
 <input name="ctl00$MainContent$Age" type="text" size="3" id="ctl00_MainContent_Age" />
</p>
<p>

 <input type="submit" name="ctl00$MainContent$SubmitButton" value="Submit" id="ctl00_MainContent_SubmitButton" />
</p>
<p>
 <span id="ctl00_MainContent_Results"></span>
</p>

Como se indicó anteriormente en este tutorial, tanto la página maestra como sus ContentPlaceHolders actúan como contenedores de nomenclatura. Por consiguiente, ambos contribuyen a los valores representados ID de sus controles anidados. Tome el atributo id de TextBox, por ejemplo: ctl00_MainContent_Age. Recuerde que el valor ID del control de TextBox era Age. Lleva como prefijo el valor del ID control ContentPlaceHolder, MainContent. Además, este valor tiene como prefijo el valor ID de la página maestra, ctl00. El efecto neto es un valor de atributo id que consta de los valores ID de la página maestra, el control ContentPlaceHolder y el propio TextBox.

En la ilustración 4 se muestra este comportamiento. Para determinar la representación id de Age TextBox, comience con el valor ID del control TextBox, Age. A continuación, trabaje hacia arriba en la jerarquía de controles. En cada contenedor de nomenclatura (esos nodos de color melocotón), prefije el actual representado id con el contenedor de nomenclatura id.

The Rendered id Attributes are Based On the ID Values of the Naming Containers

Ilustración 04: los atributos id representados se basan en los valores ID de los contenedores de nomenclatura

Nota:

Como hemos explicado, la parte ctl00 del atributo id representado constituye el valor ID de la página maestra, pero es posible que se pregunte cómo se produjo este valor ID. No se ha especificado en ninguna parte de nuestra página maestra o de contenido. La mayoría de los controles de servidor de una página de ASP.NET se agregan explícitamente a través del marcado declarativo de la página. El control MainContent ContentPlaceHolder se especificó explícitamente en el marcado de Site.master; el Age TextBox se definió como marcado de IDIssues.aspx. Podemos especificar los valores ID de estos tipos de controles a través del ventana Propiedades o de la sintaxis declarativa. Otros controles, como la propia página maestra, no se definen en el marcado declarativo. Por lo tanto, sus valores ID deben generarse automáticamente para nosotros. El motor de ASP.NET establece los valores ID en tiempo de ejecución para esos controles cuyos identificadores no se han establecido explícitamente. Usa el patrón ctlXX de nomenclatura, donde XX es un valor entero que aumenta secuencialmente.

Dado que la propia página maestra actúa como contenedor de nomenclatura, los controles web definidos en la página maestra también tienen valores de atributo id representados modificados. Por ejemplo, la etiqueta DisplayDate que agregamos a la página maestra en el tutorial Crear de un diseño para todo el sitio con páginas maestras tiene el siguiente marcado representado:

<span id="ctl00_DateDisplay">current date</span>

Tenga en cuenta que el atributo id incluye el valor ID de la página maestra (ctl00) y el valor ID del control Web de etiqueta (DateDisplay).

Paso 3: hacer referencia mediante programación a controles web a través deFindControl

Cada control de servidor ASP.NET incluye un método FindControl("controlID") que busca los descendientes del control para un control denominado controlID. Si se encuentra este control, se devuelve; si no se encuentra ningún control coincidente, FindControl devuelve null.

FindControl es útil en escenarios en los que necesita acceder a un control, pero no tiene una referencia directa a él. Cuando se trabaja con controles web de datos como GridView, por ejemplo, los controles dentro de los campos de GridView se definen una vez en la sintaxis declarativa, pero en tiempo de ejecución se crea una instancia del control para cada fila de GridView. Por lo tanto, existen los controles generados en tiempo de ejecución, pero no tenemos una referencia directa disponible en la clase de código subyacente. Como resultado, es necesario usar FindControl para trabajar mediante programación con un control específico dentro de los campos de GridView. (Para obtener más información sobre el uso de FindControl para acceder a los controles dentro de las plantillas de un control web de datos, consulte Formato personalizado basado en datos). Este mismo escenario se produce cuando se agregan controles web dinámicamente a un formulario web, un tema descrito en Crear interfaces de usuario de entrada de datos dinámicos.

Para ilustrar el uso del método FindControl para buscar controles dentro de una página de contenido, cree un controlador de eventos para el evento Click de SubmitButton. En el controlador de eventos, agregue el siguiente código, que hace referencia mediante programación a TextBox Age y Etiqueta Results mediante el método FindControl y, a continuación, muestra un mensaje en Results en función de la entrada del usuario.

Nota:

Por supuesto, no es necesario usar FindControl para hacer referencia a los controles de etiqueta y TextBox para este ejemplo. Podríamos hacer referencia a ellos directamente a través de sus valores de propiedad ID. Uso FindControl aquí para ilustrar lo que sucede cuando se usa FindControl desde una página de contenido.

protected void SubmitButton_Click(object sender, EventArgs e)
{
    Label ResultsLabel = FindControl("Results") as Label;
    TextBox AgeTextBox = Page.FindControl("Age") as TextBox;

    ResultsLabel.Text = string.Format("You are {0} years old!", AgeTextBox.Text);
}

Aunque la sintaxis usada para llamar al método FindControl difiere ligeramente en las dos primeras líneas de SubmitButton_Click, son semánticamente equivalentes. Recuerde que todos los controles de servidor ASP.NET incluyen un método FindControl. Esto incluye la clase Page, de la que todas las clases de código subyacente ASP.NET deben derivarse. Por lo tanto, llamar FindControl("controlID") a es equivalente a llamar a Page.FindControl("controlID"), suponiendo que no haya reemplazado el método FindControl en la clase de código subyacente o en una clase base personalizada.

Después de escribir este código, visite la página IDIssues.aspx a través de un explorador, escriba su edad y haga clic en el botón "Enviar". Al hacer clic en el botón "Enviar" se genera un elemento NullReferenceException (vea la ilustración 5).

A NullReferenceException is Raised

Ilustración 05: se genera un NullReferenceException (haga clic para ver la imagen a tamaño completo)

Si establece un punto de interrupción en el SubmitButton_Click controlador de eventos, verá que ambas llamadasFindControl devuelven un null valor. NullReferenceException se genera cuando intentamos acceder a la propiedad TextBox Age de Text.

El problema es que Control.FindControl solo busca los descendientes de Controlque están en el mismo contenedor de nomenclatura. Dado que la página maestra constituye un nuevo contenedor de nomenclatura, una llamada a Page.FindControl("controlID") nunca impregna el objeto ctl00 de la página maestra. (Consulte la ilustración 4 para ver la jerarquía de controles, que muestra el objeto Page como elemento primario del objeto ctl00 de la página maestra). Por lo tanto, no se encuentran el Label Results y TextBox Age y ResultsLabel y AgeTextBox se asignan valores de null.

Hay dos soluciones alternativas a este desafío: podemos explorar en profundidad, un contenedor de nomenclatura a la vez, para el control adecuado; o podemos crear nuestro propio método FindControl que permee los contenedores de nomenclatura. Vamos a examinar cada una de estas opciones.

Exploración en profundidad del contenedor de nomenclatura adecuado

Para usar FindControl para hacer referencia a etiqueta Results o TextBox Age, es necesario llamar a FindControl desde un control anterior en el mismo contenedor de nomenclatura. Como se muestra en la ilustración 4, el control ContentPlaceHolder MainContentes el único antecesor de Results o Age que está dentro del mismo contenedor de nomenclatura. En otras palabras, llamar al método FindControl desde el control MainContent, como se muestra en el fragmento de código siguiente, devuelve correctamente una referencia a los controles Results o Age.

Label ResultsLabel = MainContent.FindControl("Results") as Label;
TextBox AgeTextBox = MainContent.FindControl("Age") as TextBox;

Sin embargo, no podemos trabajar con ContentPlaceHolder MainContent desde la clase de código subyacente de la página de contenido con la sintaxis anterior porque ContentPlaceHolder se define en la página maestra. En su lugar, tenemos que usar FindControl para obtener una referencia a MainContent. Reemplace el código del controlador de eventos SubmitButton_Click por las siguientes modificaciones:

protected void SubmitButton_Click(object sender, EventArgs e)
{
    ContentPlaceHolder MainContent = FindControl("MainContent") as ContentPlaceHolder;

    Label ResultsLabel = MainContent.FindControl("Results") as Label;
    TextBox AgeTextBox = MainContent.FindControl("Age") as TextBox;

    ResultsLabel.Text = string.Format("You are {0} years old!", AgeTextBox.Text);
}

Si visita la página a través de un explorador, escriba su edad y haga clic en el botón "Enviar", se genera un NullReferenceException. Si establece un punto de interrupción en el controlador de eventos SubmitButton_Click, verá que esta excepción se produce al intentar llamar al métodoFindControl objeto MainContent. El objeto MainContent es igual a null porque el método FindControl no puede encontrar un objeto denominado "MainContent". La razón subyacente es la misma que con los controles de etiqueta Results y TextBox Age: FindControl inicia su búsqueda desde la parte superior de la jerarquía de controles y no penetra en los contenedores de nomenclatura, pero ContentPlaceHolder MainContent está dentro de la página maestra, que es un contenedor de nomenclatura.

Antes de poder usar FindControl para obtener una referencia a MainContent, primero necesitamos una referencia al control de página maestra. Una vez que tengamos una referencia a la página maestra, podemos obtener una referencia a MainContent ContentPlaceHolder a través FindControl de y, desde allí, referencias a Results Label y Age TextBox (de nuevo, mediante FindControl). ¿Pero cómo obtenemos una referencia a la página maestra? Al inspeccionar los id atributos del marcado representado, es evidente que el valor de ID la página maestra es ctl00. Por lo tanto, podríamos usar Page.FindControl("ctl00") para obtener una referencia a la página maestra y, a continuación, usar ese objeto para obtener una referencia a MainContent, etc. En el fragmento de código siguiente se muestra esta lógica:

// Get a reference to the master page
MasterPage ctl00 = FindControl("ctl00") as MasterPage;

// Get a reference to the ContentPlaceHolder
ContentPlaceHolder MainContent = ctl00.FindControl("MainContent") as ContentPlaceHolder;

// Reference the Label and TextBox controls
Label ResultsLabel = MainContent.FindControl("Results") as Label;
TextBox AgeTextBox = MainContent.FindControl("Age") as TextBox;

Aunque este código funcionará sin duda, supone que la página maestra generada ID automáticamente siempre será ctl00. Nunca es buena idea realizar suposiciones sobre los valores generados automáticamente.

Afortunadamente, se puede acceder a una referencia a la página maestra a través de la propiedad Page de la clase Master. Por lo tanto, en lugar de tener que usar FindControl("ctl00") para obtener una referencia de la página maestra para tener acceso a ContentPlaceHolder MainContent, podemos usar Page.Master.FindControl("MainContent") en su lugar. Actualice el control de eventos SubmitButton_Click con el siguiente código:

protected void SubmitButton_Click(object sender, EventArgs e)
{
    ContentPlaceHolder MainContent = Page.Master.FindControl("MainContent") as ContentPlaceHolder;

    Label ResultsLabel = MainContent.FindControl("Results") as Label;
    TextBox AgeTextBox = MainContent.FindControl("Age") as TextBox;

    ResultsLabel.Text = string.Format("You are {0} years old!", AgeTextBox.Text);
}

Esta vez, visitar la página a través de un explorador, escribir su edad y hacer clic en el botón "Enviar" muestra el mensaje en la etiqueta Results, según lo previsto.

The User's Age is Displayed in the Label

Ilustración 06: la edad del usuario se muestra en la etiqueta (haga clic para ver la imagen a tamaño completo)

Búsqueda recursiva mediante contenedores de nomenclatura

La razón por la que el ejemplo de código anterior hace referencia al control ContentPlaceHolder MainContent desde la página maestra y, a continuación, los controles de etiqueta Results y TextBox Age de MainContent, se debe a que el método Control.FindControl solo busca dentro del contenedor de nomenclatura de Control. Que FindControl se mantenga dentro del contenedor de nomenclatura tiene sentido en la mayoría de los escenarios, ya que dos controles de dos contenedores de nomenclatura diferentes pueden tener los mismos valores ID. Tenga en cuenta el caso de un control GridView que define un control Web de etiqueta denominado ProductName dentro de uno de sus TemplateFields. Cuando los datos se enlazan a GridView en tiempo de ejecución, se crea una etiqueta ProductName para cada fila de GridView. Si FindControl se busca en todos los contenedores de nomenclatura y se llama a Page.FindControl("ProductName"), ¿qué instancia de etiqueta debe devolver FindControl? ¿La etiqueta ProductName de la primera fila de GridView? ¿La de la última fila?

Por lo tanto, que Control.FindControl busque solo el contenedor de nomenclatura de Control tiene sentido en la mayoría de los casos. Pero hay otros casos, como el que nos atañe, donde tenemos un único ID en todos los contenedores de nomenclatura y queremos evitar tener que hacer referencia minuciosamente a cada contenedor de nomenclatura de la jerarquía de control para acceder a un control. Tener una variante FindControl que busca recursivamente todos los contenedores de nomenclatura también tiene sentido. Desafortunadamente, .NET Framework no incluye este método.

La buena noticia es que podemos crear nuestro propio método FindControl que busca recursivamente todos los contenedores de nomenclatura. De hecho, el uso de métodos de extensión se puede poner en un método FindControlRecursive a la clase Control para acompañar su método FindControl existente.

Nota:

Los métodos de extensión son una característica nueva de C# 3.0 y Visual Basic 9, que son los lenguajes que se incluyen con .NET Framework versión 3.5 y Visual Studio 2008. En resumen, los métodos de extensión permiten a un desarrollador crear un nuevo método para un tipo de clase existente a través de una sintaxis especial. Para obtener más información sobre esta característica útil, consulte mi artículo Extending Base Type Functionality with Extension Methods (Extender la funcionalidad de tipo base con métodos de extensión).

Para crear el método de extensión, agregue un nuevo archivo a la carpeta App_Code denominada PageExtensionMethods.cs. Agregue un método de extensión denominado FindControlRecursive que toma como entrada un parámetro string denominado controlID. Para que los métodos de extensión funcionen correctamente, es fundamental que la propia clase y sus métodos de extensión se marquen como static. Además, todos los métodos de extensión deben aceptar como primer parámetro un objeto del tipo al que se aplica el método de extensión y este parámetro de entrada debe ir precedido de la palabra clave this.

Agregue el código siguiente al archivo de clase PageExtensionMethods.cs para definir esta clase y el método de extensión FindControlRecursive:

using System;
using System.Web;
using System.Web.UI;

public static class PageExtensionMethods
{
    public static Control FindControlRecursive(this Control ctrl, string controlID)
    {
        if (string.Compare(ctrl.ID, controlID, true) == 0)
        {
            // We found the control!
            return ctrl;
        }
        else
        {
            // Recurse through ctrl's Controls collections
            foreach (Control child in ctrl.Controls)
            {
                Control lookFor = FindControlRecursive(child, controlID);

                if (lookFor != null)
                    return lookFor;  // We found the control
            }

            // If we reach here, control was not found
            return null;
        }
    }
}

Con este código en su lugar, vuelva a la clase de código subyacente de la IDIssues.aspx página y convierta en comentario las llamadas al método actual FindControl. Reemplácelas por llamadas a Page.FindControlRecursive("controlID"). La parte buena de los métodos de extensión es que aparecen directamente en las listas desplegables de IntelliSense. Como se muestra en la ilustración 7, cuando se escribe Page y, a continuación, se alcanza el punto, el FindControlRecursive método se incluye en la lista desplegable IntelliSense junto con los otros Control métodos de clase.

Extension Methods are Included in the IntelliSense Drop-Downs

Ilustración 07: los métodos de extensión se incluyen en las listas desplegables de IntelliSense (haga clic para ver la imagen a tamaño completo)

Escriba el código siguiente en el controlador de eventos SubmitButton_Click y, a continuación, pruébelo visitando la página, escribiendo su edad y haciendo clic en el botón "Enviar". Como se muestra en la ilustración 6, la salida resultante será el mensaje "Tiene años"

protected void SubmitButton_Click(object sender, EventArgs e)
{
    Label ResultsLabel = Page.FindControlRecursive("Results") as Label;
    TextBox AgeTextBox = Page.FindControlRecursive("Age") as TextBox;

    ResultsLabel.Text = string.Format("You are {0} years old!", AgeTextBox.Text);
}

Nota:

Dado que los métodos de extensión son nuevos en C# 3.0 y Visual Basic 9, si usa Visual Studio 2005, no podrá usar métodos de extensión. En su lugar, deberá implementar el FindControlRecursive método en una clase auxiliar. Rick Strahl tiene un ejemplo de este tipo en su entrada de blog, Páginas maestras ASP.NET y FindControl.

Paso 4: Usar el valor de atributo correctoiden el script del lado cliente

Como se indicó en la introducción de este tutorial, el atributo representado id de un control web suele usarse en el script del lado cliente para hacer referencia mediante programación a un elemento HTML determinado. Por ejemplo, el siguiente JavaScript hace referencia a un elemento HTML por su id y, a continuación, muestra su valor en un cuadro de mensaje modal:

var elem = document.getElementById("Age");
if (elem != null)
    alert("You entered " + elem.value + " into the Age text box.");

Recuerde que en ASP.NET páginas que no incluyen un contenedor de nomenclatura, el atributo del id elemento HTML representado es idéntico al valor de propiedad del ID control Web. Por este motivo, es tentador codificar de forma rígida los valores de atributo en id código JavaScript. Es decir, si sabe que desea acceder al Age control web TextBox a través del script del lado cliente, hágalo a través de una llamada a document.getElementById("Age").

El problema con este enfoque es que al usar páginas maestras (u otros controles de contenedor de nomenclatura), el HTML id representado no es sinónimo de la propiedad del ID control Web. Su primera inclinación puede ser visitar la página a través de un explorador y ver el origen para determinar el atributo real id. Una vez que conozca el valor representado id, puede pegarlo en la llamada a para getElementById acceder al elemento HTML con el que debe trabajar a través del script del lado cliente. Este enfoque es menor que ideal porque ciertos cambios en la jerarquía de control de la página o los cambios en las ID propiedades de los controles de nomenclatura modificarán el atributo resultante id, lo que interrumpirá el código de JavaScript.

La bueno es que el valor de atributo id que se representa es accesible en el código del lado servidor a través de la propiedad ClientID del control Web. Debe usar esta propiedad para determinar el valor de id atributo usado en el script del lado cliente. Por ejemplo, para agregar una función de JavaScript a la página que, cuando se llama, muestra el valor de Age TextBox en un cuadro de mensaje modal, agregue el código siguiente al controlador de Page_Load eventos:

ClientScript.RegisterClientScriptBlock(this.GetType(), "ShowAgeTextBoxScript",
 string.Format(@"function ShowAge()
 {{
 var elem = document.getElementById('{0}');
 if (elem != null)
 alert('You entered ' + elem.value + ' into the Age text box.');
 }}", AgeTextBox.ClientID), true);

El código anterior inserta el valor de la Age propiedad ClientID de TextBox en la llamada de JavaScript a getElementById. Si visita esta página a través de un explorador y ve el origen HTML, encontrará el código JavaScript siguiente:

<script type="text/javascript">
//<![CDATA[
function ShowAge()
{
 var elem = document.getElementById('ctl00_MainContent_Age');
 if (elem != null)
 alert('You entered ' + elem.value + ' into the Age text box.');
}//]]>
</script>

Observe cómo aparece el valor de atributo id correcto, ctl00_MainContent_Age, dentro de la llamada a getElementById. Dado que este valor se calcula en tiempo de ejecución, funciona independientemente de los cambios posteriores en la jerarquía de controles de página.

Nota:

Este ejemplo de JavaScript simplemente muestra cómo agregar una función de JavaScript que hace referencia correctamente al elemento HTML representado por un control de servidor. Para usar esta función, tendría que crear JavaScript adicional para llamar a la función cuando se cargue el documento o cuando transcurra alguna acción de usuario específica. Para obtener más información sobre estos temas y relacionados, lea Trabajar con script del lado cliente.

Resumen

Algunos controles de servidor ASP.NET actúan como contenedores de nomenclatura, lo que afecta a los valores de atributo representados id de sus controles descendientes, así como al ámbito de los controles en lienzo por el FindControl método. En lo que respecta a las páginas maestras, tanto la propia página maestra como sus controles ContentPlaceHolder son contenedores de nomenclatura. Por lo tanto, es necesario poner un poco más de trabajo para hacer referencia mediante programación a los controles dentro de la página de contenido mediante FindControl. En este tutorial hemos examinado dos técnicas: profundizar en el control ContentPlaceHolder y llamar a su FindControl método; y rodar nuestra propia FindControl implementación que busca recursivamente en todos los contenedores de nomenclatura.

Además de los problemas del lado servidor, los contenedores de nomenclatura presentan con respecto a las referencias a controles web, también hay problemas del lado cliente. En ausencia de contenedores de nomenclatura, el valor de propiedad ID del control web y el valor de atributo id representado son uno en el mismo. Sin embargo, con la adición de un contenedor de nombres, el atributo id representado incluye los valores ID del control web y los contenedores de nomenclatura en su jerarquía de controles. Estos problemas de nomenclatura son un problema siempre y cuando use la propiedad ClientID del control web para determinar el valor de atributo id representado en el script del lado cliente.

¡Feliz programación!

Lecturas adicionales

Para obtener más información sobre los temas tratados en este tutorial, consulte los siguientes recursos:

Acerca del autor

Scott Mitchell, autor de varios libros de ASP/ASP.NET y fundador de 4GuysFromRolla.com, ha estado trabajando con tecnologías web de Microsoft desde 1998. Scott trabaja como consultor independiente, entrenador y escritor. Su último libro es Sams Teach Yourself ASP.NET 3.5 in 24 Hours. Se puede contactar con Scott en mitchell@4GuysFromRolla.com o a través de su blog en http://ScottOnWriting.NET.

Agradecimientos especiales a

Esta serie de tutoriales fue revisada por muchos revisores que fueron de gran ayuda. Los revisores principales de este tutorial fueron Zack Jones y Suchi Barnerjee. ¿Le interesaría revisar mis próximos artículos de MSDN? Si es así, escríbame a mitchell@4GuysFromRolla.com.