ViewState

Una de las diferencias más importantes que había hasta ahora entre la programación de un programa para PC y la programación de una Web, es que en las aplicaciones Web el cliente realiza una petición (de una página), el servidor ejectua el código asociado a la petición y le devuelve un resultado al cliente. A partir de ese momento, el servidor no tiene consciencia de cómo está actuando el cliente.

Para explicarlo mejor, veamos un sencillo ejemplo:

paginaEjemplo.aspx
<asp:TextBox ID="TextBox2" runat="server"></asp:TextBox>
<asp:TextBox ID="TextBox1" runat="server"></asp:TextBox>
<asp:Button ID="Button1" runat="server" Text="Button" OnClick="Button1_Click" />
<asp:Label ID="Label1" runat="server" Text="Label"></asp:Label>   
  

paginaEjemplo.aspx.cs
    protected void Button1_Click(object sender, EventArgs e)
    {
        Label1.Text = TextBox1.Text;
    }


En este ejemplo, cuando el cliente hace clic en el Button1, el servidor devolverá una página en la que en Label1 estará el texto que haya escrito el cliente en TextBox1... pero sucede algo más: en TextBox1 sigue estando el texto que ha escrito el cliente.

Esto, que parece tan normal y que en los programas para PC es tan típico, esconde una complejidad muy grande. Pensémoslo bien... el cliente envío los resultados de un formulario mediante el método POST, el servidor los procesa y devuelve un resultado en forma de HTML, con los controles en el mismo estado que cuando el cliente hizo el envío, en este caso con el TextBox1 rellenado...

Ese es el concepto de ViewState: el mantener el estado de los controles de una misma página entre una ida y venida al servidor. Sí, vale, estamos de acuerdo en que tampoco es que tenga demasiado mérito que el TextBox1 mantenga el valor, de hecho, eso mismo lo podríamos hacer nosotros fácilmente!!

Pero es que ViewState no se queda sólo ahí. Rellenemos, por ejemplo, tanto el TextBox1 como el TextBox2, y comprobamos que aunque al TextBox2 no se le hace alusión alguna en nuestro código, también mantiene el mismo estado... ¿sigue siendo fácil? ¡¡Pues imagínate hacer lo mismo con 20 listas desplegables, 15 cuadros de texto y todos los controles de usuario que quieras!!

Pero, ¿dónde está el truco? Como ya sabrás, lo único que no se puede hacer en programación es magia. Por ejemplo, échale un vistazo al código fuente de esta página (o cualquier otra página ASP.NET), y verás que hay un campo hidden llamado ViewState con un valor incomprensible y generalmente muy largo (depende de la página, de la cantidad de controles de los que haya que controlar el estado, etc.).

En ese valor tan extraño está almacenado el estado de todos los controles de la página (como imaginarás es una codificación de los valores legibles). Pero el ViewState no sólo es eso, el ViewState permite otras muchísimas cosas, como por ejemplo, averiguar si es la primera vez que ejecutamos una página o la hemos llamado antes ya (algo que en código se maneja con el Page.IsPostBack, del que ya escribimos un artículo).

Pero lo que más me gusta a mí es que desde código se puede acceder al ViewState y guardar las variables que quieras en él, incluso de tipos de datos que te hayas inventado tú (siempre que sean Serializables).

Por ejemplo, puedes guardar una variable en ViewState...

        ViewState["ejemplo"] = 21;

... y acceder a su valor cuando has hecho clic en tres botones y has hecho un postback con 2 listas desplegables

        int i = (int)ViewState["ejemplo"];

Sólo tienes que recordar dos cosas básicas:
1.- Hay que parsear el dato al tipo de datos que corresponde cuando leemos el valor del ViewState (en este caso al tipo integer, pero puede tratarse de cualquier tipo de datos)
2.- El ViewState es una variable del ámbito de petición una misma página. Es decir, tú puedes trabajar con el ViewState siempre que estés en la misma página y siempre que lo único que hagas en ella sean postBack. Por ejemplo, el ViewState se resetearía a su valor inicial si estando en una página presionamos el "Go" de nuestra barra de direcciones.

Si lo queremos pensar de otra forma, el ViewState es lo mismo que una variable de Session en el ámbito de una página (los puritanos me pegarán por esta definición )

