HtmlEncode y HtmlDecode

Como en muchas otras cosas, ASP.NET nos los vuelve a poner muy fácil.
En muchas ocasiones podemos estar interesados en mostrar datos con código HTML, pero sin que el código HTML haga su función. Por ejemplo, no nos interesaría que un usuario malintencionado inyectara en nuestro foro cierto código javascript. O por ejemplo queremos mostrar "<b>texto en negrita</b>" en lugar de "texto en negrita".

Con ASP.NET, esto es muy sencillo sin más que utilizar HtmlEncode y HtmlDecode.

Resulta obvio, pero HtmlEncode codifica el código html. Por ejemplo, pasa de '<' a '&lt;', mientras que HtmlDecode hace lo contrario.

Y su uso en ASP.NET es trivial:

    string text = "<b>Mi texto en negrita</b>";
    string textEncoded =  HttpUtility.HtmlEncode(text);
    string textDecoded = HttpUtility.HtmlDecode(textEncoded);


Donde textEncoded valdrá "&lt;b&gt;Mi texto en negrita&lt;/b&gt;" y textDecoded volverá a valer lo mismo que el texto original.

Y ya que sabemos como usarlo, yo aconsejaría que siempre, en todo caso, que recibamos texto de usuarios desconocidos (por ejemplo de un foro de una de nuestras webs) hagamos un HtmlEncode antes de mostrarlo, para evitar inyecciones de HTML indeseadas (negritas sin cerrar, tablas, javascript, flash...), y si queremos permitir algún tipo de HTML, hacerlo tras haber hecho dicho HtmlEncode.

Por ejemplo, para evitar todo el HMTL excepto la negrita, haríamos:

    string text = "<b>Mi texto en negrita</b>";
   
text text.Replace(HttpUtility.HtmlEncode("<b>"), "<b>");



Customiza la clase System.Web.UI.Page

Vaya un título extraño para un artículo, ¿No?

A veces lo más difícil de un artículo es encontrar un título que lo identifique totalmente, y es que vale más un simple ejemplo que mil palabras.

Vayamos al grano. Como sabréis, en vuestro Code-Behind (las páginas .aspx.cs) las partial classes comienzan por

    public partial class Default : System.Web.UI.Page

Lo que quiere decir que hereda (como todas las .aspx.cs) de la clase System.Web.UI.Page.

Lo que se pretende en este artículo es dar la oportunidad de que en lugar de que todas nuestras páginas hereden de Page, lo hagan de una clase nuestra... cada día me gusta más esto de la orientación a objetos

Esta clase que nos inventamos debe, obviamente, heredar de la clase Page. Pongamos un ejemplo:

namespace Subgurim
{
    using System;
    using System.IO;
    using System.Web.UI;

    /// <summary>
    /// Hereda de Xavi.MyPage, y todas las páginas de nuestra aplicación
    /// heredarán de ésta.
    /// </summary>
    public class MyPage : Page
    {
        /// <summary>
        /// Cuando se produzca un error, lo escribiremos en un fichero.
        /// </summary>
        /// <param name="e"></param>
        protected override void OnError(EventArgs e)
        {
            base.OnError(e);
            StreamWriter writer = File.AppendText(Server.MapPath("~/errorlog.txt"));
            Exception ex = Server.GetLastError();
            writer.WriteLine(ex.Message + "[" + DateTime.Now + "]");
            writer.Close();
        }
    }
}

Con esta nueva clase, cada vez que se produzca un error quedará constancia de ello en un archivo (errorlog.txt) situado en la raíz de nuestra aplicación.

Para ello, lo único que tenemos que hacer es sustituir en todos los archivos de Code-Behind, esto:

    public partial class Default : System.Web.UI.Page

por esto

    public partial class Default : Subgurim.MyPage

Y así de fácil es.

En nuestro ejemplo, sólo lo hemos utilizado para hacer un log de errores, pero podemos utilizarlo para lo que queramos (o nuestra imaginación nos diga):

- Establecer culturas
- Hacer que ciertas palabras aparezcan subrayadas
- Asignar Themes a partir del profile de usuario
- Muchas cosas más



Variables de Cache

En general, todo el tema de la cache de ASP.NET te entra por los ojitos y es otro de los puntos que te hace decidirte por esta plataforma de creación Web en lugar de cualquier otra.

No nos equivocamos si decimos que la (correcta) utilización de la cache permite una tremenda mejora del performance de nuestra aplicación.

Llegada esta afirmación "mejora de performance", surge la inteligente pregunta de ¿En qué punto se cortan las rectas de "gasto por acceso a datos" y "gasto por almacenamiento de cache", tabulado por la cantidad de accesos que se hacen + el tamaño de esos datos?

