Precaución: las startups de software no son tan fáciles como dicen todos

Published 34 weeks, 2 days ago
Wed Nov 07 2007

Lo que viene a continuación es un artículo traducido. Habiendo conversado ayer con Jomaweb, y habiendo leído su artículo sobre la Burbuja 2.0 en la que ambos creemos estar inmersos ahora mismo, aunque el mercado y los MBAs lo nieguen; no he podido por menos que admirar la sincronía que ha llevado a Rob Walling a escribir un artículo al respecto a medio mundo de distancia. Le he pedido permiso para traducirlo, como ya hice con otro artículo suyo y aquí lo tenéis:

Hace unos meses tuve una conversación con un amigo mío triunfador (por triunfador me refiero a que tiene 29 años, trabaja por su cuenta, gana un salario de seis dígitos y tiene dos casas en el norte de California). Dirige su propia compañía de software. Discutíamos sobre lo que hace falta para ser un emprendedor en el campo del software y en un momento de introspección me dijo: "No tengo tanto éxito como esperaba. Creía que el éxito sería más fácil."

Un chico listo
Cuando eres joven la gente te lanza cumplidos a diestro y siniestro.
Si te lavas las manos, te aplauden.
Si te atas los zapatos, te ovacionan.
Si te lees el libro Guinness de los Récords de cabo a rabo... bueno, eso hará que te peguen los niños de preescolar... pero ya me entendéis.

Cuando eres joven, la gente te dice que el éxito es fácil para evitar que te sientas desanimado. Te dicen que cualquier cosa es posible si trabajas lo bastante duro. Según vas haciéndote mayor, si lees cualquier cosa aparte de lo que te mandan en la escuela ya sabes más que tus compañeros. Acabas en clases con prefijos con "Máster" o sufijos como "Avanzado". Y empiezas a sentirte listo.
En algún momento comienzas a ver anuncios o publirreportajes que te dicen lo sencillo que es hacer dinero invirtiendo en cancelación de hipotecas, o en tu propio negocio, o en internet. Pero el cebo es obvio. (Te daré una pista, empieza con "Pero espera, ¡aún hay más!").
Y escuchas las historias de tu fracasado Tío Eduardo que terminan diciendo "... inventó el churriflurri digital y se convirtió en millonario de la noche a la mañana. ¡Lo que hubiera dado por ser él!" Por supuesto, lo que tu tío Eduardo no te cuenta son los miles de horas que el inventor estuvo trabajando solo en su sótano, preparándose para el éxito "de la noche a la mañana". Eso arruinaría la historia.

La Conspiración de las Startup
Tus padres, tus profesores, la televisión, internet y tu tío Eduardo han inculcado en tu cerebro que ser listo y tener una idea es suficiente para garantizarte un lugar en la portada de Gaceta de los Negocios Farolero's 2.0 Fast Company. Parece haber una suerte de conspiración que dice que el éxito de las startup es sencillo; que es algo que te entregan como un collar de flores hawaiiano cuando te bajas del avión en el aeropuerto de Honolulu.
Pero no es sencillo. Es muy difícil. Es una combinación de enormes cantidades de trabajo duro, inteligencia y suerte. Cualquiera que te diga lo contrario te está intentando vender algo. Aunque sólo sea una suscripción a una revista.

