lunes, 16 de noviembre de 2009

Como leer un archivo pdf desde un aplicativo web form / asp.net

Aquí les dejo un método que les permitirá leer un archivo pdf (adobe) desde un aplicativo Web Asp.NET.

Deben utilizar el siguiente imports:

using System.Net;

A continuación el método:

private void LeerArchivoPdf(string NombreArchivoPDF)
{
string ruta = NombreArchivoPDF;
WebClient client = new WebClient();
Byte[] buffer = client.DownloadData(ruta);
if (buffer != null)
{
Response.ContentType = "application/pdf";
Response.AddHeader("content-length", buffer.Length.ToString());
Response.BinaryWrite(buffer);
}
}

Hugo Bernachea
MCT - MCSD

viernes, 16 de octubre de 2009

A namespace does not directly contain members such as fields or methods - App.Config

Si les aparece el siguiente error en el app.config:
A namespace does not directly contain members such as fields or methods
O su equivalente es castellano, y ustedes verifican y se aseguran que su app.config está correctamente configurado, si verifican el contenido del app.config un par de veces y ven que el contenido del app.config es ok, lo mejor que pueden hacer es copiar el contenido del app.config en un archivo de texto, guardar dicho archivo de texto, eliminar el app.config y volver a agregar un app.config en blanco y nuevamente copiar el contenido del app.config anterior. Con eso se suele corregir ese error.

Función IsNumeric en C#