Por cierto, y para acabar, si quieres que no se guarde el estado de un control, no tienes más que añadirle un atributo a éste: EnableViewState="false"



Trabajar con Excel

Trabajar con Excel en ASP.NET tiene su miga, pero gracias a gente como carlosag, lo tenemos más fácil. Veréis que su página está en inglés, pero habla perfectamente castellano pues es mexicano.

La cuestión es que el hombre es un crack, y que ha creado una librería para trabajar con ficheros Excel en XML a la que no le falta de nada, y cuya única limitación es que no se trabaja con un fichero en Excel en sí, sino que se trabaja con su traducción a XML.

¿Qué quiere decir esto? Pues que por ejemplo no puedes colocar imágenes, pero no te hace falta tener el Excel instalado en servidor para trabajar con el fichero.

Ya sabéis: fórmulas entre hojas, todo tipo de estilos en todo tipo de elementos (fuentes, celdas)... vamos, que la cosa viene "completita" .

Pero eso no es todo, no contento don carlosag, con su obra, también ha creado un generador de código, con el que metiendo como entrada el código XML del fichero Excel correspondiente, te da como salida el código (basado en la librería que ha creado) que crearía dicho XML.

Pero vale ya de presentaciones, para los interesados, visitad su Excel XML writer library, desde donde encontraréis tanto la librería como el generador de código, sin olvidaros daros una vuelta por su página en sí, pues tiene otros proyectos igual de interesantes a disposición del visitante.




Trabajo con imágenes con C#. Redimensionar una imagen.

Tras ponerle el borde a una imagen, y mezclar dos imágenes entre sí, ahora le toca el turno a redimensionar una imagen.

Empezaremos con el código completo, y obviaremos tanto el principio del código como el final, pues ambos están explicados en los dos artículos anteriores.

redimensionaImagen.cs
        public static MemoryStream Redimensiona(Stream imagen, int targetW, int targetH, int resolucion)
        {
            System.Drawing.Image original = System.Drawing.Image.FromStream(imagen);
            System.Drawing.Image imgPhoto = System.Drawing.Image.FromStream(imagen);

            // No vamos a permitir que la imagen final sea más grande que la inicial
            targetH = targetH <= original.Height ? targetH : original.Height;
            targetW = targetW <= original.Width ? targetW : original.Width;

            Bitmap bmPhoto = new Bitmap(targetW, targetH, PixelFormat.Format24bppRgb);

            // No vamos a permitir dar una resolución mayor de la que tiene
            resolucion = resolucion <= Convert.ToInt32(bmPhoto.HorizontalResolution) ? resolucion : Convert.ToInt32(bmPhoto.HorizontalResolution);
            resolucion = resolucion <= Convert.ToInt32(bmPhoto.VerticalResolution) ? resolucion : Convert.ToInt32(bmPhoto.VerticalResolution);

            bmPhoto.SetResolution(resolucion, resolucion);
            Graphics grPhoto = Graphics.FromImage(bmPhoto);
           
            grPhoto.SmoothingMode = SmoothingMode.AntiAlias;
            grPhoto.InterpolationMode = InterpolationMode.HighQualityBicubic;
            grPhoto.PixelOffsetMode = PixelOffsetMode.HighQuality;
            grPhoto.DrawImage(imgPhoto, new Rectangle(0, 0, targetW, targetH), new Rectangle(0, 0, original.Width, original.Height), GraphicsUnit.Pixel);
           
            MemoryStream mm = new MemoryStream();
            bmPhoto.Save(mm, System.Drawing.Imaging.ImageFormat.Jpeg);

            original.Dispose();
            imgPhoto.Dispose();
            bmPhoto.Dispose();
            grPhoto.Dispose();

            return mm;
        }

Lo primero que hacemos, como siempre, es recoger las Images desde los streams de entrada.

Ahora analicemos:

            // No vamos a permitir que la imagen final sea más grande que la inicial
            targetH = targetH <= original.Height ? targetH : original.Height;
            targetW = targetW <= original.Width ? targetW : original.Width;


Lo que estamos haciendo es redimensionar una imagen, pero lo que no queremos es que la imagen final sea más grande que la original. Esto se puede hacer o no, es a gusto de cada uno, pero a mí me gusta así , y además esta parte es bastante didáctica y puede servir para otros campos.

