Archive for the ‘De Todo’ Category

Lidiando con errores de validación al renderear un control a HTML (reportes por Mail III)

Tuesday, October 14th, 2008

En el post anterior vimos lo facil que es hacer uso del Control.RenderControl() para exportar nuestras paginas a un formato HTML. Sin embargo, este metodo nos va a generar errores dependiendo del uso creativo que estemos empleando.

El primero es un error que ocurrirá al intentar hacer uso del metodo que ya vimos:

RegisterForEventValidation can only be called during Render()

Al parecer el problema es que estamos rendereando un control “fuera de tiempo” para ASP.Net. Para omitir este problema habra que deshabilitar la validación de los controles para esta pagina, poniendo en el encabezado la siguiente directiva:

<%@ Page Language=”C#” AutoEventWireup=”true” enableEventValidation =”false”….

Desconozco los efectos secundarios que puede tener deshabilitar la validación de eventos para la pagina, asi que recomiendo usarlo con cuidado, y en todo caso, tratar de aislar en la medida de lo posible el control a exportar.

El segundo problema viene al tratar de exportar mas de un control. Ya encarrerados, ¿por que no meter varios controles, digamos en un asp:Panel y al final renderear este ultimo?

Bueno, pues eso es precisamente lo que estaba intentado cuando me tope con este problema:

The control must be placed inside a form tag with runat=server

Al parecer, a ASP.Net no le gusta que rendereemos controles fuera de una forma. Y de esto se encarga un metodo de las formas llamado VerifyRenderingInServerForm.

La forma recomendada es simplemente sobreescribir este metodo. La forma mas sencilla y practica (aunque algo salvaje) de hacerlo, es simplemente crear nuestra propia clase que sobreescriba este metodo:

public class EmailReady : System.Web.UI.Page
{
public override void VerifyRenderingInServerForm(Control control)
{

}
}

De manera que el metodo ahora no hace nada (usese bajo su propio riesgo).

Para que esto funcione, tendremos que cambiar la herencia de la clase de nuestra pagina, de

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

a

public partial class Pagina : EmailReady

Y nuestro método estara funcionando sin problemas.¬¬

Exportando datos a Excel o a HTML (reportes por Mail II)

Tuesday, October 14th, 2008

En la antigua antiguedad, cuando queria exportar información a Excel  de una pagina Web, usaba el metodo de recorrer el control con los datos, escribir un csv en el servidor, y luego redireccionar al usuario al archivo recien creado (tenia incluso una rutina para hacer esto con GridViews, que se perdio en la base de codigo de mi antiguo trabajo). Un csv es un formato estandar que permite ser abierto en Excel, y aunque no tiene formato, pues muestra facilmente la información. Sin embargo, descubri que hay una forma mucho mas sencilla de lograr esto, con la ventaja de que podemos darle formato sin problemas.

Resulta que los controles de ASP.Net tienen la funcionalidad de poder escribirse a si mismos en formato HTML. Esto se logra con la funcion RenderControl. Esto nos da la posibilidad de ponernos creativos para exportar nuestros datos.

Si queremos que un control en particular sea descargable a Excel (que lee formato HTML) tendremos que hacer lo siguiente (lamento mucho no poder dar crédito al sitio donde encontre esta información, tiene que ser uno de estos):

Response.Clear();
Response.AddHeader(”content-disposition”, “attachment;filename=FileName.xls”);
Response.Charset = “”;

// If you want the option to open the Excel file without saving then
// comment out the line below
// Response.Cache.SetCacheability(HttpCacheability.NoCache);
Response.ContentType = “application/file.xls”;

System.IO.StringWriter stringWrite = new System.IO.StringWriter();
System.Web.UI.HtmlTextWriter htmlWrite = new HtmlTextWriter(stringWrite);
ControlAExportar.RenderControl(htmlWrite);

Response.Write(stringWrite.ToString());
Response.End();