Csharp (C#) no cuenta con una función isnumeric al estilo Visual Basic, pero se pueden implementar diferentes soluciones para lograr el objetivo, uno es hacer una función como la siguiente:

public bool IsNumeric(object Expression)
{
bool esnumero;
double returnNumero;

esnumero= Double.TryParse(Convert.ToString(Expression), System.Globalization.NumberStyles.Any,System.Globalization.NumberFormatInfo.InvariantInfo, out returnNumero);
return esnumero;
}

La otra solución es crear una librería (Dll) en visual basic.NET y directamente referenciarla en C#, que yo pienso que es quizás la mejor opción.

sábado, 1 de agosto de 2009

.NET 3.5 - Usando el Nuevo tipo SqlDbType.Structured para pasar parámetros de tipo Table a Stored Procedures en SQL Server 2008

En SQL Server 2008 se ha agregado la posibilidad (largamente esperada) de pasar un parámetro de tipo Table a un Stored Procedure, pueden ver una explicación aquí:
http://sqldata.blogspot.com/2009/08/mejoras-en-t-sql-de-sql-server-2008-el.html

La pregunta es como hacer desde .NET 3.5 para invocar este tipo de stored procedures, lo cual se resuelve fácilmente con el nuevo sqltype de tipo Structured

El código que viene a continuación es un ejemplo de lo dicho, como verán todo esto es código conocido, la única novedad es que pasamos directamente un datatable completo al value del parámetro, definido como Structured, veamos el ejemplo:

//creo un data table para almacenar algunos registros de ejemplo
DataTable dtClientes = new DataTable("Clientes");
DataColumn dcNombre = new DataColumn("nombre", typeof(string));
DataColumn dcApellido = new DataColumn("apellido", typeof(string));
DataColumn dcEmail = new DataColumn("Email", typeof(string));
dtClientes.Columns.Add(dcNombre);
dtClientes.Columns.Add(dcApellido);
dtClientes.Columns.Add(dcEmail);
//agrego un cliente
DataRow drCustomer = dtClientes.NewRow();
drCustomer["nombre"] = "aaa.net";
drCustomer["apellido"] = "XYZ";
drCustomer["Email"] = "info@kyriosdata.com.ar";
dtClientes.Rows.Add(drCustomer);
//Agrego otro cliente
drCustomer = dtClientes.NewRow();
drCustomer["nombre"] = "bbb.net";
drCustomer["apellido"] = "XYZ";
drCustomer["Email"] = "info@kyriosdata.com.ar";
dtClientes.Rows.Add(drCustomer);
//Y otro cliente mas
drCustomer = dtClientes.NewRow();
drCustomer["nombre"] = "ccc.net";
drCustomer["apellido"] = "XYZ";
drCustomer["Email"] = "info@kyriosdata.com.ar";
dtClientes.Rows.Add(drCustomer);
//Creo la conexion, recuerde modificar el connection string
// y modifique server e indique SU servidor
// y modifique en base de datos e indique SU base de datos
SqlConnection conn = new SqlConnection("server=.;database=Ejemplo;Integrated Security=true");
conn.Open();
//Lo tradicional, creamos un comando que invoque al store "Agrega clientes"
// que está definido y se pueden encontrar en el enlace siguiente:
// http://sqldata.blogspot.com/2009/08/mejoras-en-t-sql-de-sql-server-2008-el.html
SqlCommand cmdCustomer = new SqlCommand("AgregaClientes", conn);
cmdCustomer.CommandType = CommandType.StoredProcedure;
//Aquí en realidad se encuentra lo único distinto con respecto
// a la lógica que veníamos usando en .NET
// y es crear el parámetro pero usando el nuevo sqldbtype Structured
// el cual puede recibir un datatable completo
// el s.p. matchea con un parámetro de tipo table, ver el link
SqlParameter paramCustomer = cmdCustomer.Parameters.Add("@ClientesTVP", SqlDbType.Structured);
paramCustomer.Value = dtClientes;
//Ejecutamos
cmdCustomer.ExecuteNonQuery();

Y si revisamos la tabla Clientes debiéramos encontrar los nuevos registros agregados a la tabla cliente.

Nuevamente y por si a alguien se le escapó el detalle, la información acerca de este nuevo tipo de parámetros como asimismo la definición del Stored Procedure se encuentra en el siguiente enlace:

http://www.sqlexperto.com.ar/index.php?topic=20.msg21#new





martes, 21 de julio de 2009

Diferencias entre Eventos y Delegados - En qué son distintos?

Eventos versus Delegados (Events vs Delegates)

Es la típica pregunta acerca de la diferencia entre eventos y delegados, considerando que uno siempre podría imitar la funcionalidad de un evento utilizando un delegado. Esto considerando que un evento siempre está relacionado a un delegado.

Un evento es parecido a un multicast delegate en algún sentido.

Veamos el siguiente ejemplo donde tenemos msgNotifier (evento) y msgNotifier2 (delegado simple) que aparentan comportarse exactamente igual en todo sentido.

using System;

namespace EventAndDelegate
{
delegate void MsgHandler(string s);

class Class1
{
public static event MsgHandler msgNotifier;
public static MsgHandler msgNotifier2;
[STAThread]
static void Main(string[] args)
{
Class1.msgNotifier += new MsgHandler(PipeNull);
Class1.msgNotifier2 += new MsgHandler(PipeNull);
Class1.msgNotifier("test");
Class1.msgNotifier2("test2");
}

static void PipeNull(string s)
{
return;
}
}
}


Si vemos el código IL para el método Main vemos que ambos msgNotifier y msgNotifier2 son usados de la misma manera.

.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
.custom instance void [mscorlib]System.STAThreadAttribute::.ctor() = ( 01 00 00 00 )
// Code size 95 (0x5f)
.maxstack 4
IL_0000: ldsfld class EventAndDelegate.MsgHandler EventAndDelegate.Class1::msgNotifier
IL_0005: ldnull
IL_0006: ldftn void EventAndDelegate.Class1::PipeNull(string)
IL_000c: newobj instance void EventAndDelegate.MsgHandler::.ctor(object,
native int)
IL_0011: call class [mscorlib]System.Delegate [mscorlib]System.Delegate::Combine(class [mscorlib]System.Delegate,
class [mscorlib]System.Delegate)
IL_0016: castclass EventAndDelegate.MsgHandler
IL_001b: stsfld class EventAndDelegate.MsgHandler EventAndDelegate.Class1::msgNotifier
IL_0020: ldsfld class EventAndDelegate.MsgHandler EventAndDelegate.Class1::msgNotifier2
IL_0025: ldnull
IL_0026: ldftn void EventAndDelegate.Class1::PipeNull(string)
IL_002c: newobj instance void EventAndDelegate.MsgHandler::.ctor(object,
native int)
IL_0031: call class [mscorlib]System.Delegate [mscorlib]System.Delegate::Combine(class [mscorlib]System.Delegate,
class [mscorlib]System.Delegate)
IL_0036: castclass EventAndDelegate.MsgHandler
IL_003b: stsfld class EventAndDelegate.MsgHandler EventAndDelegate.Class1::msgNotifier2
IL_0040: ldsfld class EventAndDelegate.MsgHandler EventAndDelegate.Class1::msgNotifier
IL_0045: ldstr "test"
IL_004a: callvirt instance void EventAndDelegate.MsgHandler::Invoke(string)
IL_004f: ldsfld class EventAndDelegate.MsgHandler EventAndDelegate.Class1::msgNotifier2
IL_0054: ldstr "test2"
IL_0059: callvirt instance void EventAndDelegate.MsgHandler::Invoke(string)
IL_005e: ret
} // end of method Class1::Main