Como vemos, estamos comparando las dimensiones finales que queremos que tenga la imagen con las originales. Si son menores, las dejamos como estaban, pero si son mayores les dejamos el tamaño original.

Hacemos exactamente lo mismo con la resolución con este código

            // No vamos a permitir dar una resolución mayor de la que tiene
            resolucion = resolucion <= Convert.ToInt32(bmPhoto.HorizontalResolution) ? resolucion : Convert.ToInt32(bmPhoto.HorizontalResolution);
            resolucion = resolucion <= Convert.ToInt32(bmPhoto.VerticalResolution) ? resolucion : Convert.ToInt32(bmPhoto.VerticalResolution);

Ahora creamos el Bitmap con la resolución deseada y el graphic a partir de él:

            Bitmap bmPhoto = new Bitmap(targetW, targetH, PixelFormat.Format24bppRgb);

            bmPhoto.SetResolution(resolucion, resolucion);
            Graphics grPhoto = Graphics.FromImage(bmPhoto);


A continuación la configuración típica ya explicada en ponerle el borde a una imagen, y ahora vamos a lo importante:

            grPhoto.DrawImage(imgPhoto, new Rectangle(0, 0, targetW, targetH), new Rectangle(0, 0, original.Width, original.Height), GraphicsUnit.Pixel);

Analicemos los cuatro parámetros básicos:
1.- imgPhoto: la imagen que queremos redimensionar.
2.- new Rectangle(0, 0, targetW, targetH): esto indica las dimensiones del rectángulo final donde se va a colocar la imagen, de modo que el trozo de imagen que se coja se ajustará a este trozo. Fijaos que este es el truco de toda la cuestión.
3.- new Rectangle(0, 0, original.Width, original.Height): esto define el "trozo" del que hablábamos. En este caso queremos que se coja toda la imagen original, pero también podríamos coger el trocito que quisiéramos.
4.- GraphicsUnit.Pixel: indica que estamos trabajando en pixeles siempre.

Ahora ya sólo queda cerrarlo todo, devolver el MemoryStream y ya tenemos la imagen redimensionada!




Trabajo con imágenes con C#. Mezclar dos imágenes

Tras ver en un artículo anterior cómo ponerle el borde a una imagen, ahora le toca el turno a realizar la mezclar de dos imágenes.

Empezamos con el código completo de la mezcla:

mezclaImagen.cs
        public static MemoryStream Mezcla(Stream imagenOrigen, Stream imagenSuperpuesta, double anchorelativo, double altorelativo, double posrelativaX, double posrelativaY, bool relativo)
        {
            // Convierte a image el fichero original y el que vamos a superponer
            System.Drawing.Image original = System.Drawing.Image.FromStream(imagenOrigen);
            System.Drawing.Image superpuesta = System.Drawing.Image.FromStream(imagenSuperpuesta);

            // Decidimos las dimensiones en base a si son relativas o absolutas
            double posicionX;
            double posicionY;
            double ancho;
            double alto;

            if (relativo)
            {
                posicionX = posrelativaX * original.Width;
                posicionY = posrelativaY * original.Height;
                ancho = anchorelativo * original.Width;
                alto = altorelativo * original.Height;
            }
            else
            {
                posicionX = posrelativaX;
                posicionY = posrelativaY;
                ancho = anchorelativo;
                alto = altorelativo;
            }

            // A mezclar se ha dicho
            System.Drawing.Image imgPhoto = System.Drawing.Image.FromStream(imagenOrigen);
            Bitmap bmPhoto = new Bitmap(original.Width, original.Height, PixelFormat.Format24bppRgb);
            Graphics grPhoto = Graphics.FromImage(bmPhoto);

            grPhoto.SmoothingMode = SmoothingMode.AntiAlias;
            grPhoto.InterpolationMode = InterpolationMode.HighQualityBicubic;
            grPhoto.PixelOffsetMode = PixelOffsetMode.HighQuality;
            Rectangle rec = new Rectangle(0, 0, original.Width, original.Height);
            grPhoto.DrawImage(imgPhoto, rec, rec, GraphicsUnit.Pixel);
            grPhoto.DrawImage(superpuesta, new Rectangle((int)posicionX, (int)posicionY, (int)ancho, (int)alto), new Rectangle(0, 0, superpuesta.Width, superpuesta.Height), GraphicsUnit.Pixel);

            MemoryStream mm = new MemoryStream();
            bmPhoto.Save(mm, System.Drawing.Imaging.ImageFormat.Jpeg);

            // Cerramos todo lo cerrable

            original.Dispose();
            imgPhoto.Dispose();
            bmPhoto.Dispose();
            grPhoto.Dispose();

            return mm;
        }