Con un uso creativo del objeto Response, ni siquiera tenemos que guardar nuestra información como archivo en el servidor. Ahora bien, en nuestro objeto stringWrite lo unico que tenemos el HTML de nuestro control. Asi que, usando lo que aprendimos en el post anterior, nada nos impide enviar la misma información por correo en lugar de crear un archivo de Excel. Basta con omitir el uso del objeto Response del codigo mostrado.

Sin embargo, este metodo no funcionara como tal, pues el ASP.Net hace algunas validaciones que estamos violando, de manera que hay que hacer uso de un par de hacks. De estos hablare en el siguiente post.¬¬

Mandando mails con formato HTML desde ASP.Net (reportes por mail I)

Tuesday, October 14th, 2008

Recientemente me salio la necesidad de enviar mails con diversos tipos de datos desde una aplicación en ASP.Net, y me encontre con varios problemas interesantes para resolver, los cuales tratare en varias partes.

Primero esta el problema de mandar un correo. Por fortuna el .Net framework nos da herramientas para lograr esto de manera muy sencilla.

 MailMessage mail = new MailMessage();
mail.From = new MailAddress(Properties.Settings.Default.MailAddress);
mail.To.Add(To);
mail.CC.Add(CC);
mail.Bcc.Add(CCO);
mail.Subject = Subject;
mail.Body = Body;
SmtpClient SMTPsender = new SmtpClient(Properties.Settings.Default.MailServerAddress);
SMTPsender.UseDefaultCredentials = false;
SMTPsender.Credentials =
new System.Net.NetworkCredential(
Properties.Settings.Default.Usuario,
Properties.Settings.Default.Password,
Properties.Settings.Default.Dominio);
SMTPsender.Send(mail);

Esta solución tan sencilla nos deja enviar correo en formato de solo texto. Pero ¿que pasa si queremos darle mas formato?. Tenemos que recurrir al HTML. Para ello, solo tenemos que hacer dos cosas diferentes:

  1. Especificar que el cuerpo de nuestro correo tiene formato HTML
  2. mail.IsBodyHtml = true;

  3. Formatear la cadena que enviamos con las etiquetas correspondientes.

Pero, ¿que seria de nuestro correo con bonito formato Html si no tiene imagenes?

Para lograr imagenes dentro de un correo simplemente tenemos que usar el MIME. En nuestro codigo HTML vamos a usar la siguiente sintaxis:

<img alt=”Imageb” src=”cid:idDeLaImagen”/>

Asegurandonos que idDeLaImagen sea unica para este correo.

Posteriormente, vamos a agregar esa imagen a nuestro correo haciendo uso de los AlternateViews

//Agregamos como recurso ligado, el logo del proyecto.
LinkedResource lr = new LinkedResource([la ruta de nuestra imagen]);
lr.ContentId = “idDeLaImagen”;
//Incluimos la vista en HTML como vista del correo a enviar.
AlternateView av = AlternateView.CreateAlternateViewFromString
(body, null, System.Net.Mime.MediaTypeNames.Text.Html);
av.LinkedResources.Add(lr);
mail.AlternateViews.Add(av);

Esto nos permitira que los datos que adjuntamos se muestren como parte del correo y no como adjuntos.

Un par de aclaraciones pertinentes:

  1.  Las lineas que dicen Properties.Settings.Default… implican que esos datos los estoy obteniendo del archivo de configuración. Una forma muy util de guardar caracteristicas de nuestro sistema.
  2. Resulta que descubri que es legal usar como dirección para el SmtpClient la dirección del servidor Exchange de mi empresa. Desconozco la razon, pues se que Exchange no usa precisamente el mismo protocolo SMTP. Mi teoria es que los servidores Exchange (no se si por default) tienen ademas abierto el servicio SMTP. Por eso hago uso de las NetworkCredential, con cualquier usuario valido del dominio se puede enviar correos. Muy útil en ambientes empresariales con Exchange y ActiveDirectory.

