julio 2007 - Posts
- Los objetos Session y Application están disponibles también en los servicios web. Permiten almacenar datos con ámbito de sesión y aplicación respectivamente y compartirlos con el resto de métodos.
- Usando estado de sesión en servicios web.
- El objeto Application se usa para almacenar datos que persistan a lo largo de las distintas sesiones, los datos persisten mientras no se reinicie el servidor web (IIS). Su uso es igual que en ASP.NET:
[WebMethod(Description="Usando el objeto Application")]
public int ProbandoObjetoApplication()
if ((Application["contador"] != null) && (Int32.TryParse(Application["contador"].ToString(), out val)))
Application["contador"] = Interlocked.Increment(ref val);
Application["contador"] = val;
- El objeto Session se usa para almacenar datos relacionados con cada sesión de usuario y los datos persisten mientras la sesión del usuario permanece activa. Para habilitar el uso de Session, hay que indicarlo en el atributo WebMethod de todos los métodos web donde lo vayamos a necesitar:
[WebMethod(Description = "Usando el objeto Session", EnableSession=true)]
public int ProbandoObjetoSession()
if ((Session["contador"] != null) && (Int32.TryParse(Session["contador"].ToString(), out val)))
Session["contador"] = Interlocked.Increment(ref val);
Session["contador"] = val;
- Hay que reconsiderar el uso de estos objetos pues almacenar datos de cara del servidor puede traducirse en una perdida de rendimiento directamente proporcional a la cantidad de datos almacenados.
- Las Cookies son otra forma de almacenar datos de forma persistente por cada usuario, aunque tienen la pega de ser un riesgo de seguridad.
- Nota Interlocked: Esta clase asegura operaciones atómicas en la modificación y lectura de variables compartidas entre threads, en el caso del ejemplo, asegura que se incrementa la última versión de la variable.
- Los servicios web también disponen de un archivo de configuración Web.config:
- El elemento webServices se usa para especificar uno ó varias opciones de configuración del servicio web, entre ellos 'protocols' donde se especifica a que tipos de mensajes puede responder el servicio web.
- El elemento sessionState se usa para especificar como se manejan los datos almacenados en el objeto Session.
- El archivo de configuración Machine.config puede usarse para especificar configuraciones comunes a todas las aplicaciones web que se ejecutan en la máquina, incluidos los servicios web.
- Los archivos de descubrimiento son documentos XML que proveen información sobre el servicio web a los posibles consumidores.
- El servicio web genera automáticamente un documento de descubrimiento cuando se accede al servicio web solicitandolo : http://servidor/servicioweb.asmx?DISCO
<contractRef ref="http://localhost:3181/Service1.asmx?wsdl" docRef="http://localhost:3181/Service1.asmx"/>
<soap address="http://localhost:3181/Service1.asmx" binding="q1:TestWebServiceSoap"/>
<soap address="http://localhost:3181/Service1.asmx" binding="q2:TestWebServiceSoap12"/>
- Si hay documentos de descubrimiento estáticos estos pueden referenciar a más de un servicio web.
- Habilitando la generación dinámica de archivos de descubrimiento se exponen todos los servicios web al público, esto podría representar un riesgo de seguridad.
- Un servicio web se publica de la misma forma que una aplicación ASP.NET, pues a fin de cuentas, también es una aplicación web.
Leo en un blog de MSMVPs.com una noticia de hace más de un año que contaba que se detecto un bug en el método estático String.IsNullOrEmpty causado por una mala optimización del JIT que puede disparar un NullReferenceException cuando se ejecuta en un ensamblado compilado en Release fuera del IDE, es decir, nuestras aplicaciones en producción. Si se ejecuta en modo Debug ó en Release dentro de IDE (donde se ejecuta sin optimizaciones) el bug no se reproduce. A día de hoy, el bug sigue existiendo.
El bug, no deja de ser bastante curioso y nos da una idea de lo lejos que está nuestro código C# de lo que verdareramente se produce. Lamentablemente Microsoft dijo que se solucionaria en Orcas... pero no sacó parche ni actualización para .NET 2.0, lo cual a mi personalmente me repatea un poco, pués una de las ventajas del .NET Framework es que MS se encarga de actualizarlo en caso de problemas de seguridad ó bugs de forma trasnparente para nuestras aplicaciones, pero así... tener que migrar a una tecnología beta... no es la solución. Aún recuerdo cuando me topé con el bug del atributo OneWay en los métodos invocados asíncronamente... otro bug que sigue igual, sin arreglar en 2.0 y solventado en Orcas. Lo que no se es si realmente se arregló en Orcas ... ó simplemente es que en Orcas no sucede :P
El bug es fácil de reproducir en un principio, basta con este código:
using System;
using System.Collections.Generic;
using System.Text;
namespace BugTest
{ class Program
{ static void Main(string[] args)
{ Console.WriteLine("starting"); test(null);
Console.WriteLine("finished"); Console.ReadLine();
}
static void test(string x)
{ for (int j = 0; j < 10; j++)
{ if (String.IsNullOrEmpty(x))
{ //TODO:
}
}
}
}
}
Ahora selecciona modo Release, construye y ejecuta fuera del IDE con Ctrl+F5 ... tachann!!