Hemos de tener en cuenta que, en el 90% de los casos, el mayor gasto de recursos se realiza en los accesos a Base de Datos (el evitar accesos a la BBDD es un ejemplo típico de uso de variables de cache). Pero también es cierto que la memoria de nuestro servidor (sobretodo si es compartido) no es infinita y si parte de esa memoria no se usa, se borra... entonces, ¿Cuándo conviene utilizar las variables de cache?

Supongamos varios escenarios de casos bastante típicos, en los que se recoge un resultado de 1000 columnas de la BBDD:

Escenario 1
- Nuestra página la acceden 100 personas por minuto.
- Cada vez que lo hacen realizamos un acceso a base de datos.
- Esa información apenas cambia un par de veces cada hora
Conclusión: probablemente convenga utilizar variables de cache

Escenario 2

- Nuestra página la acceden 100 personas cada hora
- Cada vez que lo hacen realizamos dos accesos a base de datos.
- La información cambia una vez a la semana.
Conclusión: aunque la información cambia cada mucho tiempo, no parece necesario ahorrarnos cuatros simples accesos por minuto.

En última instancia, la decisión depende de vuestras pruebas y experiencias. En un futuro publicaré algún artículo haciendo pruebas de eficiencia al respecto.

A lo largo de este artículo vamos a trabajar específicamente con las variables de cache, el modo en que, mediante código, almacenamos información en la cache.

¿Cómo se utiliza una variable de cache?
Es muy sencillo. Su uso es muy similar al de las variables de sesión o aplicación. Veamos un ejemplo en el que se guarda en una variable de cache el momento en que la hemos guardado, y posteriormente recogemos ese momento y lo guardamos en una variable DateTime:

    Cache["miprimeraVariable"] = DateTime.Now;
    //...
    DateTime cuando = (DateTime)Cache["miprimeraVariable"];


Ejemplo de uso eficiente
Veamos un ejemplo de uso eficiente de variables de Cache. Supongamos que accedemos a una base de datos y con su resultado rellenamos una lista genérica de 10.000 componentes. Para ahorrarnos ese acceso tan gordo, sobretodo si lo exigimos varias veces por segundo, haríamos algo como:

variablesCache.cs
    public static List<Person> recogePersonasBLL()
    {
        List<Person> personas = (List<Person>)Cache["personas"];

        if (null == personas)
        {
            personas = miDAL.recogePersonasDAL();

            Cache.Insert("personas", personas, null, DateTime.Now.AddMinutes(5), TimeSpan.Zero);
        }
        return personas;
    }


Como véis, lo primero que hacemos es comprobar si nuestro listado de personas está almacenada en la variable de cache. Si es así, ya no hacemos nada más, nos hemos ahorrado un gordo acceso a BBDD y devolvemos el listado.

Si no existiera esa variable de Cache, bien porque hubiera caducado o bien porque no la habíamos creado hasta ahora, entonces realizamos el acceso a la base de datos y guardamos esos datos en una variable de cache:

            Cache.Insert("personas", personas, null, DateTime.Now.AddMinutes(5), TimeSpan.Zero);

En el primer parámetro asignamos el nombre de esa variable de cache.
En el segundo ubicamos la propia variable a almacenar.
El tercer parámetros define ciertas dependencias de la cache, pero en nuestro sencillo ejemplo no nos interesa y le damos un valor de null.
En cuanto al cuarto parámetro, definimos el tiempo absoluto de caducidad de la variable de cache. En nuestro caso, caducará 5 minutos después de haberla creado.
El último parámetro es el tiempo relativo de caducidad, que es el tiempo que pasa desde la última vez que hemos accedido a la variable de cache. Por ejemplo, si hubiéramos indicado un tiempo relativo de 5 minutos, la variable hubiera caducado 5 minutos después de la última vez que se ha accedido (de cualquier modo) a esa variable de cache.

Habitualmente, el mayor problema con el que nos enfrentamos cuando trabajamos con variable de cache es la sincronización entre los datos reales que hay en la BBDD y los datos que hay almacenados en la variable de cache. con SQLServer 2005 esa sincronización la podemos definir como automática: "Cuando alguien cambio ciertos datos de alguna de tus tablas, guarda automáticamente los cambios en la variable de cache que te digamos".

Sin embargo, en la mayor parte de los casos vale con definir una caducidad apropiada, que en algunas aplicaciones será de un tiempo absoluto de 5 minutos, en otra puede ser de algunas horas, y en otras puede convenir que sea definida una caducidad relativa.


Pues poco más que añadir!! Animaos a utilizar el enorme poder de las variables de cache!!




Listas genéricas: System.Collections.Generic.List<>

Fue conocerlas y no soltarlas!! Las listas genéricas son una de las nuevas clases de .NET 2.0 dentro del namespace System.Collections.Generic.

Como su propio nombre indica, permite listar cualquier cosa, desde un simple listado de strings hasta un listado de la clase más compleja que hayáis creado. Y permite funciones muy útiles para ordenar, buscar un índice, comparar...