Las siguientes partes se trataran de como enviar datos directo de una pagina web por correo, ya sea como HTML o como un archivo adjunto de Excel, y ademas, de como lograr que los metodos propuestos funcionen :-P ¬¬

El extraño caso de SQL server management studio y la pantalla azul

Friday, October 3rd, 2008

Pese a los detractores, yo soy un feliz usuario de windows XP (y estoy determinado a no usar Vista). Una de las razones es por su estabilidad. Dificilmente veo que mi equipo se congele, o peor aun, las tan populares “Blue screen of death” o BSOD. Sin embargo, desde que cambie de empleo, comenze a tener el problema de las pantallas azules cada vez que usaba el SQL Server Management Studio 2005.

En concreto, cada que habria una tabla (comando Open Table) era como jugar a la ruleta rusa. La pantalla azul aparecia, yo calculo que en uno de 3 intentos.

Intente muchas soluciones, desde actualizar, reinstalar, aplicar parches… pero nada funcionaba. Hasta que al fin, despues de husmear un poco en Google, encontre una pagina de Microsoft donde se leian las siguientes e insignificantes lineas:

 As workaround states, removing iPoint mouse driver should alleviate the problem.
But why? Why should a mouse driver cause a database management application to BSOD in the Win32 kernel ?!

Un poco incredulo, use las palabras correctas en Google, y BUM! resulto que es un problema mucho muy comun. En concreto: El software intellipoint (version 6.X.X) que sirve para controlar los apuntadores de Microsoft) al parecer tiene un bug que causa una pantalla azul al usar El SQL Server Management Studio en laptops con procesadores Intel de doble core.

No entiendo por que. Bueno, si entiendo, tiene que ver algo con el Kernell. Sin embargo me parecio una combinacion muy extraña. La bendita solucion es desinstalar Intellipoint. Sin embargo, a mi me gusta mucho mi mouse de Microsoft y la configuración de teclas que permite, de manera que no quiero desinstalarlo. Solución alterna: Mata el proceso ipoint.exe cada vez que vayas a usar el Management Studio. Un poco extraño, pero funciona :-)

Unable to find the requested .Net Framework Data Provider

Thursday, February 14th, 2008

Precisamente hablando del MySql Connector para .Net, resulta que cuando quiere uno instalar la aplicacion en un servidor, puede haber un error como el que sigue:

 System.ArgumentException: Unable to find the requested .Net Framework Data Provider. It may not be installed.

Despues de romperme un poco la cabeza, descubrí el origen del problema: El MySQL provider no esta agregado a los DBProviderFactories. Normalmente esto lo hace el instalador del MySql connector, y lo hizo en mi equipo de desarrollo, pero no en el de producción.

La solución mas inmediata es, pues instalarlo en el servidor. Sin embargo, si no quieres o no estas en la posibilidad de meterle mano al servidor, puedes agregar lo siguiente a tu web.config:

<system.data>
<DbProviderFactories>
<add name=”MySQL Data Provider” invariant=”MySql.Data.MySqlClient” description=”.Net Framework Data Provider for MySQL” type=”MySql.Data.MySqlClient.MySqlClientFactory, MySql.Data, Version=5.1.4.0, Culture=neutral, PublicKeyToken=c5687fc88969c44d” />
</DbProviderFactories>
</system.data>

Cuidando unicamente de que la version sea la que corresponda a la de nuestro connector.

También, no esta de mas checar que la dll de MySql.Data vaya en la carpeta bin ;-)

Conectar el .Net framework con MySql (con total soporte para ADO)

Thursday, February 14th, 2008

Recientemente me salio la necesidad de manipular algunos datos a una base de MySql en lugar del ya tradicional SQL Server. Mi primera opcion fue hacer mi desarrollo en PHP. Pero luego se me ocurrio buscar una forma limpia de conectar el ASP.Net con MySQL. Afortunadamente, si la hay. El MySql Connector/Net