Si vemos en el MSDN keywords list vemos que los event son solo modificadores (modifier).

La pregunta es que cosa exactamente modifican?

Valor agregado de un evento
Eventos e interfaces

Un evento puede ser incluido en la declaración de una interface mientras que un campo no.

interface ITest
{
event MsgHandler msgNotifier; // compila
MsgHandler msgNotifier2; // error CS0525: Interfaces cannot contain fields
}

class TestClass : ITest
{
public event MsgHandler msgNotifier; // cuando se implementa una interface se debe implementar el evento también.
static void Main(string[] args) {}
}


Invocation de un evento

Un evento solo puede ser invocado en la clase en la que se declaró, mientras que un delegado puede ser invocado desde cualquier parte.

Ejemplo:

using System;

namespace EventAndDelegate
{
delegate void MsgHandler(string s);

class Class1
{
public static event MsgHandler msgNotifier;
public static MsgHandler msgNotifier2;

static void Main(string[] args)
{
new Class2().test();
}
}

class Class2
{
public void test()
{
Class1.msgNotifier("test"); // error CS0070: The event 'EventAndDelegate.Class1.msgNotifier' can only appear on the left hand side of += or -= (except when used from within the type 'EventAndDelegate.Class1')
Class1.msgNotifier2("test2"); // compiles fine
}
}
}

Esta restricción es muy fuerte y aún las clases derivadas no pueden invocar dicho evento (salvo un método protected virtual que lo dispare).

Accesores de Eventos
Eventos disponen de métodos add y remove.
Esto es similar al get y set de las properties.

Firma del evento

el .NET framework agrega una restriction en la firma de los delegados que pueden ser usados como eventos. La firma tienen que ser en este estilo foo(object source, EventArgs e), donde source es el objeto que dispara el evento y e contiene información específica a este evento.

Estas serían a mi entender algunas diferencias entre eventos (events) y delegates (delegados)

domingo, 21 de junio de 2009

Como exponer los eventos RowUpdated y RowUpdating en un TableAdapter?

Bueno, después de dar un par de vueltas e investigar un poco, acá les dejo la solución a como exponer los eventos RowUpdated y RowUpdating de un TableAdapter de los generados con diseñador de Dataset fuertemente Tipados.

Tanto en C# 2005 como en C# 2008 tienen que ir al Dataset tipado (xsd).
Identifican al TableAdapter (NO el datatable) que van a modificar y clickean con boton izquierdo.
Van a la ventana de propiedades para el tableadapter y ponen la propidad ConnectionModifier en PUBLIC.

Después clickean con el botón derecho en el mismo TableAdapter y van a la opción Ver Código (View Code) y en la ventana de código agregan la clase parcial necesaria para incorporar el código que expone el dataadapter interno.

El código a continuación es un ejemplo de como debiera queda la clase

namespace WinCon.DataSet1TableAdapters //obviamente acá va el namespace de ustedes
{
using System.Data;
using System.Data.SqlClient; //acá va el provider de la base que ustedes están usando
// en caso que no sea SQL Server

public partial class EjemploTableAdapter //de nuevo, el nombre exacto de su tableadapter
{
public System.Data.SqlClient.SqlDataAdapter Adapter2 //el nombre que se les ocurra
{
get
{
this.Adapter.SelectCommand = this.CommandCollection[0];
return this.Adapter;
}
}
}
}

Hasta acá la modificación, si lo estuvieran invocando por ejemplo desde un windows forms, podrían ejecutar lo siguiente (ojo que ahora el código que viene a continuación está adentro de un windows forms que utiliza al tableadapter).

DataSet1TableAdapters.EjemploTableAdapter da = new WinCon.DataSet1TableAdapters.EjemploTableAdapter();
DataSet1 ds = new DataSet1();