| starting
Unhandled Exception: System.NullReferenceException: Object reference not set to an instance of an object. at BugTest.Program.test(String x) in C:\Documents and Settings\Valeriano\Mis documentos\Visual Studio 2005\Projects\BugTest\BugTest\Program.cs :line 19 at BugTest.Program.Main(String[] args) in C:\Documents and Settings\Valeriano\Mis documentos\Visual Studio 2005\Projects\BugTest\BugTest\Progr am.cs:line 12 Presione una tecla para continuar . . . |
Igualmente sucede dentro de un bucle while, se puede ver en otra simple función:
static void test2(string x)
{ int count=0;
while (count < 10)
{ if (String.IsNullOrEmpty(x))
{ //TODO:
}
count++;
}
}
Pero cuando hago una simple modificación como añadir una línea para mostrar en que iteración sucede la excepción ... se ejecuta correctamente:
static void test(string x)
{ for (int j = 0; j < 10; j++)
{ Console.WriteLine(j.ToString());
if (String.IsNullOrEmpty(x))
{ //TODO:
}
}
}
Del mismo modo si altero 'test2' con una simple línea se ejecuta correctamente sin que se dispare la excepción:
static void test2(string x)
{ int count=0;
while (count < 10)
{ if (String.IsNullOrEmpty(x))
{ //TODO:
}
count++;
Console.WriteLine(count.ToString());
}
}
Para más 'inri' si intento capturar la excepción dentro del loop no se dispara :
static void test(string x)
{ for (int j = 0; j < 10; j++)
{ try
{ if (String.IsNullOrEmpty(x))
{ //TODO:
}
}
catch (NullReferenceException nEx)
{ Console.WriteLine("Capturada {0}",nEx.GetType().Name); throw;
}
}
}
Ya paranoíco, re-compruebo comentando las nuevas líneas para asegurarme... y se dispara la excepción de nuevo:
static void test(string x)
{ for (int j = 0; j < 10; j++)
{ //try
//{ if (String.IsNullOrEmpty(x))
{ //TODO:
}
//}
//catch (NullReferenceException nEx)
//{ // Console.WriteLine("Capturada {0}",nEx.GetType().Name); // throw;
//}
}
}
Ya para crear más incertidumbre sobre el tema (xD), en los comentarios del reporte del bug se indica que intentando crear un sustitutivo ha conseguido reproducir el mismo error. Por ejemplo, creando una clase con un método similar:
/// <summary>
/// Clase para intentar imitar la funcionalidad
/// de String.IsNullOrEmpty()
/// </summary>
class MyUtil
{ public static bool MyIsNullOrEmpty(string s)
{ return s == null || s.Length == 0;
}
}
Y realizando un test de la misma forma se consigue el mismo resultado, que se dispare un NullReferenceException:
static void test3(string x)
{ for (int j = 0; j < 10; j++)
{ if(MyUtil.MyIsNullOrEmpty(x))
{ //TODO:
}
}
}
Así que posiblemente sea un problema relacionado entre la forma en que se cachean las variables para su uso dentro de los 'loop', dicho método de String y sepa Microsoft qué más.
Muchos usuarios sugirieron posibles soluciones y alternativas en el reporte del bug. La recomendación sigue siendo ... "andar con cuidado al usar este método en bucles" :P
Realmente, estando Orcas hace un año en "muy beta"... parece mentira que Microsoft diga que no solventa el error :(
- La página de ayuda de los métodos web puede usarse para invocarlos. También muestra ejemplos de peticiones/respuestas SOAP 1.1 y 1.2.
- Utilizar encabezados SOAP.
- Envelope es el elemnto padre de un mensaje SOAP. Opcionalmente puede contener un elemento cabecera Header y debe contener un elemento Body. Estos elemenos indican al manejador .asmx como tratar el mensaje y que hacer con él.
- Style y Use son dos atributos de formato usados para formatear mensajes SOAP. Use se utiliza para formatear el elemento Body.
- RPC y Document son los dos valores disponibles para el atributo Style. .NET permite alterar estos valores aplicando los atributos SoapRpcMethod y SoapDocumentMethod del espacio de nombre System.Web.Services.Description. RPC (Remote Procedure Call) es el sistema antiguo.
- Literal y Encoded son los dos valores disponibles para para especificar la codificación de los parámetros aplicando el atributo Use. Literal es el valor por defecto.
[SoapDocumentMethod(Use=SoapBindingUse.Encoded)]
- Bare, Default y Wrapped son los valores de la emueración SoapParameterStyle para especificar la encapsulación de los parámetros.
[SoapDocumentMethod(ParameterStyle=SoapParameterStyle.Default)]
- Bare: Todos los parámetros deben ser colocados en elementos XML individuales justo después del elemento Body del mensaje SOAP.
- Default: Usará el defecto del servicio web, que por defecto suele ser Wrapped.
- Wrapped: Todos los parámetros deben ser envueltos en un mismo elemento XML colocado justo después del elemento Body del mensaje SOAP.
- El atributo WebServiceBinding permite especificar una o más uniones de forma que se puedan crear componentes conforme al WS-I Basic Profile. Este atributo ornamenta la clase que deriva de WebService.
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
- Para especificar una unión propia de un método, se puede hacer con la propiedad Binding del atributo SoapDocumentMethod:
[WebService(Name="TestWebService",
Description="Mi Webservice en pruebas",
Namespace = "http://webservice.vtortola.NET/")]
[WebServiceBinding(Name = "Biding1", ConformsTo = WsiProfiles.BasicProfile1_1)]
[WebServiceBinding(Name = "Biding2", ConformsTo=WsiProfiles.None)]
public class Service1 : System.Web.Services.WebService
[SoapDocumentMethod(Binding = "Binding1")]
public string HelloWorld()
- Los servicios web utilizan un proceso en dos pasos conocidos como serialización y deserialización para mapear el XML generado por el método web a objetos .NET. La clase XmlSerializer puede ser usada para personalizar los mensajes SOAP generados por los métodos web.
- Las cabeceras SOAP generalmente contienen metadatos que describen como el cliente puede procesar el método web. El elemento Header es opcional pero puede estar consituido por multiples bloques. Puede también especificar como debe ser procesada la petición SOAP y contener información respecto a autenticación y transacciones.
- Para asignar una cabecera SOAP a un método web público, primero es necesario crear una clase propia que herede de SoapHeader. El atributo SoapHeader puede ser añadido a un método web público, y el miembro "Name" de este atributo referenciará dicha clase.
- El atributo SoapHeader inlcuye una propiedad "Direction" que puede ser configurada con uno de los siguiente valores:
- In: Aplica solo al servicio web.
- Out: Aplica solo al cliente del servicio web.
- InOut: Aplica a ambos.
- Fault: Indica que el servicio web disparó una excepción.
- Los clientes consumidores de servicios web con cabeceras SOAP personalizadas necesitan crear una instancia de la cabecera en su código y configurar el valor de cualquier propiedad pasada a través de él.
- El servicio web debería tener la habilidad de procesar caberas SOAP desconocidas en caso de recibirlas sin esperarlo. Se pueden manejar estas situaciones configurando la propiedad DidUnderstand a false para cabeceras desconocidas.
- Las extensiones SOAP proveen de una forma de añadir funcionalidad a los métodos web. Se suelen usar para manejar datos encriptados y/ó comprimidos, también se suelen usar en combinación con cabeceras SOAP personalizadas para la autenticación de usuarios. Se crean usando una libreria de clases personalizada que herede de SoapExtension.
- Las cuatro etapas del proceso de manejo de un mensaje SOAP en el cliente:
- BeforeSerialize: Ocurre justo después de que el método web sea invocado, pero antes de que la petición sea serializada en XML.
- AfterSerialize: Ocurre despues de la serialización de la petición en XML, pero antes de ser enviada a través de la red.
- BeforeDeserialize: Ocurre antes de que la respuesta es deserializada en un objeto.
- AfterDeserialize: Ocurre justo después del proceso de deserialización, pero antes de que el cliente reciba el resultado.
- Las cuatro etapas del proceso de manejo de un mensaje SOAP en el servidor:
- BeforeDeserialize: Ocurre después de que la petición es recibida, pero antes de que el mensaje es deserializado en un objeto.
- AfterDeserialize: Ocurre después de que el mensaje es deserializado en un objeto, pero después de que el método haya sido llamado.
- BeforeSerialize: Ocurre después de que el método es invocado, pero antes de que los valores de retorno sean serializados y enviados de vuelta al cliente.
- AfterSerialize: Ocurre después de que los valores de retorno se serializen en XML, pero ante de que sean enviados al cliente.
- La enumeración SoapMessageState se usa para determinar que etapa del proceso esta ocurriendo y si puede ser examinada en el método sobreescrito (override) ProcessMessage.
- Puedes configurar una extensión SOAP para asociarla a un método web en particular añadiendo una clase derivada de SoapExtensionAttribute.
- Los servicios web son aplicaciones que exponen datos y funcionalidada en internet a cualquier plataforma que use el protocolo de comunicación SOAP, que nos provee de una forma estandarizada y simple de representar mensajes usando XML.
- La declaración de un servicio web se debe relizar en cada uno de los archivos .asmx de la aplicación. Esta declaración permite al manejador asmx saber donde, en que clase esta implementado el código del servicio y el lenguaje .NET con el que se ha implementado. Abriendo el archivo .asmx con un editor de texto plano podemos ver algo como esto:
<%@ WebService Language="C#" CodeBehind="Service1.asmx.cs" Class="TestWebService.Service1" %>
- La clase WebService es la clase base de todos los serivicios web y esta dentro del espacio de nombres System.Web.Services. Entre sus propiedades están viejos conocidos de entornos web en .NET, como Application, Context, Server, Session y User ;)
- El atributo WebService se usa para dar una información básica del servicio web, como puede ser el nombre, la descripción y un espacio de nombres único. Hay que cambiar el espacio de nombres por defecto cuando se distribuye la aplicación (no iba a ser el primer webservice con lo de 'tempuri.org' que he visto por ahi xD). Este atributo ornamenta la clase que derivamos de WebService junto con otros atributos.
[WebService(Name="TestWebService",
Description="Mi Webservice en pruebas",
- Los métodos expuestos por un servicio web deben ser declarados como públicos. Pueden existir métodos privados, pero no serán visibles (obviamente).
- El atributo WebMethod debe ser aplicado a todos los métodos públicos que necesitan ser expuestos a través del servicio web. Dispone de varias propiedades que permiten configurar ciertos aspectos del método:
[WebMethod(Description="Descripción",
MessageName="MiMetodoWeb",
EnableSession=false,CacheDuration=0,BufferResponse=true,
TransactionOption=System.EnterpriseServices.TransactionOption.Disabled)]
- Description: Una descripción del método.
- MessageName: El nombre que identifica al método en el XML enviado con el mensaje SOAP. Por defecto es el mismo nombre del método.
- EnableSession: Habilita ó no el estado de sesión, por cuestiones de rendimiento por defecto no.
- CacheDuration: Número de segundos que ASP.NET chacheará la respuesta del método. Por defecto esta a 0 (no se cachea), pero puede resultar una opción interesante según que escenarios ya que podría mejorar el rendimiento si vemos que hay muchas peticiones iguales.
- BufferResponse: Habilita ó deshabilita el buffering para la comunicación entre el proceso en ejecución e IIS. Por defecto es true (se utiliza el buffering), pero debería deshabilitarse si se está devolviendo grandes cantidades de datos que no deberían guardarse en buffers.
- TransactionOption: Indica cuando el método puede participar en una transación. Por defecto esta deshabilitado.
- El espacio de nombres System.Web.Services.Protocols incluye las clases atributo SoapDocumentMethodAttribute y SoapRpcMethodAttribute usadas para formatear los mensajes SOAP. Entre sus propiedades, destacan en ambas la posibilidad de declarar un método como OneWay.
- Un método 'OneWay' no requiere respuesta por parte del cliente, simplemente este lo invoca y después sigue con su ejecución sin importar si se devueven datos ó se disparan excepciones. De esta forma se mejora el rendimiento a costa de no saber que sucedió con lo que ejecutamos. Para declarar un método de esta forma simplemente hay que indicarlo en alguno de los atributos anteriores:
[SoapDocumentMethod(OneWay = true)]
- Puedes verificar el funcionamiento de tu servicio web usando una sesión de depurado desde el proyecto con Visual Studio y proceder como lo harias con cualquier otro proyecto.
- También puedes invocar tu servicio web desde el navegador con la URL del asmx.
- El resultado también puede ser verificado creando una apliación Windows ó web que consuma del servicio web, esta es la manera más fiable pues asegura que otros también se podrán conectar.
Cuando MSDN dice que crear un Thread del sistema operativo es un proceso costoso y que el ThreadPool mejora el rendimiento implementando una cache de estos objetos ... no lo dice de broma :D Realizando un experimento pude comprobar la notoria diferencia entre una forma y otra de ejecutar una tarea en segundo plano, tanto las mediciones como la diferencia entre ellas depende de la máquina, la configuracion de procesadores (uno, HT, Dual-core) que tenga y por su puesto de su potencia.
ThreadPool: Grupo de subprocesos administrados.
Yo soy un adicto al APM (Asynchronous Programming Model) y conocia sus ventajas, entre ellas que usa el ThreadPool, pero no esperaba que fuese 50 y hasta 100 veces más rapido. La tarea en segundo plano en si se ejecutará prácticamente igual de rápido de las dos formas, lo que varia es la forma en que dicho mecanismo se pone en marcha, y parece que el proceso de instanciar un objeto Thread y ponerlo en marcha no es trivial :) Para subprocesos de largo recorrido tal vez esta diferencia no sea demasiado importante pero para pequeñas tareas repetitivas si.
La pega del ThreadPool está en que no es ilimitado (por defecto tiene 25 slots por procesador), si se agotan los threads podríamos provocar que nuestra aplicación quedase en un estado de Idle infinito (ó deadlock). La recomendación suele ser usar Thread para tareas de largo recorrido ú dotar a un objeto de su propio hilo de ejecución, y utilizar el ThreadPool para operaciones de corto recorrido y APM. También es cierto que podemos reajustar los parámetros del ThreadPool por ejemplo, SetMaxThreads para redefinir ese límite de 25 ó SetMinThreads para redefinir cuantos threads tenemos cacheados como mínimo, pero el hecho de poder lanzar más hilos ó asegurar una reserva de ellos NO resultará en una mejor respuesta y/ó rendimiento de la aplicación, de hecho, puede ser contraproducente.
| Últimamente me estoy viendo más en la necesidad de usar interfaces y genéricos para crear clases más reusables y tipadas. En mis inicios con C# y .NET... aunque la idea de los genéricos me gustó... a las interfaces ... no les veia demasiada utilidad. A día de hoy soy consciente de la tremenda simbiosis que tienen estos dos elementos para crear código reusable. |
Una interfaz, como quiero ilustrar con la imagen, es como la carcasa que envuelve a un dispositivo, la carcasa no tiene funcionalidad en si, pero representa la funcionalidad que que tiene el dispositivo que en ella encaja (pensad en un acople funcional y no físico :P). Por ejemplo, la carcasa de un móvil tiene (entre otras cosas) un agujero para un micrófono, otro para el altavoz, un teclado y una pantalla, define entonces la funcionalidad de un dispositivo con el que se puede hablar, escuchar, escribir y ver. Al acoplar a la carcasa el dispositivo móvil, podrémos llamar y enviar SMSs a través de estos elementos en dicho dispositivo móvil, pero, si tuviesemos una PDA que tuviese definidas las funcionalidades de hablar, escuchar, escribir y ver ... podríamos acoplarla a esta carcasa y utilizar dichas funcionalidades con ella también, es más, si el fabricante sacase posteriormente un nuevo dispositivo con las mismas funcionalidades podríamos acoplarlo y usarlo sin tener que actualizar nuestra carcasa, y si sacase uno con más funcionalidades (por ejemplo una cámara) pero tampoco quisiésemos actualizarla... podríamos usarlo también pero solo las funcionalidades que expone la nuestra, quedando el resto ocultas. Exacto, nos brinda una funcionalidad similar a la abstración de clases, pero con ventajas adicionales, como que esa PDA podría al mismo tiempo tener definidas funcionaliades para acoplarse a otras carcasas, como a la de dispositivo GPS, a la de dispositivo de pantalla táctil ... ó incluso a una super-carcasa que fuese la suma de todas las demás.
Una interfaz:
- Es un contrato que asegura que la instancia que la implementa tiene definidos una serie de métodos y propiedades.
- Es como una clase abstracta pero donde todos sus miembros son abstractos y públicos.
- No tiene campos porque no puede tener ningún tipo de estado, únicamente se pueden definir propiedades.
- No tiene implementación, solo las definiciones de los métodos.
- No puede heredar de una clase, pero si de una ó de múltiples interfaces (multiherencia).
- No tiene ni constructores ni destructores.
- Pueden ser especializadas por estructuras.
Una interfaz se declara:
interface IMovil
{ // Propiedades de solo lectura
Botones Teclado { get;} Microfono Micro { get;} Altavoz Auricular { get;}
// Propiedades de lectura/escritura
ManosLibres MLBluethoot { get; set;}
// Metodos
void Hablar(Stream Voz);
Stream Oir();
void Teclear(char pulsacion);
}
Y se implementa como la herencia:
class NokiaN70 : IMovil
{ public Botones Teclado
{ }
public Microfono Micro
{ }
public Altavoz Auricular
{ }
public ManosLibres MLBluethoot
{ get
{ }
set
{ }
}
public void Hablar(Stream Voz)
{ }
public Stream Oir()
{ }
public void Teclear(char pulsacion)
{ }
}
Para implementar varias se separan por comas:
interface IMovil3G
{ void EnviarDatos(byte[] Datos);
byte[] RecibirDatos();
}
class NokiaN70 : IMovil, IMovil3G
{ public Botones Teclado
{ }
public Microfono Micro
{ }
public Altavoz Auricular
{ }
public ManosLibres MLBluethoot
{ get
{ }
set
{ }
}
public void Hablar(Stream Voz)
{ }
public Stream Oir()
{ }
public void Teclear(char pulsacion)
{ }
public void EnviarDatos(byte[] Datos)
{ }
public byte[] RecibirDatos()
{ }
}
A una variable de tipo interfaz se le puede asignar una instancia que cumpla dicha interfaz y usar los métodos y propiedades definidos por ella.
NokiaN70 MiMovil = new NokiaN70();
byte[] b1 = MiMovil.RecibirDatos();
IMovil im = MiMovil;
im.Teclear('K'); byte[] b2 = im.RecibirDatos(); // Error de compilación
Se puede comprobar si una instancia cumple una interfaz con el operador 'is':
object MiMovil = new NokiaN70();
if (MiMovil is IMovil3G)
{ IMovil3G im3g = MiMovil;
byte[] b =im3g.RecibirDatos();
}
No hay ambiguedad en la implementación de una interfaz, es decir, si dos interfaces definen un método del mismo nombre y firma no hay ambiguedad, pues simplemente se esta requiriendo que la instancia tenga ese método definido:
interface INavegadorGPS
{ PantallaTactil Pantalla { get;} Stream Oir();
}
class NokiaN70 : IMovil, INavegadorGPS
{ public Botones Teclado
{ }
public Microfono Micro
{ }
public Altavoz Auricular
{ }
public ManosLibres MLBluethoot
{ get
{ }
set
{ }
}
public void Hablar(Stream Voz)
{ }
public Stream Oir()
{ }
public void Teclear(char pulsacion)
{ }
// Solo se implementa este miembro, ya que ya existe un Oir()
public PantallaTactil Pantalla
{ }
}
Si la posible ambiguedad es en una reimplementación, pervalece el miembro de la interfaz derivada, aunque Visual Studio producirá un 'Warning' para que le pongamos el operador 'new', pero simplemente es indicatorio:
class NokiaN70v2 : NokiaN70, INavegadorGPS
{ new PantallaTactil Pantalla
{ }
new Stream Oir()
{ }
}
Al implementar la interfaz, es posible declarar miembros de forma explícita, esto es, que solo serán accesibles si usamos la instancia a través de una variable de ese tipo de interfaz, no desde el tipo derivado:
class NokiaN70 : INavegadorGPS
{ // Miembro declarado explicitamente
Stream INavegadorGPS.Oir()
{ }
// Miembro delcarado normalmente
public PantallaTactil Pantalla
{ }
}
class Program
{ static void Main(string[] args)
{ NokiaN70 M = new NokiaN70();
Console.WriteLine(M.Pantalla.ToString());
// Asi no
Console.WriteLine(M.Oir().ToString());
// Asi si
INavegadorGPS gps = M;
Console.WriteLine(gps.Oir().ToString());
}
}
También se pueden usar para enmascarar instancias, ocultando los miembros que no queramos mostrar:
class NokiaN70 : INavegadorGPS, IMovil3G, IMovil
{ // Implementación obviada.
static public INavegadorGPS GetGPS()
{ return new NokiaN70();
}
}
class Program
{ static void Main(string[] args)
{ INavegadorGPS gps = NokiaN70.GetGPS();
gps.Oir();
// Error
gps.Teclear('K'); }
}
Espero haber sido medianamente claro :P En mi próximo artículo hablaré de los genéricos y como con las interfaces podemos hacer aún más tipados nuestras clases y métodos genéricos.
 | Ya puestos en temas del GC, hablemos del patrón desechable, que aunque nos permite crear clases que sean de usar y tirar… no llega a ser una forma de destrucción determinista, pero ayuda. Este mes ha aparecido en la sección CLR Inside Out del MSDN Magazine un artículo de Shawn Farkas acerca del correcto uso de la interfaz IDisposable (en castellano), explicando el porqué de cada cosa. No puedo explicarlo mejor y más detalladamente de lo que lo ha hecho Shawn Farkas, pero si más resumido  |
Una de las cuestiones críticas a la hora de optimizar el uso de memoria es que el GC se ejecuta de forma no determinística, esto es, que no podemos predecir cuando se va a ejecutar, y aunque podemos invocarlo explícitamente con GC.Collect(), no es recomendable.
Los destructores de clase, definidos con un circunflejo, el nombre de la clase y sin modificador de acceso alguno (ie: ~Miclase() ), corresponde al código que ejecutará el GC (si existe) cuando destruya el objeto. Este método no puede ser invocado, por lo que no podemos llamarlo para realizar la destrucción determinística del mismo. Si el GC se dispone a recolectar este objeto y no tiene dicho destructor, es recolectado directamente, en caso de tener un destructor pasa a una cola de destrucción para ser destruido a posteriori, lo cual nos dice que no siempre va a ser óptimo definir un constructor, únicamente cuando sea necesario.
La interfaz IDisposable nos provee de la forma de crear clases desechables, que pueden ser destruidas de forma casi determinística. El método Dispose que implementa no es un destructor, de hecho, no deja de ser un simple método cualquiera con la particularidad de que su misión contractual (por el hecho de pertenecer a IDisposable) es liberar los recursos administrados y no administrados. Una vez implementada, podríamos “desechar” nuestra clase invocando al método .Dispose ó usando nuestra clase dentro de una sentencia using.
A bote pronto parece sencillo pero aparecen varias casuísticas especiales, de las cuales las más destacables son:
1) ¿Qué sucede si se llama a Dispose mientras ya se esta ejecutando otro Dispose? Pués es fácil de predecir, si se intenta hacer un Stream.Close() de un Stream que ya ha sido destruido… se dispararía una NullReferenceException.
2) ¿Qué sucede si se nos olvida hacer un Dispose? Pues también es obvio, a no ser que tengamos definido un destructor que también libere los recursos… cuando el GC recolecte esta clase… quedarán sin liberar.
3) ¿Qué sucede si tenemos Dispose y un destructor con el mismo código duplicado para liberar recursos? Pues que si llamamos a Dispose, después cuando el GC detecte que hay un destructor y lo llame, si no hay forma de averiguar si los recursos se han liberado ó no… podría ó podrían dispararse NullReferenceException al intentar liberar recursos ya liberados. Además del problema intrínseco de diseño que implica tener código duplicado.
4) ¿Y si apuntan a un código en común para liberar recursos? Pues volviendo a la casuística #3, si no tenemos forma de controlar si se ha liberado ya podríamos obtener el mismo resultado.
5) ¿Y si pudiese controlar cuando han sido liberados? Pues el único defecto que quedaría es el hecho de que el GC encolaría la clase al recolectarla por el mero hecho de tener un destructor definido, siendo esto no un problema, pero si mejorable.
Estos cinco casos quedan cubiertos con la correcta implementación del patrón desechable:
class MiClase : IDisposable
{ private bool disposing;
//
// … resto del código …
//
/// <summary>
/// Método de IDisposable para desechar la clase.
/// </summary>
public void Dispose()
{ // Llamo al método que contiene la lógica
// para liberar los recursos de esta clase.
Dispose(true);
}
/// <summary>
/// Método sobrecargado de Dispose que será el que
/// libera los recursos, controla que solo se ejecute
/// dicha lógica una vez y evita que el GC tenga que
/// llamar al destructor de clase.
/// </summary>
/// <param name=”b”></param>
protected virtual void Dispose(bool b)
{ // Si no se esta destruyendo ya…
if (!disposing)
{ // La marco como desechada ó desechandose,
// de forma que no se puede ejecutar este código
// dos veces.
disposing = true;
// Indico al GC que no llame al destructor
// de esta clase al recolectarla.
GC.SuppressFinalize(this);
// … libero los recursos…
}
}
/// <summary>
/// Destructor de clase.
/// En caso de que se nos olvide “desechar” la clase,
/// el GC llamará al destructor, que tambén ejecuta la lógica
/// anterior para liberar los recursos.
/// </summary>
~MiClase()
{ // Llamo al método que contiene la lógica
// para liberar los recursos de esta clase.
Dispose(true);
}
}
· La variable booleana ‘disposing’ controla si se ha desechado ó se esta desechando el objeto para que no se ejecute dos veces.
· El método ‘Dispose(bool b)’ es realmente quien libera los recursos, primero comprueba que la instancia no ha sido desechada ó este desechandose, libera los recursos y ejecuta ‘GC.SuppressFinalize(this)’ para que se suprima el constructor de esta clase y el GC pueda recolectarla sin más. De esta forma solo se ejecutaría este método una vez por instancia.
· El método ‘Dispose()’ llama a su homónimo sobrecargado ‘Dispose(bool b)’ para que deseche la instancia. El destructor de clase también llama a ‘Dispose(bool b)’ para que deseche la instancia. De esta forma, tanto si desechamos el objeto intencionadamente, como si se nos olvida y lo destruye el GC, se ejecutará el mismo método y solo una vez por instancia.
Crossposting : El patrón desechable con la interfaz IDisposable en vtortola.Net
|