A continuación veremos una pequeña introducción al uso de las listas genéricas, para perderle el miedo, animaros a usarlas y para que investiguéis todas sus funcionalidades vosotros mismos.

Vamos a hacer una lista de la típica clase Person:

Person.cs
    public class Person
    {
        private string _nombre;
        /// <summary>
        /// Nombre de la persona
        /// </summary>
        public string Nombre
        {
            get { return _nombre; }
            set { _nombre = value; }
        }

        private string _apellido;
        /// <summary>
        /// Apellido de la persona
        /// </summary>
        public string Apellido
        {
            get { return __apellido; }
            set { __apellido= value; }
        }

        private DateTime _nacimiento;
        /// <summary>
        /// Cuando nació la persona
        /// </summary>
        public DateTime Nacimiento
        {
            get { return _nacimiento; }
            set { _nacimiento = value; }
        }

        public Person(string nombre, string apellido, DateTime nacimiento)
        {
             this.Nombre = nombre;
             this.Apellido = apellido;
             this.Nacimiento = nacimiento;
        }
    }


Listado.cs
        List<Person> personas = new List<Person>();
        personas.Add(new Person("José", "García", new DateTime(1940, 12, 2)));
        personas.Add(new Person("Pedro", "López", new DateTime(1992, 2, 22)));
        personas.Add(new Person("Antonio", "Pérez", new DateTime(1976, 6, 21)));


La lista es una especie de array que se va redimensionando conforme a las necesidades. Al crear la variable List<> se inicializa su capacity, que se aumenta conforme la lista va creciendo. Pero, a no ser que indiquemos lo contrario, es totalmente transparente a nosotros.

La cuestión es que nosotros podemos hascer la lista tan grande como queramos, y listarlo fácilmente. Por ejemplo, vamos a ver dos modos de mostrar los elementos de la lista, colocando el resultado en un StringBuilder:

Listado.cs
    private void Mostrar1(List<Person> personas)
    {
        System.Text.StringBuilder sb = new System.Text.StringBuilder();
        foreach (Person persona in personas)
        {
            sb.AppendLine(persona.Nombre + " " + persona.Apellido + "<br />");
        }
    }

Listado.cs
    private void Mostrar2(List<Person> personas)
    {
        System.Text.StringBuilder sb = new System.Text.StringBuilder();
        for (int i = 0; i < personas.Count; i++)
        {
            sb.AppendLine(personas[i].Nombre + " " + personas[i].Apellido + "<br />");
        }
    }

A mí personalmente me gusta más el modo 1

Pero obviamente, no es mostrar lo único que se puede hacer con una lista genérica. Por ejemplo podemos buscar los elementos de la lista que cumplan las condiciones que le indiquemos. Por ejemplo, queremos encontrar las personas que hayan nacido a partir de 1975 (a las que llamaremos jóvenes ):

Listado.cs
    private List<Person> Jovenes(List<Person> personas)
    {
       // Encuentra todas las personas que "jóvenes"
        return personas.FindAll(encuentra);

       // Encuentra la primera persona joven de la lista
       // personas.Find(encuentra);

       // Encuentra el índice de la primera persona joven de la lista
       //  personas.FindIndex(encuentra);

       // Encuentra la última persona joven de la lista
       // personas.FindLast(encuentra);

       // Encuentra el índice de la última persona joven de la lista
       //  personas.FindLastIndex(encuentra);

    }

    private bool encuentra(Person P)
    {
        return P.Nacimiento.Year > 1975;
    }


Y del mismo modo podemos realizar varias operaciones interesantes, como insertar en cierto índice (insert), borrar una entrada (remove), vaciar la lista (clear), ordenarla (sort), comprobar si existe cierte entrada (exist)...

Listado.cs
    private void Varios(List<Person> personas)
    {
       // Inserta una nueva persona en el índice 2 (tercera posición)
        personas.Insert(2, new Person("Javier", "Navarro", new DateTime(1981, 12, 21)));

       // Ordena el listado de personas según su edad
        personas.Sort(ordenarPorEdad);

       // Devuelve si existen jóvenes en el listado. En este caso devolvería true
       bool existenJovenes = personas.Exists(encuentra);

       // Borrar la Persona que está en la tercera posición (índice 2)
        personas.RemoveAt(2);

       // Borra todas las personas jóvenes
        personas.RemoveAll(encuentra);

       // Ahora devolvería false
       bool existenJovenes = personas.Exists(encuentra);


       // Vacía la lista
        personas.Clear();
    }

    private bool encuentra(Person P)
    {
        return P.Nacimiento.Year > 1975;
    }

    private int ordenarPorEdad(Person P1, Person P2)
    {
        return P1.Nacimiento.CompareTo(P2.Nacimiento);
    }

Como véis, es muy sencillo trabajar con listas genéricas, y realmente resultan muy útiles en muchos campos.