public Form1()
{
//atacho los eventos y digo que lo gestionan
// los métodos metodo y metodo2 respectivamente.
da.Adapter2.RowUpdating += metodo; //fijensé que uso la propiedad Adapter2
// que creé adentro del TableAdapter.
da.Adapter2.RowUpdated += metodo2;
InitializeComponent();
}


Espero que a alguien le sea de utilidad.

Hugo Bernachea
http://www.linkedin.com/in/bernachea

viernes, 19 de junio de 2009

Firebird con Visual Studio 2005

********* Consejo Dado "AS IS" "ASI COMO ESTA", sin garantias y bajo la total y absoluta responsabilidad de quien lo aplica, como todos los consejos, tipos y sugerencias dados en este sitio *********

Firebird como base de datos "free" no es tan conocida como otras bases de datos mas populares como MySQL y PostgreSQL, pero lo cierto es que Firebird tiene una serie de características que la hacen muy interesante a la hora de realizar desarrollos de programas "empaquetados".
Una de las características es que esta base puede ser embebida en el producto final, con lo cual no se le instala un servidor al cliente. Además Firebird tiene Stored Procedures, Triggers, Funciones y todas las características que presentan las bases de datos comerciales y que MySQL recién está empezando a adoptar desde su ultima versión. Obviamente Firebird es muy superior a SQL Lite e incluso es superior a las versiones express de SQL Server y su licencia comercial permite utilizarlo incluso en programas comerciales sin restricciones.

La idea de utilizarlo con .NET viene pronto a las manos y por lo tanto voy a describir como hacerlo funcionar en Visual Studio 2005.

Pasos a seguir:
1. Ir a la página de Firebird y descargar el provider para .NET 2.
2. Descargue el Visual Studio 2005 SDK (NO el SDK del framework please) desde el sitio de descargas de Microsoft. Recuerden que el SDK del Visual Studio NO es parte de la instalación, es una descarga adicional.
3. Verifique y en caso de no estar previamente agragado, agregue el provider de firebird para .net 2 (el archivito FirebirdSQL.Data.FirebirdClient.dll) a la Global Assembly Cache (GAC) con gacutil.exe -i FirebirdSQL.Data.FirebirdClient.dll.
4. Ejecute gacutil.exe /l FirebirdSql.Data.FirebirdClient y fíjese los números que indica esta ejecución, particularmente el número de version, el publickeytoken y cultura.
5. Ubique el machine.config y fíjese en los siguientes tags:
Desdpués de configuration ->configSections agregue lo siguiente (todo seguido)

add <section name="firebirdsql.data.firebirdclient" type="System.Data.Common.DbProviderConfigurationHandler, System.Data, Version=SU_NUMERO_DE_VERSION, Culture=neutral, PublicKeyToken=EL_PublicKeyToken_DE_SU_DLL_EN_SU_GAC">

6. En <system.data> -> <dbproviderfactories> agregar
<add name="FirebirdClient Data Provider" invariant="FirebirdSql.Data.FirebirdClient" description=".Net Framework Data Provider for Firebird" type="FirebirdSql.Data.FirebirdClient.FirebirdClientFactory, FirebirdSql.Data.FirebirdClient, Version=ACA_VA_SU_NUMERO_DE_VERSION, Culture=neutral, PublicKeyToken=DE_NUEVO_ACA_VA_SU_PUBLIC_KEY_TOKEN">

7. Abra el archivo FirebirdDDEXProvider.reg y reemplace la variable %Path% con la ruta exacta donde se encuentra la dll del provider, por ejemplo C:\Program Files\FirebirdClient, recuerde de agregar doble barra, por ejemplo C:\\Program Files\\FirebirdClient. Guarde el archivo .reg y con un doble click agregue esa información en el registro.

Si hizo todo de la manera correcta ya tiene disponible la opción para abrir el VS 2005 y usar firebird como cualquier otra base de datos, incluso agregando la conexion en el server explorer.

lunes, 5 de enero de 2009

Haciendo Generadores de Codigo con CodeDom