Como vemos, es un ejemplo bastante completito, y no se limita sólo a la mezcla de dos simples imágenes, sino que tiene algunas características extra. Pero empecemos por el principio.

Vemos que las imágenes vienen en forma de stream como parámetros. Podríamos haberlo hecho de cualquier otro modo. Lo primero de todo es convertir a Image tanto el stream correspondiente al fichero de la imagen original como el de la imagen que vamos a superponer:

            // Convierte a image el fichero original y el que vamos a superponer
            System.Drawing.Image original = System.Drawing.Image.FromStream(imagenOrigen);
            System.Drawing.Image superpuesta = System.Drawing.Image.FromStream(imagenSuperpuesta);


Ahora tomaremos nuestra primera decisión. Está claro que queremos que la imagen original sea mezclada con la superpuesta, pero... ¿y si la imagen superpuesta es mayor que la original? Si así fuera y las mezcláramos a pelo, lo que ocurriría es que acabaríamos con un trozo de la imagen superpuesta y no se vería nada de la original. Para eso tenemos el segundo bloque de código.

            // Decidimos las dimensiones en base a si son relativas o absolutas
            double posicionX;
            double posicionY;
            double ancho;
            double alto;

            if (relativo)
            {
                posicionX = posrelativaX * original.Width;
                posicionY = posrelativaY * original.Height;
                ancho = anchorelativo * original.Width;
                alto = altorelativo * original.Height;
            }
            else
            {
                posicionX = posrelativaX;
                posicionY = posrelativaY;
                ancho = anchorelativo;
                alto = altorelativo;
            }


Como vemos, podemos decidir si queremos que la imagen superpuesta sea relativa a la original o no. Por ejemplo, si quisiéramos que la imagen superpuesta se pusiera en el centro de la imagen original con la mitad de las dimensiones de la imagen original, los valores correspondientes serían:

relativo = true;
anchorelativo = altorelativo = posrelativaX = posrelativaY = 0.5; // Ojalá se puediera hacer esto nen C#, ¿no? :P

Ahora viene lo importante: la mezcla. Los pasos previos son los mismos que el artículo anterior ponerle el borde a una imagen, por lo que no hace falta explicarlos. Lo interesante viene cuando llegamos a:

            Rectangle rec = new Rectangle(0, 0, original.Width, original.Height);
            grPhoto.DrawImage(imgPhoto, rec, rec, GraphicsUnit.Pixel);
            grPhoto.DrawImage(superpuesta, new Rectangle((int)posicionX, (int)posicionY, (int)ancho, (int)alto), new Rectangle(0, 0, superpuesta.Width, superpuesta.Height), GraphicsUnit.Pixel);


Como vemos, lo que hacemos es colocar en un Grafics (grPhoto) la imagen original tal cual y sobre ésta colocamos la imagen superpuesta, pero cambiándole las dimensiones a las que hayamos decidido previamente... ups! ¿He dicho redimensionar? Bien, eso vendrá en otro capítulo . Lo importante es que primero colocamos la imagen original dentro del graphics y después lo hacemos con la superpuesta.

Lo siguiente también es lo típico y que ya se explicó en anterior ponerle el borde a una imagen.

¡Espero que os haya servido de ayuda!




XmlTextReader (o cómo leer ficheros XML lo más rápidamente posible)

Desde el principio, uno de los principales puntos a tratar del equipo de ASP.NET ha sido todo lo concerniente a XML. Ahora, con la versión 2.0 la eficiencia a la hora de tratar archivos XML es mucho mayor de la ya de por sí buena en la versiones 1.x.

En este artículo vamos a tratar el XmlTextReader, y para empezar, qué mejorar que un listado de ventajas y desventajas de su uso:

Ventajas:
1.- Es un puntero "forward-only", que simplemente va leyendo hacia adelante y que no carga el fichero entero para ello. Por tanto, es muy eficiente en cuanto a uso de memoria.
2.- En un momento dado, en cualquier parte de la lectura del fichero, el XmlTextReader puede extraer alguno de los elementos del XML de forma independiente.
3.- Es el lector de XML más rápido que podemos encontrar.

Desventajas:
1.- Al ser "forward-only", no podemos ir hacia atrás nunca :)
2.- No puede editar directamente el fichero XML que está leyendo.
3.- Su uso es menos intuitivo que otros elementos que trabajan con XML.

Por tanto, el XmlTextReader es aconsejable usarlo cuando lo único que queremos es simplemente leer un fichero XML, y cuanto más largo sea éste, más se notará la diferencia con respecto a otros objetos de lectura de XML.

Por ejemplo, imaginemos que tenemos el siguiente fichero XML

fichero.xml
<raiz>
    <persona>
       <nombre>
          Juan
       </nombre>
       <apellidos>
          Martínez Martínez
       </apellido>
    </persona>
    ...
</raiz>


Si nosotros queremos averiguar los apellidos de la primera persona que se llame Vicente, procederemos del siguiente modo:

ejemploXML.cs
string apellidosdeVicente = string.Empty;
using (XmlTextReader reader = new XmlTextReader(pathFichero))
{
    reader.MoveToContent();
    reader.ReadStartElement();
    while (reader.Read())
    {
        if (reader.NodeType == XmlNodeType.Element && reader.Name == "nombre")
        {
             if (reader.ReadString() == "Vicente")
                {
                    reader.ReadToNextSibling("apellidos");
                   
apellidosdeVicente = reader.readString();
                    break;
                }
        }
    reader.Close();
}


Lo primero que cabe destacar es que usamos la sentencia using, por lo que se cerrará todo lo necesario y se liberará toda la memoria pertinente en el caso de que haya un error en la lectura. También observamos que cuando terminamos de leer el fichero, cerramos el XmlTextReader.

Con las dos primeras sentencias le decimos a reader que se mueva al nodo raíz de nuestro fichero, y a partir de ese momento, entramos en un bucle while del que, en principio, sólo saldríamos cuando se haya alcanzado el final del documento.

Una vez dentro del bucle entramos en una primera "criba" en forma de sentencia if. Lo que estamos comprobando es que el nodo en que estamos sea del tipo elemento (podría ser un atributo, podría ser el final de un elemento), y si es así comprobar que el nombre de ese elemento sea "nombre" (ojo porque XML es case sensitive).

Si se cumplen ambas condiciones, leemos cuál es el nombre en cuestión, comprobación que hemos puesto en otro if para dejar un código más claro (lo podríamos haber incluído en el if previo). Si realmente se trata de nuestro Vicente, viajamos al elemento "apellidos", leemos estos y salimos del while con una sentencia break.

Cabe hacer hincapié en que:
- reader.ReadString(): lee el valor del elemento en forma de string.
- reader.ReadToNextSibling("apellidos"): lee el siguiente elemento "hermano" con nombre "apellidos". En este caso, podríamos haber puesto ReadToFollowing en lugar de ReadToNextSibling. La diferencia entre ambos es que ReadToFollowing("apellidos") viaja hacia el siguiente elemento de nombre "apellidos", y ReadToNextSibling("apellidos") lo hace sólo cuando ese elemento es su hermano.

¿Qué significa que lea el siguiente elemento hermano?
Dada la estructura de un fichero XML, por nodos hermanos se conoce a aquellos que tienen el mismo padre, jeje .
Es decir, el nodo padre de nombre y de apellidos es persona, por lo que nombre y apellidos son hermanos, pero en la siguiente variación del ejemplo anterior...

<raiz>
    <persona>
       <nombre>
          Juan
       </nombre>
       <maridoDe>
          <nombre>
             María
          </nombre>
          <apellidos>
             García
          </apellidos>
       </padre>
       <apellidos>
          Martínez Martínez
       </apellido>
    </persona>
    ...
</raiz>

... vemos que hay otro elemento apellidos en el nombre y los apellidos de Juan, por lo que
1.- Si hubiéramos puesto ReadToFollowing, hubiéramos viajado a los apellidos de la mujer de Juan, NO a los apellidos de Juan.
2.- Poniendo ReadToNextSibling, no sólo viajamos a los apellidos de Juan, sino que nos ahorramos que el XmlTextReader se piense nada que no sea descendiente directo de "persona". En este ejemplo no importa demasiado, pero imaginad que en lugar de "MaridoDe", tengamos que poner "vecinoDe" con todos los nombres y apellidos de sus vecinos .

Bueno, pues lo dicho, esto es un ejemplillo simplón, pero con XmlTextReader podréis hacer muchas cosas interesantes. Entre ellas, destaco dos cosas:
1.- Se puede recoger el dato en el el tipo de datos que queramos. Es decir, además de ReadString() (el típico), se puede poner reader.ReadElementContentAsInt() o reader.ReadElementContentAsBoolean(), y así con los tipos de datos más típicos.
2.- Con ReadSubtree(), recogemos un XmlReader con el nodo en el que estamos y todos sus descendientes. Por ejemplo, es muy útil para encontrar un nodo sobre el que queremos trabajar y mandarlo a una función auxiliar que lo trate, de modo totalmente independiente al XmlTextReader inicial.



Javascript vs. controles ASP.NET

Imaginaos que queréis que al presionar sobre un Button aparezca un alert por pantalla con el contenido de un TextBox... con controles HTML típicos es muy fácil, pero con los controles de ASP.NET hay que darse cuenta de un par de cosillas para consegir que funcione.

Por cierto, os recomiendo que si lo consideráis oportuno le echéis un vistazo a un artículo previo de título Insertar javascript dinámicamente.

Pero vamos a lo que interesa, lo que quisiéramos es que esto funcionara

estonofunciona.aspx
<script language="javascript">
function mostrarTexto()
{
   alert(document.getElementById('Text1').value);
}
</script>

<asp:TextBox ID="TextBox1" runat="server"></asp:TextBox>
<input id="Button1" type="button" onclick="mostrarTexto()" value="button" runat="server" />


En teoría debería funcionar pero no es así... y si no sabes el motivo, puede darte más de un dolor de cabeza!!
El motivo es que ASP.NET trata al TextBox de modo que acaba presentando al usuario lo siguiente:

    <input name="ctl00$ContentPlaceHolder1$Text1" type="text" id="ctl00_ContentPlaceHolder1_Text1" />

por lo que en teoría la solución será cambiar nuestro código javascript a

    alert(document.getElementById('ctl00$ContentPlaceHolder1$Text1').value);

Lo cual es una chapuza como un piano. Pero vayamos por partes... ¿Por qué ASP.NET cambia el name y el id del código HTML a "ctl00$ContentPlaceHolder1$Text1" si nosotros le hemos explicitado que sea "TextBox1"?

El motivo es muy sencillo, y es que la razón de ser de ASP.NET es que toda la página es un formulario, y cada vez que se va y vuelve al servidor ASP.NET debe poder distinguir entre un TextBox y otro (y por extensión al resto de controles).

De modo que cuando se usa un control de servidor (el TextBox, el DropDownList, etc.) o un control HTML típico con el atributo runat="server" (que es como decirle a ASP.NET que ese atributo debe poder ser tratado en código), ASP.NET les cambia el id y el name de modo que sean únicos en toda la página. Tiene su lógica, ¿no?

Pero bueno, al grano... entonces, ¿cómo lo podemos solucionar?

Bien, pues personalmente uso dos soluciones diferentes, la primera usando el RegisterStartupScript o el RegisterClientScriptBlock explicados ambos en el artículo Insertar javascript dinámicamente y la segunda variando dinámicamente  el atributo onclick del, en este caso, Button1.

En ambos casos, el quid de la cuestión se trata de utilizar la propiedad ClientID común a todos los controles que trata ASP.NET, propiedad que nos devuelve el  id final  e único de el control en cuestión, que ASP.NET presentará al usuario.

A continuación ejemplos de ambas soluciones:

sifunciona1.aspx.cs
    protected void Page_Load(object sender, EventArgs e)
    {
        Type cstype = this.GetType();
        string nombreScript = "alertar";
        ClientScriptManager cs = Page.ClientScript;
        StringBuilder sb = new StringBuilder();
        sb.AppendLine("function mostrarTexto()");
        sb.AppendLine("{");
        sb.AppendLine("alert(document.getElementById('" + Text1.ClientID + "').value);");
        sb.AppendLine("}");

        /*if (!cs.IsClientScriptBlockRegistered(nombreScript))
        {
           cs.RegisterClientScriptBlock(cstype, nombreScript, sb.ToString(), true);
        }*/

        if (!cs.IsStartupScriptRegistered(nombreScript))
        {
            cs.RegisterStartupScript(cstype, nombreScript, sb.ToString(), true);
        }
    }


Lo dicho, este primer ejemplo es clavado al del artículo triplemente mencionado Insertar javascript dinámicamente, por lo que el código no hace falta explicarlo.

En cuanto a la otra opción, que en mi opinión es menos "elegante", cambiamos un poco el código del .aspx y del .aspx.cs:

sifunciona2.aspx
function mostrarTexto(idTexto)
{
   alert(document.getElementById(idTexto).value);
}
</script>
<asp:TextBox ID="Text1" runat="server"></asp:TextBox>
<input id="Button3" type="button" value="button" runat="Server" />


sifunciona2.aspx.cs
Button3.Attributes.Add("onclick", "mostrarTexto('" + Text1.ClientID + "')");

Como veis, no hemos más que añadido una variable de entrada a la función javascript (que NO creamos dinámicamente) y quitar el atributo del Button3 en el .aspx. Mientras tanto en el .aspx.cs no hacemos más que añadir el atributo onclick dinámicamente, de modo que será desde aquí desde donde se pasará el ClientID del Text1... más corto pero más chapucero, ¿no?

Como anexo, os recomiendo esta función para conseguir que el getElemetById de javascript funcione en todos los navegadores:

if(!document.getElementById){
  if(document.all)
  document.getElementById=function(){
    if(typeof document.all[arguments[0]]!="undefined")
    return document.all[arguments[0]]
    else
    return null
  }
  else if(document.layers)
  document.getElementById=function(){
    if(typeof document[arguments[0]]!="undefined")
    return document[arguments[0]]
    else
    return null
  }
}




Devolver un fichero

En ocasiones, queremos hacer que nuestros usuarios descarguen el típico fichero Excel, PDF, Word, etc., pero no queremos que estos se abran en la misma ventana del explorer, sino que se les dé la opción de guardar el fichero o que se abra en la aplicación correspondiente.

Conseguirlo es muy sencillo, y para ello no tenemos más que "engañar" al navegador y decirle que lo que se está descargando no es un Excel, PDF o Word en particular, sino que se trata de una serie de bytes que se tiene que bajar sin rechistar

El siguiente código hace esa función:

octet.cs
public static void descarga(string filepath, string filename)
{
            Response.Clear();
            Response.ContentType = "application/octet-stream";
            Response.AddHeader("Content-Disposition", "attachment; filename=" + filename);
            Response.Flush();
            Response.WriteFile(filepath);
            Response.End();
}


Esta función se puede ejecutar desde donde queramos (lo típico es llamarlo cuando el usuario presiona un botón). básicamente, lo que hace es:

1.- Limpia el contenido de salida.
2.- Le cambia el contentType a tipo octet... aquí es donde "engañamos al navegador".
3.- Le añadimos la cabecera Content-Disposition y le damos un nombre al fichero. Esto es opcional, y lo que hace es dar el nombre que queremos que aparezca si el usuario decide guardar el fichero.
4.- Manda la info que tenemos hasta ahora (la única cabecera que hemos añadido) a la salida hacia el usuario.
5.- Mandamos el fichero en sí desde Response.WriteFile(filepath), donde, obviamente, filepath es el path interno del fichero en nuestro servidor.
6.- Enviamos todo y terminamos la ejecución de la página.



Trabajo con imágenes con C#. Ponerle un borde a una imagen

Trabajar con imágenes en C# para ASP.NET no es demasiado difícil, pero ni mucho menos trivial.

Los namespaces que se suelen utilizar para el trabajo con imágenes son System.Drawing, System.Drawing.Imaging y System.Drawing.Drawing2D.

Como siempre, un ejemplo vale más que horas de teoría, por lo que a lo largo de una serie de tres artículos estudiaremos los siguientes ejemplillos:
1. Ponerle el borde a una imagen
2. Mezclar dos imágenes
3. Redimensionar una imagen.

1. Ponerle el borde a una imagen.
El código completo del ejemplo es el siguiente:

trabajaImagenes.cs
        public static MemoryStream Borde(Stream imagen, Color color, float grosor)
        {
            System.Drawing.Image original = System.Drawing.Image.FromStream(imagen);
            System.Drawing.Image imgPhoto = System.Drawing.Image.FromStream(imagen);

            Bitmap bmPhoto = new Bitmap(original.Width, original.Height, PixelFormat.Format24bppRgb);
            Graphics grPhoto = Graphics.FromImage(bmPhoto);
            grPhoto.SmoothingMode = SmoothingMode.AntiAlias;
            grPhoto.InterpolationMode = InterpolationMode.HighQualityBicubic;
            grPhoto.PixelOffsetMode = PixelOffsetMode.HighQuality;

            Rectangle rec = new Rectangle(0, 0, original.Width, original.Height);
            grPhoto.DrawImage(imgPhoto, rec);
          
            Pen pen = new Pen(color, grosor);
            grPhoto.DrawRectangle(pen, rec);

            MemoryStream mm = new MemoryStream();
            bmPhoto.Save(mm, System.Drawing.Imaging.ImageFormat.Jpeg);

            // Cerramos todo lo cerrable
            original.Dispose();
            imgPhoto.Dispose();
            bmPhoto.Dispose();
            grPhoto.Dispose();

            return mm;
        }


Lo que hace este código es recibir el Stream de una imagen y dos parámetros de configuración del borde: su color y su grosor, de modo que se devuelve en un MemoryStream la imagen con el borde deseado.

El primer paso es recoger dos copias independientes de System.Drawing.Image.

            System.Drawing.Image original = System.Drawing.Image.FromStream(imagen);
            System.Drawing.Image imgPhoto = System.Drawing.Image.FromStream(imagen);


Lo pongo con el namespace entero porque es habitual equivocar este tipo Image con el WebControl  Image. De todos modos, este no es necesario hacerlo siempre que no se importe el namespace System.Web.UI.WebControls.

El segundo paso es crear un Graphics desde un Bitmap con las dimensiones originales de la imagen que pasamos como parámetro.

            Bitmap bmPhoto = new Bitmap(original.Width, original.Height, PixelFormat.Format24bppRgb);
            Graphics grPhoto = Graphics.FromImage(bmPhoto);

Las tres siguientes líneas de código no son más que opciones de configuración del Graphics merecedoras de un artículo entero. De momento nos vale con saber que son las habituales:

            grPhoto.SmoothingMode = SmoothingMode.AntiAlias;
            grPhoto.InterpolationMode = InterpolationMode.HighQualityBicubic;
            grPhoto.PixelOffsetMode = PixelOffsetMode.HighQuality;

Ahora viene lo interesante, pues con grPhoto.DrawImage vamos a crear realmente la imagen. Como vemos, utilizamos la función de dos parámetros, donde el primero es la imagen que vamos a dibujar y el segundo define la dimensión final de la imagen mediante un rectángulo:

            Rectangle rec = new Rectangle(0, 0, original.Width, original.Height);
            grPhoto.DrawImage(imgPhoto, rec);
  

Hasta este momento no tenemos más que la misma imagen que al principio, y es ahora cuando le ponermo el borde definiendo el Pen con el color y grosor especificados y finalmente haciendo un DrawRectangle con ese Pen y el Rectangle definido anteriormente

            Pen pen = new Pen(color, grosor);
            grPhoto.DrawRectangle(pen, rec);

Ahora ya no nos queda más que guardar el resultado en un MemoryStream, cerrar todo lo cerrable y devolver dicho MemoryStream.

            MemoryStream mm = new MemoryStream();
            bmPhoto.Save(mm, System.Drawing.Imaging.ImageFormat.Jpeg);

            // Cerramos todo lo cerrable
            original.Dispose();
            imgPhoto.Dispose();
            bmPhoto.Dispose();
            grPhoto.Dispose();

            return mm;


Pues ya está, espero que os haya servido de ayuda. Próximamente publicaré los artículos con los ejemplos prometidos para aclarar un poco más las ideas y dejar que, como siempre, sea vuestra imaginación la que os limite, no el desconocimiento de las herramientas.