Tu posibilidad del 2%
Si vuelvo a leer una historia más sobre aplicaciones Facebook le arreo a alguien en todo el Facebook.
Da la impresión de que cada vez que abro mi lector RSS veo citas como ésta (de un reciente artículo de Fast Company): "Creada en una semana por dos hermanos en la India, [su aplicación para Facebook] se expandió como la pólvora, con medio millón de usuarios apuntándose en las primeras 10 semanas."
Este es sólo uno de entre unos 20 artículos que he leído sobre el fenómeno de las aplicaciones para Facebook. Cada uno tiene numerosos ejemplos de aplicaciones construidas en cuestión de días que ahora poseen enormes cantidades de usuarios.
¡Super empuja a tu amigo!
¡Tírale una oveja a tu amigo!
¡Tírale mierda virtual a tu amigo!
Eso no es suerte, es genio puro... en serio.
Un desarrollador animoso lee estos artículos y piensa "Quiero escribir una aplicación que consiga medio millón de usuarios en 10 semanas. ¡Voy a hacer lo mismo!" Así que se tira una semana en su sótano y termina una aplicación Facebook. Cuando la sube se encuentra con unos pocos miles de usuarios y con apenas los suficientes ingresos por anuncios mensuales como para comprarse un Frapuccino. ¿Por qué?
Porque la inmensa mayoría del tráfico (87%) va a 84 de las 5.000 aplicaciones existentes actualmente para la plataforma. Eso es menos del 2%. Ni uno sólo de los artículos que he leído menciona esta proporción, o el enorme factor suerte relacionado con crear una aplicación para Facebook "exitosa".

Suerte por un Tubo
Ya puedo oír a alguno pensando:
"¿Y qué hay de HotOrNot y PlentyOfFish? Sus fundadores se forran, no parecen excepcionalmente listos, y no se mataron a trabajar. Para ellos fue sencillo..."
Hot Or Not y Plenty of Fish son los ejemplos perfectos del viejo sueño de generar enormes cantidades de dinero con poco esfuerzo (si no estás familiarizado con ellos, son dos sitios de contactos que se convirtieron en superéxitos "de la noche a la mañana" y que permiten a sus dueños ganar millones de dólares al año por más o menos 10 horas de trabajo a la semana).
Si escuchas sus historias y comparas la cantidad de suerte con la cantidad de talento y trabajo duro que hizo falta para que sus sitios tuvieran éxito... la cantidad de suerte es impresionante. No sólo el timing fue absurdamente fortuito y sus ideas no excepcionalmente creativas, además los sitios web en sí mismos fueron bastante sencillos de construir. Parece que ambos se encontraron con una especie de anomalía extraña en el tejido temporal que ha ocurrido... hasta donde puedo ver... exactamente en dos ocasiones.
Si crees que todo lo que hace falta para construir una compañía de software es un sitio web chulo, una aplicación bonita y un enlace desde GigaOm... por favor, piénsatelo mejor.
La web está repleta de aplicaciones lanzadas por gente que pensó que sería así de sencillo. Leen acerca de Hot or Not, construyen y lanzan una aplicación, reciben un vínculo o dos de un bloguero de élite y cuando el dinero no empieza a llegar, abandonan. Y ahora venden el fruto de sus esfuerzos en Sitepoint.
No dejes que eso te pase a tí.

Moraleja
Hay excepciones a la regla las startups de software son difíciles. Pero si tu plan es tener éxito con una idea estilo lanzar ovejas virtuales, más te valdría seguir currando tus 40 horas semanales y gastarte el sueldo en comprar billetes de lotería.
Si quieres tener alguna oportunidad de éxito deberías evitar en todo lo posible el intentar copiar las historias de "una entre un millón". Tus oportunidades de tener éxito son un orden magnitud mayores con una idea aburrida como bug tracking o software de facturación que con el nuevo sitio web social. Tu pregunta a los chorrocientos sitios de contactos gratuitos que se lanzaron después de que Plenty of Fish tuviera éxito.
Puedes malgastar tu tiempo soñando con el éxito y pensando que si tuvieras tan sólo una idea brillante ganarías millones, tu cara en la portada de Sexy Startup Magazine, tendrías un Ferrari ecológico, y esa hermosa/o mujer/marido trofeo. O puedes agachar la cabeza, convertirte en un experto en tu campo, trabajar a destajo y; algún día, quizás... tendrás tu ración de éxito.


Artículo Copyright © 2007 Rob Walling

Este artículo aparece originalmente en el blog Software por Rob: El lado humano del desarrollo de software.