La mas nueva version (5.1), ya esta perfectamente integrada con el Visual Studio 2005. Y manipular las bases de datos exactamente de la misma manera que ya estamos acostumbrados. Les recomiendo que lo prueben!

Un bonito y util ejercicio

Wednesday, December 12th, 2007

En días pasados, y por la época, me salio la necesidad de organizar un intercambio (si, esos en que se reúnen un grupo de amigos y/o conocidos y se intercambian regalos entre ellos). Como tenia la necesidad de que fuera secreto, decidí que la mejor solución (y la mas Geek), sería crear un programita que hiciera el sorteo por mi, y enviara correo a los interesados sin que yo me enterara de quien le dará a quien. El programita me represento algunos retos pequeños pero divertidos. Quiza lo mas divertido de aprender fue como funciona la clase Random y como podemos mandar Mails usando el SMTP de GMail.

No suena a demasiado dificil hacer un sorteo de intercambio programaticamente. Y en realidad, lo es. Solo que el generador de numeros aleatorios del .Net Framework no es realmente aleatorio. Veran.

Para usarlo empleamos una sentencia mas o menos asi:
Random rnd = new Random(int base)
Donde base es el limite superior para los aleatorios. Para generar un numero aleatorio de una serie (ojo, dije serie) usamos
rnd.Next();
El asunto esta en que, si siempre le damos la misma base, siempre obtendremos los mismos resultados. No podemos crear un objeto Random cada que lo queramos usar, tenemos que usar uno solo y dejar que solito “randomize” la serie. Despues de algunas aproximaciones, lo que termine por hacer es darle una base de los milisegundos de la hora.
Random rnd = new Random(DateTime.Now.Millisecond)
Y como necesitaba que los numeros aleatorios estuvieran dentro de un rango (el tamaño de la lista de participantes), simplemente use el modulo:
rnd.Next() % elQueDa.Count //elQueDa es un ArrayList con mi lista de participantes.
Una vez solucionado ese asunto, tenia que hacer que enviara los mails a los participantes. Para ello, use la bendita clase SmtpClient del framework. En realidad es muy facil de usar. Mi codigo se explica solo:
System.Net.NetworkCredential credential = new System.Net.NetworkCredential
(Properties.Settings.Default.SmtpUserName,Password);
SmtpClient senderClient = new SmtpClient(Properties.Settings.
Default.SmtpAddress,Convert.ToInt32(Properties.Settings.Default.SmtpPort));
senderClient.EnableSsl = Properties.Settings.Default.SmtpEnableSsl;
senderClient.UseDefaultCredentials = false;
senderClient.Credentials = credential;

MailMessage Mail = new MailMessage();
Mail.From = new MailAddress(Properties.Settings.Default.MailFromAddress);
Mail.Subject = Properties.Settings.Default.MailSubject;
senderClient.Send(Mail)
Unicamente hay que preocuparse de cachar las excepciones adecuadas. Por si les paso de noche, Properties.Settings.Default es la forma de acceder a las propiedades guardadas en el archivo de configuracion, asi que no busquen en la documentacion propiedades como SmtpEnableSsl por que yo las di de alta asi en los Settings.

Hay que recordar que GMail trabaja con SSL. Para ello la linea de
senderClient.EnableSsl = Properties.Settings.Default.SmtpEnableSsl;
Y tambien que hay que configurar el puerto (465 o 587, si uno da TimeOut, intentas con el otro).

Y ya, la verdad es que es muy facil hacer este tipo de cosas con el .Net Framework. Si le quieren echar un ojo, aqui encuentran el codigo. Y si les interesa descargarlo para su uso, pueden entrar a esta pagina (hacemos uso de las ventajas del Click Once Deployent). No me esforce demasiado en las validaciones, asi que asegurense de meter bien los datos.

Seleccionar el texto de un Textbox en ASP.Net

Thursday, November 15th, 2007