Por ejemplo, se puede marcar como datasource de cualquier ServerControl de datos, por ejemplo:

Listado.cs
    private void arreglaDatos(List<Person> personas)
    {
       GridView1.DataSource = personas;
       GridView1.DataBind();
    }


Personalmente, los datos que me devuelve mi capa DAL están ya en una lista genérica que almacena elementos de la clase que me he creado, y después ya los trato como interese en el resto de capas BLL y capa de presentación.



Pero qué xxxxxx es AJAX!!!!!??????

A día de hoy, por AJAX se entiende a todo aquello que desde el cliente vaya a servidor, realice las acciones que quiera y vuelva al cliente presentando (o no) cambios sin recargar la página.

Pues vaya una definición para un Blog "Técnico"!! Pues ya ves, la cosa ha llegado hasta ese extremo. Incluso diría, sin miedo a equivocarme, que vale eso mismo incluyendo que no hace ni falta que sea Asíncrono!

Realmente, una definición más técnica sería que en AJAX se realizan llamadas asíncronas a servicios web utilizando el objeto XMLHTTPRequest de JavaScript para transferir recibir info en XML. Lo interesante del asunto es que el XMLHTTPRequest provee una especie de proxy que manda y recibe esos datos, de modo que de cara al usuario, la página no se recarga.

Como todos sabréis, AJAX no está ligado (ni mucho menos) a .NET, pero dada la infraestructura de .NET, han surgido decenas de FrameWorks para que, con .NET, sea increíblemente sencillo y/o potente trabajar con AJAX.

Pero también hay varios frameworks para PHP, Python, Ruby... y todo lo que queráis encontrar. Desde aquí podréis encontrar toda la info que queráis sobre frameworks AJAX en varios lenguajes: ajaxpatterns.org