|
Aprobé el 70-526, por lo que ya soy MCTS: Windows Applications :D
Ahora ya a por el 70-528 de Web Applications, pero de este no haré resumenes porque no me agrada mucho la programación web y el único objetivo de hacerlo es que lo necesito para ser MCPD:Enterprise Applications :P |
CrossPosting
desde vtortola.NET
70-526 Aprobado !!
ClickOnce:
· Implementación de ClickOnce para aplicaciones Windows Forms.
· Implementación ClickOnce con Visual Studio.
· Es una nueva tecnología que permite al desarrollador distribuir sus aplicaciones de forma rápida y confiable en sitios web, carpetas compartidas ó sitios ftp.
· Las aplicaciones ClickOnce pueden ser configuradas para actualizarse automáticamente. Se puede configurar la aplicación para que busque actualizaciones siempre que se ejecute ó en un intervalo predefinido.
· Por defecto, estas aplicaciones se ejecutan en la zona de seguridad correspondiente a Internet si es descargada de un sitio web ó en la zona correspondiente a Intranet si se descargó de una carpeta compartida.
· Si se requieren permisos adicionales, pueden ser concedidos por el desarrollador, introducidos manualmente ó calculando los necesarios, para que posteriormente sean aceptados ó rechazados por el instalador.
Proyectos de instalación:
· Remember: 70-536: Instrumentación e Instalar y Configurar aplicaciones.
· Implementación de Windows Installer.
· Permiten crear aplicaciones “Windows Installer” para instalar una solución. Son altamente configurables y permiten un gran control sobre la configuración del proceso de instalación.
· Tienen varios editores para editar aspectos del proceso de instalación, incluyendo el editor de sistema de archivos, el editor del registro, el editor de tipos, el editor de interfaz de usuario, el editor de acciones personalizadas y el editor de condiciones de ejecución.
· El editor del sistema de archivos se usa para añadir otros archivos necesarios para la aplicación que no están en la solución ó no pertenecen al código. Puede crear directorios e instalar archivos en ellos. Puede crear accesos directos e incluso copiar según que archivos en función del sistema operativo.
· Tienen varias propiedades que muestran información descriptiva sobre la aplicación y afectan al comportamiento de esta en el momento de la instalación. Se pueden cambiar desde la ventana Propiedades de Visual Studio seleccionando el proyecto.
· Las acciones personalizadas son procedimientos que se ejecutan en tiempo de instalación/desinstalación. Se puede crear una acción personalizada añadiendo el código a una clase Installer, añadiendo dicha clase al proyecto, e indicando que proyecto es en el editor de acciones personalizadas.
· Cuando se dan errores que son irrecuperables en una acción personalizada, deberías lanzar una InstallException para deshacer la instalación sin dañar el sistema destino.
CrossPosting
desde vtortola.NET
70-526 Resumen 8/8: Distribuir aplicaciones Windows.
Controles compuestos:
· Desarrollar controles compuestos de formularios Windows Forms.
· Controles de formularios Windows Forms.
· Los controles compuestos, también llamados controles de usuario, consisten en un control compuesto de otros controles convencionales, llamados en este caso constituyentes. Se puede extender la funcionalidad de este tipo de controles siguiendo las reglas del polimorfismo y la POO. Estos controles son una especialización de la clase UserControl.
· Al añadir una nueva clase de tipo UserControl, Visual Studio nos provee de las herramientas y funcionalidades básicas para crearlo y diseñarlo.
· Las propiedades de los controles constituyentes generalmente no son accesibles por los desarrolladores (suelen ser privados). Se pueden exponer dichas propiedades envolviéndolas con propiedades públicas propias.
· Puedes hacer invisible un control en tiempo de ejecución configurando la propiedad Visible a True. Puedes definir el fondo transparente configurando BackColor a Color.Transparent. Puedes hacer un color transparente para todo el formulario configurándolo como Form.TransparencyKey.
· Puedes añadir una imagen al control para que aparezca en la barra de herramientas en la propiedad ToolboxBitmap.
Controles personalizados:
· Desarrollar controles personalizados en Windows Forms.
· A diferencia de los controles de usuario, para los controles personalizados hay que darles su propio código de pintado y renderizado
· Estos controles no tienen una interfaz visual por defecto y por ello no podemos diseñarlos al 100% con Visual Studio. Generalmente no incorporan otros controles Windows Forms.
· Se debe sobrescribir el método OnPaint para indicar como pintar el control.
· Con la instancia de Graphics obtenida de OnPaint se puede pintar el control.
· Estos controles son especializaciones de la clase Control.
· Remember: 70-536, Globalización, gráficos y manipulación de texto.
· Cuando se renderiza un control personalizado, se deben usar coordenadas para referenciar puntos en el control, y así indicar donde se quiere pintar.
Controles Extendidos:
· Se puede extender la funcionalidad de un control creando una clase que especializa dicho control existente. Encapsulan toda la funcionalidad del control del que heredan, pero además añaden unas nuevas funcionalidades y sobrescriben otras, creando un control con un fin ó especialización distintos.
· Puedes modificar la apariencia de un control extendido sobrescribiendo su método OnPaint y codificando ahí la nueva forma de renderizado.
Ventanas de dialogo personalizadas:
· Crear ventanas de dialogo en tiempo de diseño.
· Las ventanas de dialogo son unos formularios especiales diseñados para solicitar una información puntual al usuario.
· Pueden ser mostrados en modal ó no, con ShowDialog ó Show respectivamente.
· En modo modal detienen la aplicación y acaparan el foco hasta que el usuario emprende una acción y cierra el dialogo.
· En el método ShowDialog se puede especificar cual será el formulario padre al que bloqueará hasta obtener respuesta.
· La enumeración DialogResult representa los distintos valores que puede devolver un formulario cuando se muestra con ShowDialog.
CrossPosting
desde vtortola.NET
70-526 Resumen 7/8: Crear controles de formulario.
Arrastrar y soltar (Drag and Drop):
· Arrastrar y colocar en Windows Forms.
· Las operaciones de “arrastrar y soltar” se inician llamando al método DoDragDrop en el control que inicia la acción, se suele hacer normalmente en el manejador del evento MouseDown de dicho control. Dicho método toma dos parámetros, un objeto que contiene los datos que serán “arrastrados y soltados” y otro parámetro DragDropEffects que indica que efectos son permitidos.
· La enumeración DragDropEffects representa el efecto ó efectos en una operación de arrastrar y soltar.
· Los eventos GiveFeedBack y QueryContinueDrag son disparados en el control origen en este punto. El primero, permite personalizar el puntero del ratón con una forma personalizada y el segundo, podría usarse para decidir si continuar ó abortar la operación.
· El control destino debe tener como True la propiedad AllowDrop. Cuando se da esta condición al arrastrar datos a él, se dispara en el destino el evento DragEnter.
· El evento DragEnter en el control destino se usa para indicar los efectos permitidos en dicho control destino. Puedes examinar los datos en la propiedad DragEventArgs.Data que existen en el manejador del evento DragEnter y determinar si los datos son apropiados para el control. Puedes abortar la operación fijando DragEventArgs.Effect a None.
· La operación se completa en el evento DragDrop del control destino.Aquí, debe escribirse el código que complete la operación a alto nivel (añadir los datos a un ListView … realizar otra operación Query … etc… ).
· Los datos pueden ser arrastrados y soltados entre controles de diferentes aplicaciones. No hace falta ningún paso adicional para ello.
· Esta operación en un control TreeView comienza llamando al método DoDragDrop en el manejador del evento TreeView.ItemDrag. El resto del proceso es similar.
Globalización y localización:
· Globalizar y localizar aplicaciones.
· Remember 70-536: Globalización, gráficos y texto.
· Globalización: se refiere a cuestiones de formateado de datos, por ejemplo, la separación de decimales, miles ó los símbolos monetarios.
· Localización: se refiere a cuestiones relacionadas con el lenguaje de la aplicación, como el idioma ó la dirección del texto, mostrando unos textos u otros en función del lenguaje en cuestión.
· Cuando se habla de cultura, se habla de la información cultural en el país ó región en el cual la aplicación se desarrolla y está representada por un código.
·