Ahora que estoy entendiendo el paradigma de ASP.Net (cuando lo conoci me confundi mucho pues no era como trabajar con ASP tradicional pero tampoco como trabajar con windows forms) le estoy tomando mucho cariño.

Un Textbox en ASP.Net no tiene una propiedad o metodo definido para seleccionar el texto dentro de el. Pero no hay problema, por que podemos usar el querido y confiable Javascript para ello.

Una forma sencilla de lograr esto es mediante el uso del metodo Attributes.Add() de nuestra caja de texto. Basta poner el siguiente codigo en el Load del codebehind de nuestra forma:

TxtBusqueda.Attributes.Add("onfocus", "SetSelected();");

Con esto, a la hora de “renderear” nuestro control, en la declaracion veremos algo como esto:

<input name="TxtBusqueda" type="text" ... onfocus="SetSelected();" />

Como vemos, agregamos ese atributo a nuestra caja de texto. Ahora, solamente hay que escribir en el HTML de nuestra forma el codigo adecuado:

<script type="text/javascript">
function SetSelected()
{
document.form1.TxtBusqueda.select();
}
</script>

Como vemos, esto puede ser muy util pues podemos controlar finamente el comportamiento de cualquier control, pudiendo agregar, por ejemplo, comportamiento para otros eventos, asi como atributos que no forman parte de nuestros controles ASP.Net ∞

SOAPopera: Publicando un web service I

Tuesday, September 25th, 2007

Una de los “ultimos” gritos de la moda en cuanto al desarrollo, son los web services. En lo particular, me parecen una tecnologia excelente, por que permiten “consumir” servicios ya construidos sin preocuparte de la plataforma en la que esten construidos. Para muchas aplicaciones, una arquitectura basada en servicios Web puede ser el santo grial… si lo sabes utilizar correctamente.

Hace poco en mi trabajo (empresa X), surgio la necesidad de que la empresa Y trabajara con algunos de nuestros datos para una validacion en una aplicacion propia. Por politicas de X, esta prohibido liberar a externos cualquier base de datos. De manera que necesitabamos que la aplicacion hecha por Y consultara los datos de X sin tener acceso real a los datos… parecia un caso perfecto para los WebServices.

Como buen .Netero, se que es muy facil construir un WebService en VS. De hecho, tengo algunos trabajando en la red interna, sin problemas (notes que dije red interna). Esa es mi experiencia con ellos, asi que sabia que construir un simple WebService que consultara una base de datos y regresara una cadena, no seria problema alguno.

Y no lo fue. Las pruebas estuvieron bien, el servicio parecia que hacia lo que tenia que hacer. Publicamos, y todo bien, de nuevo probe y todo funcionaba bien. Incluso hice un pequeño ejecutable que lo consumia, y de nuevo, todo sin problemas.

Pero a la hora de pedirles a los usuarios que lo probaran, simplemente no se podian conectar. Descartamos tullidez de su parte por que en esta pagina (muy buena para las pruebas, por cierto) tampoco estaba funcionando, asi que el problema caia en mi cancha.

Me pase largas y tediosas horas tratando de decifrar la solucion, pero, al menos ese dia, no lo logre. Despues de mas de 10 horas de cambiar el codigo sin exito comienzas a alucinar, asi que decidi que seria mejor continuar al dia siguiente con la cabeza mas fresca.

Al dia siguiente, inmediatamente note algo extraño (descansar ayuda). La pagina de pruebas de SOAP leia correctamente el WSDL, pero luego, buscaba la funcion en una direccion mas o menos asi:

http://192.168.1.100/servicio/funcion.asmx

En lugar de buscarla en:

 http://empresaX.com/servicio/funcion.asmx

No habia notado nada raro hasta que cai en la cuenta de que la direccion 192.168.x.x es una IP local, no publica. Al leer el WSDL, en efecto, encontre la causa del problema

<wsdl:port name=ServicioSoap binding=tns:ServicioSoap>