Sobre el autor
Rob Walling es un programador .NET que vive y trabaja en New Haven, Connecticut. Su blog, Software by Rob, es leído por 50.000 personas todos los meses; y habla sobre la contratación de programadores, técnicas de entrevista, técnicas de motivación, gerencia de proyectos de software y características de personalidad de los mejores programadores. Su empresa de consultoría, The Numa Group realiza desarrollo en ASP.NET para compañías en todos los Estados Unidos. Se puede contactar con Rob aquí.

Mi opinión

Dejando aparte el hecho de que soy incapaz de entender qué le ve la gente a FaceBook (tengo un perfil, pero en serio que no le veo la gracia); cada vez estoy más convencido de que estamos inmersos en otra burbuja. En España la estamos notando mucho menos, a pesar de ejempos como expone Joma en el artículo que vinculo al principio; pero en Estados Unidos están volviendo a las andadas de ofrecer cantidades millonarias por empresas con modelos de negocio entre tenues e inexistentes, o basados exclusivamente en los ingresos por publicidad. Creo que los ingresos por publicidad estaban bien cuando las fuentes de dichos ingresos estaban diversificadas, pero ahora la mayoría viene de un sólo sitio: Google Ads. ¿Y si Google cierra el grifo, o hace que gotee menos?

Comprimiendo y descomprimiendo ficheros ZIP

Published 78 weeks, 1 day ago
Thu Jan 04 2007
Para comprimir y descomprimir ficheros en formato ZIP tenemos una herramienta muy valiosa a nuestra disposición: la excelente librería SharpZipLib, creada por IC#Code, el mismo grupo de desarrolladores que nos han proporcionado el excelente IDE Open Source para C# SharpDevelop, del que ya os he hablado otras veces.

Lo primero que tenemos que hacer, por lo tanto, es descargar dicha librería a nuestro sistema. Una vez hecho esto, debemos incluirla como referencia en el proyecto de Visual Studio en el que vayamos a usarla. Si creemos que vamos a usarla con relativa frecuencia, lo más cómodo es instalarla en el GAC. Para ello, abrimos la Ventana de comandos de Visual Studio 2005, que encontraremos bajo Herramientas del grupo del menú de Inicio Visual Studio 2005. Cuando lo ejecutamos, se abre una consola que deberemos dirigir a la ruta donde hayamos descargado la librería ICSharpCode.SharpZipLib.dll (por comodidad, yo la tengo en una carpeta propia en C:\Archivos de Programa) y una vez situados ahí escribimos el siguiente comando:

gacutil /i ICSharpCode.SharpZipLib.dll

De esta manera añadimos la librería a la caché global de ensamblados para Visual Studio.

Tanto si añadimos la librería al GAC como si no, debemos referenciarla en nuestro proyecto. Para ello, ya sabéis, una vez creado nuestro proyecto (de consola, Windows o Web) elegimos el menú Proyecto, Añadir Referencia y elegimos la librería en el cuadro de diálogo que nos aparece: en la solapa llamada .NET si hemos incluido la librería en el GAC, o podemos buscarla mediante la solapa Examinar. Una vez incluida la referencia, incluimos la línea

using ICSharpCode.SharpZipLib.Zip;

en nuestro código y ya estamos listos.
Una última aclaración antes de meternos en faena: para hacer más simple este artículo he decidido que los ejemplos de código incluidos funcionen en la consola y he dado por supuestas varias cosas, como rutas y nombres de ficheros. Si quieres que el código fuente te funcione no te limites a cortarlo y pegarlo, debes adaptarlo a las rutas y nombres de ficheros existentes en tu sistema.

Listar Contenido de Ficheros

Vamos a ver primero cómo listar el contenido de un fichero ZIP. Para ello, nos limitamos a declarar una variable llamada zip de tipo ZipFile. Nótese que SharpZipLib hace que un ZipFile esté compuesto de objetos ZipEntry, no de ficheros ni de líneas ni de ninguna otra cosa por el estilo. El objeto ZipEntry va a representar cualquiera de las entidades que pueden estar contenidas en un fichero ZIP, sean un fichero o un directorio. Una vez sabido esto, nos recorremos las entradas del fichero ZIP mediante un bucle foreach y sencillamente mostramos el nombre de la entrada en la consola:

private static void ListarContenidoZip(string sFile) 
{
    ZipFile zip 
= new ZipFile(File.OpenRead(sFile));
    foreach
(ZipEntry entry in zip) 
    {
        Console.WriteLine(entry.Name)
;
    
}
}

Colorized by: CarlosAg.CodeColorizer

Descomprimir Ficheros

La trama se complica. Para descomprimir un fichero ZIP lo abriremos como un objeto ZipInputStream, recorremos sus entradas mediante el método GetNextEntry() y guardaremos el contenido binario de cada entrada en un array de bytes. Dicho array alimentará a un objeto StreamWriter, con el cual crearemos el nuevo fichero, que será una copia exacta de los datos binarios de la entrada del fichero ZIP.

private static void DescomprimirZip(string sFile)
{
    ZipInputStream zipIn 
= new ZipInputStream(File.OpenRead(sFile));
    
ZipEntry entry;
    while 
((entry zipIn.GetNextEntry()) != null)
    {
        FileStream streamWriter 
File.Create(@"C:\Temp\" + entry.Name);
        long 
size entry.Size;
        byte
[] data = new byte[size];
        while 
(true)
        {
            size 
zipIn.Read(data, 0, data.Length);
            if 
(size > 0) streamWriter.Write(data, 0, (int) size);
            else break;
        
}
        streamWriter.Close()
;
    
}
    Console.WriteLine(
"Hecho!!");
}

Colorized by: CarlosAg.CodeColorizer

Comprimir ficheros

Para comprimir ficheros, usaremos un objeto similar al anterior pero de propósito opuesto, el ZipOutputStream. Para mantener la sencillez del ejemplo, he supuesto que queremos comprimir todos los ficheros existentes en la ruta pasada en el parámetro sRuta. Por lo tanto, nos recorremos todos los ficheros existentes en dicha ruta, creamos un nuevo objeto ZipEntry, obtenemos cierta información mediante FileInfo (podríamos haber incluido chequeo de CRC, pero quería mantener el ejemplo sencillo) para asignar dicha información a la nueva entrada, y añadimos la entrada al objeto ZipOutputStream mediante el método PutNextEntry. Ojo, esto crearía una entrada vacía: para realmente introducir los datos que contiene el fichero (sea un fichero de texto o un binario cualquiera) debemos leer sus datos binarios mediante un FileStream como en el ejemplo anterior y entonces invocar el método Write del ZipOutputStream para guardar dichos datos binarios en la última entrada del stream de salida Zip. Una vez recorridos e incluidos todos los ficheros, terminamos y cerramos el objeto ZipOutputStream.

private static void ComprimirZip(string sRuta) 
{
    ZipOutputStream zipOut 
= new ZipOutputStream(File.Create(@"C:\Temp\prueba.zip"));
    foreach
(string fName in Directory.GetFiles(sRuta))
    {
        FileInfo fi  
= new FileInfo(fName);
        
ZipEntry entry = new ZipEntry(fi.Name);
        
FileStream sReader File.OpenRead(fName);
        byte
[] buff = new byte[Convert.ToInt32(sReader.Length)];
        
sReader.Read(buff, 0, (int) sReader.Length);
        
entry.DateTime fi.LastWriteTime;
        
entry.Size sReader.Length;
        
sReader.Close();
        
zipOut.PutNextEntry(entry);
        
zipOut.Write(buff, 0, buff.Length);
    
}
    zipOut.Finish()
;
    
zipOut.Close();
}

Colorized by: CarlosAg.CodeColorizer

Y esto concluye esta pequeña introducción a SharpZipLib. IC#Code mantiene un excelente foro (en inglés) en el que podréis ver más ejemplo de código y más dudas resueltas.

Actualización 5/01/2007.- Jugando un poco con la librería y el Examinador de Objetos de VS 2005, me encuentro con una clase llamada FastZip. Hmmmm. Pues sí, es lo que parece: una clase wrapper para facilitar la compresión y descompresión de archivos mediante SharpZipLib. Por ejemplo, para descomprimir un fichero es tan simple como esto:

private static void DescomprimirZipFast(string sFile)
{
    FastZip fZip 
= new FastZip();
    
fZip.ExtractZip(sFile, @"C:\Temp""");
}

Colorized by: CarlosAg.CodeColorizer
Los tres argumentos del método ExtractZip son de tipo string, y son el nombre del fichero ZIP a extraer, la ruta de destino para los ficheros extraídos y una máscara para el tipo de ficheros que debemos extraer. Ojo, no vale una máscara al estilo "*.txt", debe hacerse mediante una expresión regular. Si queremos incluir todos los archivos existentes en el directorio, debemos dejar este último parámetro como null o como cadena vacía "".

Y para comprimir:

private static void ComprimirZipFast()
{
    FastZip fZip 
= new FastZip();
    
fZip.CreateZip(@"C:\Temp\walls.zip"@"C:\Wallpapers"false".jpg$");
    
Console.WriteLine("Hecho!!");
}

Colorized by: CarlosAg.CodeColorizer
En este caso el método CreateZip admite cuatro argumentos: el nombre y ruta del fichero ZIP a crear, el directorio donde se encuentran los ficheros que queremos comprimir, un parámetro booleano que indica si la compresión es recursiva (incluimos subdirectorios) o no, y por último un parámetro de tipo string que contiene la máscara para elegir el tipo de ficheros que queremos a comprimir. En este caso es todos los ficheros con extensión JPG. Se aplican las mismas excepciones en uso de máscaras que he comentado para ExtractZip.

Introducir y recuperar imágenes en Microsoft Access

Published 117 weeks, 4 days ago
Mon Apr 03 2006
Si hace unos días hablábamos de cómo guardar ficheros binarios en SQL Server, hoy le toca a Microsoft Access. El proceso es similar, aunque para este ejemplo he decidido usar imágenes como los datos binarios que vamos a guardar y recuperar de la base de datos.

Imaginemos que tenemos una sencilla base de datos Access, con una única tabla llamada Images, que tiene la siguiente estructura:

Nombre Tipo
imgID int (Clave autonumérica)
FileName Texto(255)
ImageData Objeto OLE


El campo imgID almacenará una clave única autonumérica para identificar a cada registro. FileName contendrá el nombre del fichero de imagen, e ImageData contendrá la imagen propiamente dicha, en formato binario.

El procedimiento para insertar imágenes en la base de datos Access es como sigue:

private void SaveData(string fileName)
{
    
try
    
{
        
//Creamos un nuevo FileStream a partir del fichero parámetro
        //
        //We create a new FileStream from the file specified as parameter
        
FileStream fs = new FileStream(fileName, FileMode.Open);
        
//Declaramos un array de bytes del tamaño del FileStream
        //
        //We declare a new byte array as big as the FileStream
        
Byte[] data = new byte[fs.Length];
        
//E introducimos los datos del FileStream en el array
        //
        //And we enter the data from the FileStream into the array
        
fs.Read(data, 0, Convert.ToInt32(fs.Length));
        
fs.Close();

        
//Creamos un nuevo DataAdapter, DataSet, CommandBuilder and DataRow
        //para insertar el nuevo registro
        //
        //We create a new DataAdapter, DataSet, CommandBuilder and DataRow 
        //to perform the insertion of the new record
        
if(conn.State !ConnectionState.Open ) conn.Open();
        
OleDbDataAdapter adap 
             new 
OleDbDataAdapter("SELECT * FROM Images", conn);
        
adap.MissingSchemaAction MissingSchemaAction.AddWithKey;
        
OleDbCommandBuilder cmdBuilder = new OleDbCommandBuilder(adap);
        
adap.InsertCommand cmdBuilder.GetInsertCommand();
        
DataSet ds = new DataSet();
        
adap.Fill(ds);
        
DataRow dr ds.Tables[0].NewRow();
        
dr["FileName"fileName
        
dr["ImageData"data//The byte array
        
ds.Tables[0].Rows.Add(dr);
        
adap.Update(ds);
        
conn.Close();

        
//Esta funcion refresca un ListBox para que se refleje 
        //el nuevo registro insertado
        //
        //This function refreshes a ListBox to show the newly inserted record
        
LoadData();
        
MessageBox.Show("Done!\nImage file: " + fileName + 
            " added to the database.");
    
}
    
catch (Exception e)
    {
        MessageBox.Show(e.Message)
;
    
}
}

Colorized by: CarlosAg.CodeColorizer

Y los procedimientos que extrae las imágenes de la base de datos es tal que así:

/// <summary>
/// Coloca en el control Image la imagen recuperada de base de
/// datos por la función LoadImage, en base al índice único de 
/// la imagen en la base de datos.
/// 
/// Sets in the Image control an image recovered from the database
/// via the LoadImage function, thanks to the unique identifier 
/// of the image on the database.
/// </summary>
/// <param name="imgIndex">El identificador único de la imagen 
///en la base de datos</param>
/// <param name="imgIndex">The unique identifier of the image 
///on the database</param>
private void LoadImageControl(string imgIndex)
{
    
try
    
{
        
//Creamos un nuevo stream de memoria con el resultado 
        //de la función LoadImage, que devuelve un array de bytes
        //
        //We create a new MemoryStream with a byte array,
        //returned by the LoadImage function
        
MemoryStream mem = new MemoryStream(LoadImage(imgIndex));
        
//Y establecemos que la imagen del control picImage es una nueva imagen, 
        //a partir del stream en memoria mem
        //
        //And we set the picImage control's image is a new image, 
        //built from the memory stream mem
        
picImage.Image Image.FromStream(mem);
    
}
    
catch (Exception e)
    {
        MessageBox.Show(e.Message)
;
    
}
}

/// <summary>
/// Carga la imagen de la base de datos en un array 
//de bytes y devuelve dicho array
/// 
/// Loads the image from the database to a byte array and returns that array
/// </summary>
/// <param name="imgIndex">El ID unico de la imagen</param>
/// <param name="imgIndex">The unique ID of the image</param>
/// <returns>Devuelve la imagen en un array de bytes</returns>
/// <returns>The image on a byte array</returns>
private byte[] LoadImage(string imgIndex)
{
    
if(conn.State !ConnectionState.Open ) conn.Open();
    
sSQL "SELECT ImageData FROM Images WHERE imgID = " + imgIndex;
    
OleDbCommand cmd = new OleDbCommand(sSQL, conn);
    byte
[] imgData (byte[]) cmd.ExecuteScalar();
    
conn.Close();
    return 
imgData;
}

Colorized by: CarlosAg.CodeColorizer

En cualquier caso, tenéis disponible aquí el código fuente completo, con comentarios en inglés y en español para esta aplicación. Descargadlo y jugad un poco con él.


NOTA.- Más adelante publicaré la forma de compactar y reparar mediante código C# una base de datos Microsoft Access. Aplicaciones como el código fuente que tenéis para descargar lo necesitan, y mucho.

DataGrid de sólo lectura en Windows Forms

Published 117 weeks, 5 days ago
Mon Apr 03 2006
Las DataGrid en Windows Forms permiten al usuario, por defecto, editar los valores que muestran e incluso crear registros nuevos.

¿Y si queremos cargar una rejilla que sólo muestre datos, sin posibilidad de editarlos o de añadir nuevos datos?

Para ello, una de las cosas que podemos hacer es modificar las propiedades del DefaultView de la tabla con la que vamos a alimentar de datos a la rejilla. Por ejemplo, pongamos que tenemos un DataTable dtDatos que va a ser el DataSource de la rejilla. Si modificamos las propiedades AllowDelete, AllowEdit y AllowNew de la vista por defecto de la tabla origen de datos, tal que así:

dtDatos.DefaultView.AllowDelete = false;
dtDatos.DefaultView.AllowEdit = false;
dtDatos.DefaultView.AllowNew = false;
grdDatos.DataSource = this.dtDatos;

Colorized by: CarlosAg.CodeColorizer

la rejilla grdDatos no permitirá al usuario que modifique, borre o añada nuevos registros.

Por otro lado, si lo que queremos es que se seleccione la fila entera de la rejilla al clickar en cualquiera de sus celdas, implementamos el siguiente código en el evento CurrentCellChanged de la rejilla, que se dispara al cambiar de celda seleccionada.

private void grdDatos_CurrentCellChanged(object sender, EventArgs e)
{
    grdDatos.Select(grdDatos.CurrentRowIndex)
;
}

Colorized by: CarlosAg.CodeColorizer

Sencillamente, lo que hacemos es decirle a la rejilla que seleccione toda la fila, sabiendo qué fila es la que tiene que seleccionar gracias a la propiedad CurrentRowIndex.

Guardar ficheros binarios en SQL Server

Published 123 weeks, 5 days ago
Mon Feb 20 2006
En mi proyecto actual, una aplicación ASP .NET, el cliente expresó el deseo de guardar en base de datos las cartas, basadas en plantillas Word y generadas mediante la aplicación. Quieren guardarlas en la base de datos porque, al ser documentos oficiales, tienen que poder volver a imprimirse o visualizarse exactamente igual a como se crearon en su día. Por lo tanto, necesitamos un método para guardar los ficheros Word en la base de datos en formato binario, como stream de datos y ser capaces de poder recuperarlos después para su visualización.

Nota: no es el objetivo de este post la creación de ficheros Word desde código C#. Podéis usar para ello la automatización Word, o componentes de terceros como los publicados por Aspose, que son excelentes.

Vamos a crear una tabla simple en SQL Server para alojar los documentos. Para este ejemplo, la llamaremos DocsBinarios, y tendrá la siguiente estructura:

Campo Tipo Nulos?
DocId Int (identity) No
Documento Image No
NombreDoc VarChar(100) No


Es bastante autoexplicativo: el campo DocId es un campo de clave primaria autogenerado. El campo Documento es el que va a almacenar los streams de bits, es decir los propios ficheros Word en formato binario. El campo NombreDoc almacenará el nombre que se proporcionó originalmente al documento cuando se generó.

Después crearemos un procedimiento almacenado, UploadDocs, que servirá para guardar registros en esta tabla:

CREATE PROCEDURE UploadDoc(@doc AS Image, @nombre AS VarChar(100)) 
AS
INSERT INTO 
DocsBinarios (Documento, NombreDoc) values (@doc, @nombre)
GO

Colorized by: CarlosAg.CodeColorizer

También es bastante autoexplicativo: un simple INSERT INTO que recibe como parámetros un Image y un VarChar

Y éste es el método que guarda los datos en la base de datos:

private void GuardarFicheroBDD(string sRuta, string sFichero)
{
    
//Creamos un nuevo objeto de tipo FileStream para leer el fichero
    //Word en modo binario
    
System.IO.FileStream  fs = new FileStream(sRuta + sFichero,
        System.IO.FileMode.Open)
;
    
//Creamos un array de bytes para almacenar los datos leídos por fs.
    
Byte[] data = new byte[fs.Length];
    
//Y guardamos los datos en el array data
    
fs.Read(data, 0, Convert.ToInt32(fs.Length));
    
//Abrimos una conexion. En este caso los datos de la cadena de
    //conexion a la base de datos se recuperan de una sección del
    //fichero web.config mediante ConfigurationSettings
    
SqlConnection cnn =
       new 
SqlConnection(ConfigurationSettings.AppSettings["conexionBD"]);
    
cnn.Open();
    
//Creamos un comando de tipo StoredProcedure para invocar a
    //UploadDocs
    
SqlCommand cmd = new SqlCommand("UploadDoc", cnn);
    
cmd.CommandType CommandType.StoredProcedure;
   
//Añadimos los parametros esperados y los valores de los mismos
    
cmd.Parameters.Add("@doc", data)//los datos del fichero Word
    
cmd.Parameters.Add("@nombre", sFichero)//y su nombre
    //Ejecutamos el procedimiento almacenado, que inserta un nuevo
    //registro en DocsBinarios con los datos que queremos introducir
    
cmd.ExecuteNonQuery();
    
//Cerramos la conexión y el fichero 
    
cnn.Close();
    
fs.Close();
}

Colorized by: CarlosAg.CodeColorizer

Ahora veamos el método para recuperar esos datos de la base de datos y mostrarlos como un fichero Word:

private void LeerDeBD()
{
    
//Abrimos la conexion, exactamente igual que antes
    
SqlConnection cnn =
        new 
SqlConnection(ConfigurationSettings.AppSettings["conexionBD"]);
    
cnn.Open();
    
//Este es el comando que abre el registro que deseamos. Para este
    //ejemplo abrimos siempre el primer registro, habría que modificar
    // este código para que el método recibiera como parámetro el
    //registro que queremos abrir, claro.
    
SqlCommand comm = new SqlCommand("SELECT * FROM DocsBinarios " +
        " WHERE docId = 1"
, cnn);
    
comm.CommandType CommandType.Text;
    
SqlDataAdapter da = new SqlDataAdapter(comm);
    
DataSet ds = new DataSet("Binarios");
    
da.Fill(ds);
    
//Creamos un array de bytes que contiene los bytes almacenados
    //en el campo Documento de la tabla
    
byte[] bits ((byte[])(ds.Tables[0].Rows[0].ItemArray[1]));            
    
cnn.Close();
    
//Vamos a guardar ese array de bytes como un fichero en el
    //disco duro, un fichero temporal que después se podrá descartar.
    //Para evitar problemas de concurrencia de usuarios,
    //generamos un nombre único para el mismo
    
string sFile "tmp" + GenerarNombreFichero() + ".doc";
    
//Creamos un nuevo FileStream, que esta vez servirá para
    //crear un fichero con el nombre especificado
    
FileStream fs = new FileStream(Server.MapPath(".") +
        @"\DocsGenerados\" 
+ sFile, FileMode.Create);
    
//Y escribimos en disco el array de bytes que conforman
    //el fichero Word
    
fs.Write(bits, 0, Convert.ToInt32(bits.Length));
    
fs.Close();
    
//Para mostrar el fichero, utilizamos una función
    //JavaScript llamada mostrarFichero (que lo único que
    //hace es cargar el fichero especificado)
    //y hacemos que se ejecute en un pop-up.
    
string script "<script languaje='javascript'> ";
    
script +"mostrarFichero('DocsGenerados/" + sFile + "') ";
    
script +"</script>" + Environment.NewLine;
    
Page.RegisterStartupScript("mostrarFichero",script);
}

Colorized by: CarlosAg.CodeColorizer

Y listo. Mediante éste último método el fichero Word se carga desde la base de datos y se muestra en un popup y en todo su esplendor.

Para que tengáis el código completo, ésta es la función JavaScript que carga el fichero generado desde base de datos:

<script language="javascript">
function mostrarFichero(destino) {
window.open(destino,null,"directories=no,height=600,
    width=800,left=0,top=0,location=no,menubar=yes,
    status=no,toolbar=yes,resizable=yes"
)
document.forms(0).submit();
}
</script>

Colorized by: CarlosAg.CodeColorizer

Y éste el método GenerarNombreFichero(), que utiliza la cuenta de ticks del servidor para crear nombres cuasi únicos de fichero.

private string GenerarNombreFichero()
{
    
int ultimoTick 0;
    while
(ultimoTick==Environment.TickCount) 
    {
        System.Threading.Thread.Sleep(
1);
    
}
    ultimoTick
=Environment.TickCount;
    return 
DateTime.Now.ToString("yyyyMMddhhmmss") + "." +
        ultimoTick.ToString()
;
}

Colorized by: CarlosAg.CodeColorizer