Pero volviendo con ASP.NET, y para compartir mi experiencia, yo he utilizado mucho AJAXPRO (http://weblogs.asp.net/mschwarz/) del gran genio Michael Schwarz, quien provee una enorme librería para trabajar con javascript y ASP.NET. Sin embargo, puede resultar complejo para proyectos grandes.

También he utilizado MagicAJAX, que realmente no es AJAX, ya que el programador no necesita escribir ni una simple línea de javascript, sino que se coloca dentro de un "AjaxPanel" el contenido que quieres que se actualice sin recargar la página y la cosa funciona sin problemas.

Sin embargo, personalmente lo voy a dejar todo y me voy a pasar definitivamente a Atlas, el Framework de Microsoft para AJAX, porque provee funcionalidades que ya incluyen MagicAJAX y AJAXPro (de hecho, los chicos de MagicAJAX se están pensando muy mucho seguir con su proyecto dado que Atlas funciona a las mil maravillas).

Además, a la hora de llevar a cabo proyectos profesionales y de mucha responsabilidad, prefiero fiarme de una gran empresa (Microsoft) a proyectos abiertos (MagicAJAX) o unipersonales (AJAXPro) que pueden abandonar cuando quieran pues no asumen ninguna responsabilidad.

Os imagináis hablando con un gran cliente que ha pagado millones por tu aplicación en AJAX, diciéndote que ha encontrado un bug?? Os imagináis pensando "Voy a decírselo a los chicos de MagicAjax a ver si me resuelven el problema"??

De modo que yo lo siento mucho, pero de momento me quedaré con Atlas

PD: cuando sepa más cosas sobre Atlas iré poniendo artículos




Validation en ASP.NET 2.0

Uno de los "tips" o "pijaditas" que más me ha atraído siempre la atención en ASP.NET son los validadores.

¿Cuántas veces os habéis tenido que currar un largo formulario donde exigíais ciertas condiciones en cada campo? Que si éste no puede estar vacío, que si este otro debe ser un mail, que si este otro debe ser un número mayor de 100 pero menor de 200... MUCHAS!!

En principio habías dos opciones, hacerlo con javascript o hacer la comprobación en servidor... o las dos cosas a la vez... pero eso daba un trabajo impresionante para un formulario medianamente largo... y no digamos nada si hablamos de cambios en ese formulario.

Pues bien, los ASPNETeros estamos de suerte, pues este tipo de cosillas son muy sencillas de implementar, sin más que arrastrar el ratón .

Durante este artículo veremos los diferentes controles web para validación de datos, y en el primero de ellos hablaremos de generalidades.

RequiredFieldValidator (y generalidades)
Personalmente es el validador que más uso y apuesto a que es el más común de todos en la web.NET. El concepto es muy sencillo: se asegura de que cierto control no esté vacío.

Pero vayamos primero con las generalidades. Aquí el concepto es que cuando el usuario provoca cierto evento (por ejemplo presiona un botón o selecciona un Item de una lista desplegable), nosotros queremos asegurarnos de que se cumplen ciertas reglas en nuestro formulario. Por ejemplo que un campo de texto no esté vacío:

    <asp:TextBox ID="TextBox1" runat="server" ValidationGroup="miPrueba"></asp:TextBox>
    <asp:RequiredFieldValidator ID="RequiredFieldValidator1" runat="server" ControlToValidate="TextBox1"
        ErrorMessage="Mensaje de error" ValidationGroup="miPrueba"></asp:RequiredFieldValidator>
    <asp:Button ID="Button1" runat="server" Text="Button" ValidationGroup="miPrueba"  OnClick="Button1_Click" />


 Estudiando el RequiredFieldValidator vemos que:
- ControlToValidate: desde aquí se asigna el control al que vamos a "vigilar" que no se quede vacío.
- ErrorMessage: obviamente es el mensaje de error. Puedes poner cosas como "Este campo no puede estar vacío" o simplemente un "*".
- ValidationGroup: esto es nuevo en ASP.NET 2.0, y define el grupo de controles que pertenecen a un grupo de validación. En ASP.NET 1.x no era así de modo que se validaban todos los controles de la página, por lo que no se podía tener 2 grupos independientes de TextBox + Button con validadores independientes, ya que si se validaba uno también se validaba otro. Como véis, ValidationGroup: también los tienen el resto de controles implicados.
- Cosas por defecto: el resto de cosillas por defecto las puede cambiar cada uno a su gusto. Por ejemplo, por defecto, el mensaje de error aparece en cursiva y en rojo, y también por defecto (y muy aconsejable) la validación se realiza en javascript, por lo que si encuentra algún error no hace falta ir al servidor.

Y esto de la validación en javascript es muy interesante y viene al hilo de que conviene exigir en servidor que los requisitos definidos con los Validadores se han cumplido. En cristiano, nuestro codebehind:

    protected void Button1_Click(object sender, EventArgs e)
    {
        Page.Validate("miPrueba");

        if (Page.IsValid)
        {
            // Realizar acciones
        }
    }


Cuando hacemos clic sobre el botón, validamos el grupo de validación que corresponda, y realizamos las acciones en caso de que el resultado sea válido. En nuestro ejemplo, daría válido si el TextBox1 no estuviera vacío.

RangeValidator
El RangeValidator se encarga de asegurar que el control que estemos validando esté dentro de cierto rango (). Lo que no es tan es que no sólo se trabaja con enteros, sino que se trabaja con strings, fechas, double y currency.

Para que nuestra TextBox2 tenga un número entre 10 y 100:    

<asp:RangeValidator ID="RangeValidator1" runat="server" ErrorMessage="Fuera de rango" ControlToValidate="TextBox2" MaximumValue="100" MinimumValue="10" Type="Integer" ValidationGroup="miPrueba"></asp:RangeValidator>

Donde todos los atributos se autoexplican.

CompareValidator
Con el comparevalidator, nos aseguramos de que dos controles tengan el mismo valor. El propio control Login de ASP.NET lo usa para asegurarse de que el "password" y "repite password" sean el mismo.

Para asegurarnos de que el TextBox3 y el TextBox4 sean iguales:

    <asp:CompareValidator ID="CompareValidator1" runat="server" ControlToCompare="TextBox4"
        ControlToValidate="TextBox3" ErrorMessage="No son iguales!!" ValidationGroup="miPrueba"></asp:CompareValidator>


Pero no sólo eso, el CompareValidator da la posibilidad de comparar un control con cualquier otra cosa mediante el atributo ValueToCompare.

Por ejemplo, y esto ya es imaginación pura y dura, podemos asegurarnos de que el TextBox3 sea igual al valor de cierta variable dentro del ViewState (o del queryString, o una variable de sesión...):

En codebehind:
        CompareValidator1.ValueToCompare = ViewState["nombreVariable"];

en .aspx:
    <asp:CompareValidator ID="CompareValidator1" runat="server" ControlToCompare="TextBox3"
        ErrorMessage="CompareValidator" ValidationGroup="miPrueba"></asp:CompareValidator>


RegularExpressionValidator
Y para validar CualquierCosaQueSeNosOcurra, ¿Qué mejor que una buena expresión regular?? (Os recomiendo que le echéis un vistazo pausadito a Expresiones regulares. Introducción. y a Expresiones regulares. Cómo trabajarlas con ASP.NET.).

Por ejemplo, con este código nos aseguramos que el TextBox5 sea un email:

    <asp:RegularExpressionValidator ID="RegularExpressionValidator1" runat="server" ControlToValidate="TextBox5"
        ErrorMessage="No cumple la expresión regular" ValidationExpression="\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*"
        ValidationGroup="miPrueba"></asp:RegularExpressionValidator>


CustomValidator
El colofón final de los validadores es cuando la validación es tan compleja (por ejemplo supone la combinación de varios controles a la vez) que sólo es posible validarla con una función específica.

Por ejemplo, vamos a hacer que una función que tenemos en servidor y que llamaremos "ValidameEnServidor", sea la que compruebe si se cumplen los requisitos que queremos:

Ejemplo_CustomValidator_Servidor.aspx
    <asp:CustomValidator ID="CustomValidator1" runat="server" OnServerValidate="ValidameEnServidor" EnableClientScript="false"
        ControlToValidate="TextBox6" ErrorMessage="No cumples la validación de servidor." ValidationGroup="miPrueba"></asp:CustomValidator>

Ejemplo_CustomValidator_Servidor.aspx.cs
    protected void ValidameEnServidor(object source, ServerValidateEventArgs value)
    {
       // Hacemos las comprobaciones que queramos con los controles que queramos,
       // y al final se le da un valor a:
       value.IsValid = true; // (o false)
    }


Fijaos que al CustomValidator le decimos que NO lo compruebe con javascript (EnableClientScript="false"), porque en nuestro ejemplo queremos que lo haga exclusivamente en servidor.

Sin embargo, la comprobación en javascript en muy sencilla, de modo que no tendríamos más que:

Ejemplo_CustomValidator_Cliente.aspx
    <asp:CustomValidator ID="CustomValidator1" runat="server" ClientValidationFunction="ValidameEnCliente"
        ControlToValidate="TextBox6" ErrorMessage="No cumples la validación de servidor." ValidationGroup="miPrueba"></asp:CustomValidator>

<script language="JavaScript">
<!--
  function
ValidameEnCliente(sender, args)
  {
       // Realizamos las comprobaciones que creamos necesarias para finalmente:
        args.IsValid = true; (o false);
  }
// -->
</script>



ValidationSummary
Como me gusta ASP.NET... y es que el equipo de ASP.NET ha pensado en todo!!
ValidationSummary lo único que hace es hacernos un pequeños resumen de los resultados de nuestra validación.

Por ejemplo, imaginemos que tenemos un enorme formulario con 15 DropDownList's y 20 TextBox's, y que entre el principio del formulario y el final hay que hacer rodar varias veces la ruedecita de nuestro ratón .

Además, nuestro botón de "Enviar información" está en la parte final... ¿Qué sucede si tenemos todos los campos bien, menos el primer TextBox (que está arriba del todo)? Pues sucede que el usuario se queda esperando, aprieta varias veces al botón y comprueba y recomprueba que el formulario no se envía al servidor - "Vaya xxxxxx de página", pensará nuestro usuario.

Pues para eso está el ValidationSummary. Nosotros lo pondremos cerquita de nuestro botón de "Enviar información" y allí aparecerán todos los fallos a la hora de rellenar datos que tenga nuestro formulario.

Esto ya funcionaría:

    <asp:ValidationSummary ID="ValidationSummary1" runat="server" ValidationGroup="miPrueba" />


¿Son o nos son maravillosos los Validators en ASP.NET 2.0?



Gridview. Diferentes columnas

Como continuación de nuestro primer artículo "GridView. Introducción" que os aconsejamos leer si no lo habéis hecho ya, en este artículos vamos a estudiar los diferentes tipos de columnas que nos podemos encontrar en un gridview.

Hasta el momento, sabemos que con...

    <asp:GridView ID="GridView1" DataSourceID="SqlDataSource1" runat="server" />

... se nos muestra por pantalla todas las columnas que nos devuelve el SqlDataSource1... pero sin ningún tipo de control por nuestra parte... ¿Qué sucede si lo que nos interesa es una de esas columas sean un enlace cuya URL dependa de un dato de la base de datos, y cuyo anchor dependa de otro parámetro de la base de datos?, ¿Y si queremos que una columna sea un checkbox?, ¿Y si queremos que una columa sea una imagen?, ¿O si simplemente queremos que una columna sea lo que nos dé la gana?

Bien, pues el gridview nos ofrece soluciones para prácticamente todos los tipos de columna que queramos. Así pues, iremos una por una para ver sus posibilidades.

Pero antes de comenzar, veamos una previa.

Previa
¿Dónde colocamos los tipos de columnas?
Veamos un ejemplo de código:

    <asp:GridView ID="GridView1" runat="server">
        <Columns>
            <asp:BoundField DataField="Campo1" HeaderText="Encabezado" SortExpression="Campo1" />
            <asp:CheckBoxField DataField="Campo2" HeaderText="EncabezacoCHK" SortExpression="Campo2" />
            <asp:TemplateField HeaderText="Encabezado template field"></asp:TemplateField>
        </Columns>
    </asp:GridView>


Como vemos, dentro del tag <aso:GridView...>...</asp:GridView> colocamos el tag <Columns>...</Columns> y dentro de éste es donde colocamos los diferentes tipos de columnas

¿Y todo esta hay que escribirlo a mano?
No, cualquier programita de la familia del Visual Studio 2005 hace la mayor parte del trabajo. Al final tú sólo vas a hacer algunos clics, pero entendiendo qué significa cada tipo de columna te ahorrarás muchos quebraderos de cabeza incluso para saber dónde hacer esos clics .

BoundField
El BoundField es el tipo de columna básico, al que no hemos más que decirle el nombre del campo que queremos representar y el gridview lo muestra (como resultado de texto) por pantalla.

Veamos un ejemplo:

            <asp:BoundField DataField="
Foro_Titulo" HeaderText="Título del post" SortExpression="Foro_Titulo" />

En el atributo DataField es donde indicamos el nombre del campo que queremos representar. Por ejemplo, si estamos listando los posts de un foro, ese nombre podría ser el correspondiente al campo que contiene los títulos del post (por ejemplo, en mis bases de datos, en la tabla correspondiente al foro, a esa columna la llamo Foro_Titulo).

El atributo HeaderText es donde definimos Cuál va a ser el encabezado de esa columna de nuestro GridView. Siguiendo nuestro ejemplo, a esa columna yo la llamaría "Título del post".

En cuanto al atributo SortExpresion, veremos en próximos artículos a qué se refiere, pero básicamente viene a señalar en base a qué campo vamos a ordenar si queremos ordenar el gridview desde esa columna. Por ejemplo, si quisiéramos ordenar el listado en base a esa columna, el listado nos aparecería ordenador alfabéticamente en base al título del post. También podríamos elegir ordenar según la fecha de inserción del post.

Tengo miedo de no explicar bien este último punto, por lo que enfatizaré que cada columna puede tener su propia SortExpresion, por lo que según qué columna queramos ordenar se usará una SortExpresion u otra.

CheckBoxField
Como os podréis imaginar, el CheckBoxField será una columna en que aparecerá el típico cuadro de selección (CheckBox) con el texto que le indiquemos a la columna. Un ejemplo sería:

    <asp:CheckBoxField DataField="Campo2" HeaderText="EncabezacoCHK" SortExpression="Campo2" />

Donde cada atributo significa lo mismo que con el BoundField, por lo que es el DataField el que indica qué texto acompañará al checkbox

ImageField
Ahora lo que queremos es mostrar una imagen en el listado, y en nuestra base de datos está la información que apunta a esa imagen... pues lo tenemos muy sencillo

    <asp:ImageField DataImageUrlField="datoImagen" DataImageUrlFormatString="~/Imagenes/img-{0}.jpg"
        HeaderText="Encabezado imagen">
    </asp:ImageField>


El DataImageUrlFormatString es el atributo que marca el path donde se encontrará la imagen, quedando a la expectativa de un parámetro. Y ese parámetro se recoge de DataImageUrlField y se sustituye por el {0}. En nuestro ejemplo, las imágenes están en el directorio "~Images" y todas tienen el nombre img-X.jpg, donde X es un número identificativo que guardamos en la base de datos con el nombre de "datoImagen".

Si en nuestra base de datos guardaramos el path completo de la imagen con la extensión incluída, tendríamos algo como:

    <asp:ImageField DataImageUrlField="datoImagen" DataImageUrlFormatString="{0}"
        HeaderText="Encabezado imagen">
    </asp:ImageField>

HyperLinkField
Otro tipo de columna muy utilizado es el HyperLinkField. Con éste, mostraremos al usuario un enlace con un Anchor determinado (proviniente de la BBDD o no) y una url determinada (proviniente de la BBDD o no).

Pongamos dos ejemplo:

1.- <asp:HyperLinkField HeaderText="Encabezado" NavigateUrl="http://www.subgurim.net" Text="ASP.NET 2.0 con C#" />
2.- <asp:HyperLinkField DataNavigateUrlFields="UrlEnlace" DataNavigateUrlFormatString="~/go.aspx?url={0}"
        DataTextField="AnchordelEnlace" DataTextFormatString="Visita {0}" HeaderText="Encabezado" />


En el primer caso, tenemos el extremo de mostrar un enlace con una Url determinada y un texto determinado... y en ningún caso recogemos info proviniente de ninguna base de datos.

En el segundo caso tenemos el otros extremo, en que tanto la url como el anchor utilizan información de la Base de datos.

Anchor
- DataTextFormatString: será el formato del texto que tendrá nuestro Anchor. En nuestro caso será "Visita {0}".
- DataTextField: y ese {0} lo sustituiremos por el dato que haya en el campo "AnchordelEnlace".

Url
- DataNavigateUrlFormatString: formato que tendrá nuestra url. En nuestro caso queremos que cuando el usuario haga clic en el enlace, se visite ~/go.aspx?url={0}.
- DataNavigateUrlFields: y esa {0} será la que esté almacenada en el campo "UrlEnlace".

Los casos 1 y 2 se pueden mezclar a vuestro antojo, y para finiquitar el ejemplo, vamos a mostrar el código de una columna que tendrá un enlace cuyo anchor sea "Ver" y se dirige directamente a la url que está almacenada en nuestra BBDD:

    <asp:HyperLinkField DataNavigateUrlFields="Url" DataNavigateUrlFormatString="{0}"
        Text="Ver" />


TemplateField
El TemplateField es el tipo de columna en el que puedes poner todo lo que quieras, sin nada predeterminado. Por ejemplo, si queremos que en una misma columna aparezcan una imagen dentro de una tabla, con un enlace, un checkbox y un botón.

Con un sencillo ejemplo lo veremos mejor:

    <asp:TemplateField HeaderText="Otra vez el Título del post">
        <ItemTemplate>
            Ejemplo template.
            <asp:Label ID="Label1" runat="server" Text='<%#Eval("Foro_Titulo") %>'></asp:Label>           
        </ItemTemplate>
    </asp:TemplateField>

En este pequeño ejemplo queremos mostrar un texto que nosotros queramos ("Ejemplo template") y luego una Label donde escribiremos (otra vez) el título de nuestro post. Como véis, el método de proceder es el mismo. Fijaos que nuestro código está dentro del <ItemTemplate /> que va dentro del <asp:TemplateField />. En sucesivos artículos veremos porque esto es así.

Pero no nos vayamos por las ramas. Aquí el "truco" está en que recogemos los campos de la BBDD con <%#Eval("Foro_Titulo") %>

ButtonField y CommandField
Estos dos tipos de columnas las explicaremos en el artículo sobre el gridview en que hablemos de cómo editar, borrar y elegir una fila del gridview.


¡Espero que os haya sido útil el artículo!



¿Qué son las funciones estáticas y cuando las uso?

Para los usuarios más avanzados esta pregunta puede parecer bastante obvia, pero cuando no tienes muy claro qué son las funciones marcadas con static y buscas información al respecto, te das cuenta de que para la mayoría de tutoriales en general es tan obvio que te quedas igual cuando te lo explican.

Por tanto, yo voy a intentar explicarlo tan mascado como me hubiera gustado que me lo explicaran a mí.

Cuando marcas una función como estática no hace falta que instancies su clase para acceder a ella . Bueno, ahora en serio, vamos con los ejemplo .

claseOperaciones.cs
    public class claseOperaciones
    {
       public int Suma1(int x, int y)
       {
          return x + y;
       }
   
       public static int Suma2(int x, int y)
       {
          return x + y;
       }
    }


Ambas funciones de claseOperaciones hacen exactamente lo mismo, sin embargo, uno es estática y la otra no. Veamos como usamos cada una de ellas:

ejemploUso.aspx.cs
    int x = 2, y = 5;
   
    // Ejemplo de uso de la función NO estática
    claseOperaciones opera = new claseOperaciones();
    int total1 = opera.Suma1(x, y);

    // Ejemplo de uso de la función estática
    int total2 = claseOperaciones.Suma(x, y);


Ahora cobra sentido la frase en que se decía que para usar una función no estática hay que instanciar primero la clase (claseOperaciones opera = new claseOperaciones();) para poder usar la función (int total1 = opera.Suma1(x, y);), mientras que para usar la función estática basta con int total2 = claseOperaciones.Suma(x, y);, donde la clase no se instancia.

¿Cuando usar funciones estáticas y en qué caso? Pues depende mucho de la funcionalidad que se le vaya a dar. Por ejemplo, en el caso de la suma de la que estamos hablando, yo utilizaría funciones estáticas, pues no me aporta nada inicializar la clase. Simplemente quiero usar una función que está almacenada en la claseOperaciones.

Un caso en que no utilizaría funciones estáticas sería en la típica clase "person" que tanto les gusta dar como ejemplo a los americanos en la que se calcula la edad de esa persona, su sueldo, se devuelve el nombre y apellidos juntos y muchas otras cosas:

Person.cs
    class Person
    {
        private string myName ="N/A";
        private int myAge = 0;

        public string Name
        {
            get
            {
                return myName;
            }
            set
            {
                myName = value;
            }
        }

        public int Age
        {
            get
            {
                return myAge;
            }
            set
            {
            myAge = value;
            }
        }

        public string NombreYEdad()
        {
            return "Name = " + Name + ", Age = " + Age;
        }

        public Person()
        { }

       public Person(string Nombre, int Edad)
       {
          this.Name = Nombre;
          this.Edad = Edad;
       }


Como vemos, se instancia la clase con un nombre y una edad para la persona, y después utilizamos esos datos para recoger una frase con el nombre y la edad. Ahora ya no es un simple contenedor de funciones, sino que se interactúa con propiedaes que hemos inicializado, de modo que no ha lugar una función estática.

Espero que os haya aclarado un poco las ideas!!