<soap:address location=https://192.168.1.100/servicio/funcion.asmx />

</wsdl:port>

El problema radicaba en que el WSDL (que es automaticamente generado), estaba tomando la IP de la maquina en la que residia (lo cual estaria bien si estuviera directo a internet, pero al estar detras de un Router, la cosa se ponia diferente). De manera que en realidad el WSDL hacia que la aplicacion preguntara por una servicio en una direccion que jamas encontraria.

Dar con el problema fue casi tan dificil como dar con la solucion. En mis busquedas practicamente todos compartian el problema, pero ningun la solucion. Encontre muchas soluciones que no funcionaban o no me convencian. Hasta que llege al benito blog de Kirk Allen Evans, donde habia una solucion para un problema similar.

El chiste aqui es  crear una clase derivada de SoapExtensionReflector que nos permitira hacer cambios en el WSDL en tiempo de ejecucion. Debe verse algo asi:

namespace EmpresaX.ServicioWeb
{
    public class LocalAddressReflector : SoapExtensionReflector
    {
        public override void ReflectMethod()
        {
            
        }

        public override void ReflectDescription()
        {
            ServiceDescription description = ReflectionContext.ServiceDescription;
            foreach (Service service in description.Services)
            {
                foreach (Port port in service.Ports)
                {
                    foreach (ServiceDescriptionFormatExtension extension in port.Extensions)
                    {
                        SoapAddressBinding binding = extension as SoapAddressBinding;
                        if (null != binding)
                        {
                            binding.Location = binding.Location.Replace
				(Properties.Settings.Default.LocalAddress, Properties.Settings.Default.PublicAddress);
                        }
                    }
                }
            }
        }
    }
}

Properties.Settings.Default.LocalAddress y PublicAddress no son mas que valores en el archivo de configuracion que corresponden a las direcciones local y publica del servicio. Lo deje en el archivo de configuracion para no tener que recompilar la aplicacion si estas direcciones cambiaban.

Despues, en el archivo de configuracion, En la seccion de Configuration/System.Web hay que agregar lo siguiente (para asegurarnos que nuestra clase sea cargada).

<webServices>

<soapExtensionReflectorTypes>

<add type=EmpresaX.ServicioWeb.LocalAddressReflector, WebService/>

</soapExtensionReflectorTypes>

</webServices>

Cabe destacar que el nombre “localAddresReflector” lo puedes reemplazar por el que tu hayas elegido para tu clase, y que “WebService” no es mas que el nombre del folder (en este caso, raiz) donde se encuentra mi clase (sin eso, no funciona).

Y eso hizo el truco,  cuando lo ejecute, el WSDL ya estaba correcto:

<wsdl:port name=ServicioSoap binding=tns:ServicioSoap>

<soap:address location=https://empresaX/servicio/funcion.asmx />

</wsdl:port>

Cabe mencionar que esto puede ser util no solo para mi caso, sino para cambiar, por ejemplo, “http” por “https” (el caso de Kirk), o bien, para especificar un puerto diferente para el web service (agregando “:1234″ al final de la direccion).

Y ahi tuve la solucion, despues de apenas 15 horas de trabajar en ella. Espero que si llegas a este post en un caso desesperado, te resulte de utilidad.

Por que el boton de borrar no borra (GridView)

Monday, August 27th, 2007

Apenas le estoy agarrando la onda al control DataGridView de .Net Framweork y ahora me estoy peleando con el control GridView de ASP.Net 2.0. Por mucho, me gusta mas la forma en que se maneja esto en windows forms, pero nada es perfecto.

En fin, el punto es que yo ponia bien bonitos mis controles para borrar un registro desde el GridView pero no pasaba nada, el borrado no se hacia. Al final, descubri que hay que especificarle al GridView cual es la llave de nuestra tabla, por medio de la la propiedad DataKeyNames. Hecho esto, el borrado funciona tan bonito como deberia.