A los que están interesados en realizar sus propios generadores de código en .NET (C# o VB.NET), les dejo un ejemplo para utilizar Codedom (los imports mínimos son: System.CodeDom y System.CodeDom.Compiler )
Lo bueno de CodeDom es que permite generar código de manera genérica pero de implementación concreta, esto es, podemos hacer generadores de código para cualquier de los lenguajes de .NET (c++, C#, Vb.NEt, etc).

Aquí vá el ejemplo que pueden utilizar y probar, este código genera un string con el código de una clase en C# y en VB, con una propiedad llamada ID y un constructor predeterminado. Ideal para comenzar a probar y experimentar.

Dim ns As CodeNamespace 'definiendo un namespace para nuestra clase

Private Sub GeneraClase
'defino un namespace base
ns = New CodeNamespace("PrimerCodeDom")

'agrego los imports correspondientes
'aca pueden agregar todos los imports necesarios
ns.Imports.Add(New CodeNamespaceImport _
("System"))
ns.Imports.Add(New CodeNamespaceImport _
("System.Diagnostics"))
ns.Imports.Add(New CodeNamespaceImport _
("System.Text"))

'creo la clase en si misma
Dim clase As New CodeTypeDeclaration("Clase_Clientes")
'agrego la clase a la colección de tipos
ns.Types.Add(clase)

'defino un campo privado

Dim campo As New CodeMemberField("integer", "_ID")
campo.Attributes = MemberAttributes.Private

'agrego el miembro privado a la clase

clase.Members.Add(campo)

'defino un campo público

Dim p As New CodeMemberProperty()

' le doy un nombre a la propiedad
' ID en este caso
p.Name = "ID"

' lo hago público
p.Attributes = MemberAttributes.Public
p.Type = New CodeTypeReference("System.Int32") 'es integer
p.HasGet = True
p.HasSet = True 'si fuese false, sería solo lectura

'esto que viene es obligatorio
'tengo que decirle que cosa devuelve el get de la propiedad
p.GetStatements.Add( _
New CodeMethodReturnStatement( _
New CodeFieldReferenceExpression( _
New CodeThisReferenceExpression(), "_ID")))
'esto también es obligatorio
'tengo que decirle como se asigna el valor del set
p.SetStatements.Add(New CodeDom.CodeAssignStatement( _
New CodeFieldReferenceExpression( _
New CodeDom.CodeThisReferenceExpression, "_ID"), _
New CodeDom.CodePropertySetValueReferenceExpression))

'agrego la propiedad a la clase
clase.Members.Add(p)

'defino un constructor público

Dim constructor As New CodeConstructor()
constructor.Attributes = MemberAttributes.Public
clase.Members.Add(constructor)

'defino los providers para los lenguajes en los que voy a generar mi clase
Dim csProvider As New Microsoft.CSharp.CSharpCodeProvider
Dim vbprovider As New VBCodeProvider

'en .net 3.5 este código tira un warning por obsoleto,
'pero igual funciona perfecto.
Dim codigo As ICodeGenerator

codigo = vbprovider.CreateGenerator()
Dim vbCod As String = generaCode(codigo)

codigo = csProvider.CreateGenerator
dim csCod as String = generaCode(codigo)

'en este punto, en las variables vbCod y csCod
'tienen el código de la clase correctamente generada.

End Sub

'metodo complementario
Private Function generaCode(ByVal CodeGenerator As ICodeGenerator) As String

Dim options As New CodeGeneratorOptions()

' defino la identación del código
options.IndentString = Space(3)

' creo un StringWriter
Dim sb As New StringBuilder()
Dim sw As StringWriter = New StringWriter(sb)

' genero
CodeGenerator.GenerateCodeFromNamespace(ns, sw, options)

' devuelvo el string con el código generado
Return sb.ToString()
End Function

Una utilidad interesante podría ser utilizar este código para generar un mapeador de clases contra tablas de una base de datos, para lo cual debemos recorrer las tablas de una base de datos, ver código SQL aquí: http://sqldata.blogspot.com/2009/01/como-obtener-todas-las-tablas-de-una.html y por cada tabla recorrer los campos con el Script SQL listado aquí: http://sqldata.blogspot.com/2009/01/como-obtener-todos-los-campos-de-una.html y aplicar el codigo codedom correspondiente para generar un mapeador de clases contra tablas.
O sea, por cada tabla generar la clase en codedom y por cada campo generar dentro de la clase correspondiente las propiedades que necesitemos.

Con eso tienen lo necesario para generarse un mapeador de clases si se ponen manos a la obra.


Hugo Bernachea