Reconocimiento de escritura manual
Transcripción
Reconocimiento de escritura manual
www.dotnetmania.com nº 42 noviembre 2007 6,50 € Visual Basic • C# • ASP.NET • ADO.NET • SQL Server • Windows System dotNetManía dedicada a los profesionales de la plataforma .NET Tablet PC SDK (I) Reconocimiento de escritura manual Visualización de grandes conjuntos de datos en ASP.NET • Aplicación de formato y selección manual de columnas sobre el control DataGridView • Microsoft Operations Framework (y III) Laboratorio VistaDB v3.2 TodotNet@QA Enlazándonos a LINQ Comunidad .NET MVP Open Day y TTT 2007 October .NET Opinión El viejo mito de la reusabilidad editorial dotNetManía Dedicada a los profesionales de la plataforma .NET Vol. III •Número 42 • Noviembre 2007 Precio: 6,50 € [OT] ¡Fuerza y Honor! Editor Paco Marín ([email protected]) Redactor jefe Marino Posadas ([email protected]) Editor técnico Octavio Hernández ([email protected]) Redacción Dino Esposito, Guillermo 'Guille' Som, José Manuel Alarcón, Luis Miguel Blanco y Miguel Katrib (Grupo Weboo) Empresas Colaboradoras Alhambra-Eidos Krasis Plain Concepts Raona Solid Quality Learning Además colaboran en este número Antonio Quirós, Daniel Seara, Eduardo Quintás, Javier Roldán, Joan Llopart y José Luis Montes. Corresponsal para América Latina Pablo Tilotta Ilustraciones Mascota (Clico): Yamil Hernández Portada: Javier Roldán Fotografía Roberto Mariscal Atención al suscriptor Pilar Pérez ([email protected]) Edición, suscripciones y publicidad .netalia c/ Thomas Edison, 4, Bloque 1, P4-6 Parque empresarial Rivas Futura 28521 - Rivas Vaciamadrid (Madrid) www.dotnetmania.com Tf. (34) 91 666 74 77 Fax (34) 91 499 13 64 Bienvenido al número 42, de noviembre de 2007, de dotNetManía. Espero que me permita este off topic, amable lector. Quisiera dedicar nuestro modesto trabajo de este mes (con el permiso de mis compañeros) a Juan Antonio Cebrián, que lamentablemente nos dejó para siempre en un maldito día de octubre. Juan Antonio, creador y director de “La rosa de los vientos”, un programa de culto de la radio española, nos acompañó en innumerables noches en los cierres de esta revista. Su manera de divulgar, con rigurosidad pero con imaginación y amenidad, tanto en sus pasajes de la historia en la radio como en sus libros, ha sido una inspiración para nosotros durante años. Lejos de las grandes audiencias, sin saber por qué, consiguió miles y miles de fieles seguidores. Quien le escuchó, le siguió. Él, a quien tanto le gustaba contar historias de héroes y batallas, tuvo siempre como lema su famoso ¡fuerza y honor! Y cada noche se presentaba alegremente con un: “...éste que os acompaña como siempre, encantado y feliz como una lombriz, vuestro amigo y compañero: Juan Antonio Cebrián...”. Hasta siempre “Cebri”. Este mes tenemos muchas firmas nuevas y les doy a todos la bienvenida; desde aquí les invito a repetir cuando gusten. ¡Mira que tenemos buenos autores en nuestro país! A ver si nuestros estudiantes demuestran el nivelazo que tenemos en la nueva Imagine Cup, cuya versión española ya ha comenzado y se ha anunciado la versión internacional, que este año será en París. Hay mucha competencia, es cierto, pero a ver si ganamos algo este año y estamos aquí para contarlo. En este número publicamos en portada la primera parte, de un total de dos, del artículo “Tablet PC. Reconocimiento de escritura manual”, en el que Javier Roldán nos muestra cómo integrar la funcionalidad de reconocimiento de escritura manual con nuestras aplicaciones .NET para Tablet PC, que cada día adquiere mayor protagonismo en el mercado de los dispositivos móviles, precisamente por su capacidad para interactuar con el usuario mediante el uso de la escritura manual. Por cierto, la portada también es obra de Javier. Lleva razón Eduardo Quintás: en cualquier aplicación Web debemos tener en cuenta la escalabilidad a la hora de visualizar datos. En su artículo “Visualización de grandes conjuntos de datos en ASP.NET", Eduardo nos explica cómo un tráiler cargado de patatas puede tener el mismo rendimiento que un Ferrari descargado. Luis Miguel Blanco vuelve con un práctico artículo dedicado al DataGridView, concretamente “Aplicación de formato y selección manual de columnas sobre el control DataGridView”. El título lo dice todo. Daniel Seara, Dani a partir de ahora, amenaza con múltiples entregas sobre cómo montarse un marco de trabajo y empieza con el artículo “Herramientas genéricas para los componentes”. Por último, Joan Llopart y José Luis Montes terminan su serie dedicada a MSF y MOF con “Microsoft Operations Framework. La vida del software después del desarrollo según Microsoft.” Pero aún hay más. Espero que le guste. Imprime Gráficas MARTE Depósito Legal M-3.075-2004 Paco Marín << dotNetManía ISSN 1698-5451 3 sumario 42 El viejo mito de la reusabilidad 7-8 Siguiendo con los signos que nos indican si una empresa dedicada al desarrollo de software presenta el suficiente grado de madurez, hoy nos toca analizar lo que este artículo denomina el viejo mito de la reusabilidad. La denominación de viejo mito viene determinada por el hecho de que construir software que no sólo sirva para un propósito sino también para otros posibles futuros no deja de ser una de las aspiraciones de los equipos que desarrollan software desde que lo hacían en lenguaje binario. Visualización de grandes conjuntos de datos en ASP.NET 10-17 Estamos acostumbrados a leer ejemplos de ASP.NET con ADO.NET en los que nos enseñan las características más espectaculares de .NET Framework con sencillez y elegancia, pero en pocas ocasiones éstos hacen referencia al escalado o al uso real en una aplicación empresarial. Aplicación de formato y selección manual de columnas sobre el control DataGridView 18-25 Cuando obtenemos información de un origen de datos para visualizarla en un control de nuestro formulario, en gran número de ocasiones precisamos de un retoque o formateo de dichos datos –fecha y numéricos habitualmente–, que nos permita pulirlos y adecentarlos un poco antes de presentarlos a nuestros usuarios. En este artículo mostraremos las técnicas que a tal efecto pone a nuestra disposición el control DataGridView, así como la posibilidad de implementar manualmente la selección y ordenación de columnas, además de su posicionamiento. Tablet PC SDK (I). Reconocimiento de escritura manual 26-32 Los dispositivos conocidos como Tablet PC van adquiriendo día a día un mayor peso específico en el mercado de los dispositivos móviles. Estos híbridos, a medio camino entre los clásicos portátiles y las PDA, deben su éxito en gran medida a su capacidad para interactuar con el usuario mediante el uso de la escritura manual, permitiendo una interacción hombre-máquina mucho más intuitiva y natural. Herramientas genéricas para los componentes 34-38 En este artículo describimos cómo pueden implementarse funcionalidades genéricas que sean útiles a cualquier tipo de aplicación que debamos programar, considerando elementos de diseño como su pertenencia a un componente específico o como simples rutinas. Dentro de estas definiciones, aclararemos cómo es posible definir configuraciones específicas para cada componente que diseñemos, así como una estructura genérica que permita llevar una bitácora histórica de los errores dentro de las aplicaciones. Microsoft Operations Framework (y III) La vida del software después del desarrollo según Microsoft 40-44 Para cerrar esta serie de artículos de metodología, presentamos Microsoft Operations Framework, la propuesta de Microsoft para planificar, desplegar y mantener soluciones de servicio. Esta metodología complementa a Microsoft Solutions Framework, que hemos presentado en los artículos anteriores, y juntas forman la propuesta de Microsoft para la gestión del ciclo de vida de los proyectos de TI. dnm.todotnet.qa 46-48 Enlazándonos a LINQ dnm.laboratorio.net 50-53 VistaDB v3.2 dnm.comunidad.net 54-56 MVP Open Day y TTT 2007 October .NET Conference dnm.biblioteca.net 57 3D Programming for Windows Modelando procesos de negocio con Workflow Foundation dnm.desvan 58 <<dotNetManía noticias noticias noticias noticias noticias 6 Arranca en España la 5ª edición de la competición universitaria “Imagine Cup” de Microsoft Bajo el lema “Imagina un mundo donde la tecnología facilite un medioambiente sostenible”, más de 100 países se darán cita en la final internacional del concurso en París. Microsoft presenta por quinto año consecutivo en nuestro país la competición internacional para estudiantes universitarios Imagine Cup. Una iniciativa promovida desde el área de desarrollo de la compañía para fomentar la creatividad e innovación entre los estudiantes que sienten un especial interés por las nuevas tecnologías. En esta ocasión, y bajo el lema Imagina un mundo donde la tecnología facilite un medioambiente sostenible, los estudiantes deberán presentar proyectos tecnológicos aplicables a la vida real y que cumplan unos requisitos técnicos básicos como el diseño con .NET Framework. El concurso se presenta en dos fases, con materias y premios diferenciados por la superación de cada una de ellas. La última etapa requiere de la presentación del proyecto completo para su evaluación ante un jurado formado por profesionales del mundo académico, representantes de empresas tecnológicas y personal de Microsoft, en la gran final española del concurso que tendrá lugar en el mes de abril. Este año, Imagine Cup se compone de 9 categorías en total, de diferentes disciplinas tecnológicas: desarrollo de aplicaciones, desarrollo de aplicaciones embebidas, desarrollo de juegos, proyecto Hoshimi, desafío IT, algoritmos, cortometraje, diseño de interfaces y fotografía. Los alumnos interesados en formar parte de este proyecto tienen hasta el 21 de marzo para inscribirse en la competición en http://www.desarrollaelfuturo.com. Final internacional en París El equipo ganador de Imagine Cup España tendrá una plaza en la final internacional Imagine Cup que tendrá lugar en París (Francia) en el mes de julio de 2008. Los estudiantes españoles tendrán que demostrar sus conocimientos y la originalidad y consistencia de su proyecto frente al resto de finalistas, para optar a un premio de 15.000 dólares. Apoyo al ámbito universitario Junto con Imagine Cup, Microsoft inauguró una nueva edición del Microsoft University Tour, las sesiones técnicas sobre tecnología de desarrollo para estudiantes y profesores universitarios que la compañía viene celebrando en nuestro país desde el año 2002. A través de distintas ponencias, los asistentes reciben práctica información de la mano de profesionales de la compañía para mejorar los proyectos que presenten a Imagine Cup. Los interesados en conocer más sobre las distintas paradas del tour a lo largo de la geografía española pueden visitar la dirección http://www.microsoft.es/estudiantes, donde podrán encontrar información de productos, formación, libros y material didáctico centrado en tecnología Microsoft. El código fuente de las librerías de .NET Framework será liberado Recientemente, Scott Guthrie, director general de la División de Desarrolladores de Microsoft, anunció que el código fuente de la mayor parte de la librería de clases de .NET será puesto a disposición de los desarrolladores con la próxima aparición de .NET Framework 3.5 y Visual Studio 2008. Entre los espacios de nombres cuyo código podremos inspeccionar en un futuro ya muy cercano están todos los que conforman la BCL (System, System.IO, System.Collections, System.Configuration, System.Threading, System.Net, System.Security, System.Runtime, System.Text, entre otros), ASP.NET (System.Web), Windows Forms (System.Windows.Forms), ADO.NET (System.Data), XML (System.Xml) y WPF (System.Windows). Más adelante se añadirán a esta iniciativa nuevas librerías, como WCF, Workflow Foundation y LINQ. Todo ese código será liberado bajo la Licencia de Referencia de Microsoft (MS-RL en http://www.microsoft.com/resources/sharedsource/licensingbasics/referencelicense.mspx). El código fuente podrá ser descargado a través de un instalador independiente, lo que hará posible utilizar cualquier editor de texto para visualizarlo. Adicionalmente, Visual Studio 2008 ofrecerá soporte de depuración integrado para ese código fuente, de modo que será perfectamente factible saltar directamente desde el código de nuestras aplicaciones al código fuente de las librerías de .NET Framework. Indudablemente, disponer del acceso al código fuente de las librerías y de la integración de éste en el proceso de depuración será una gran ventaja para los desarrolladores .NET, que alcanzarán un grado de conocimiento mucho mayor sobre la implementación de las librerías, lo que les permitirá hacer un mejor uso de ellas y así desarrollar mejores aplicaciones. Más información en http://weblogs. asp.net/scottgu (en castellano, en http://thinkingindotnet.wordpress.com. También puede ver más información en http://www.hanselminutes.com/default.aspx?showid=101. opinión Antonio Quirós El viejo mito de la reusabilidad << Pero hemos de plantearnos si realmente hoy estamos más cerca que hace veinte años (y digo veinte porque yo soy tan viejo que entonces ya estaba en esta profesión) de conseguir acercarnos al objetivo de la reusabilidad del código. Cuando comenzamos a trabajar bajo el paradigma de la orientación a objetos pensábamos que ahí estaba la clave, que la fabricación de componentes reutilizables sería el factor que realmente impulsaría la reusabilidad. Han pasado entre quince y veinte años desde que comenzaron a popularizarse las herramientas que trabajan bajo esta orientación; hoy tenemos además SOA, como un paso más allá en ese mismo camino. Sin embargo, no observo en general un gran salto cualitativo. Las piezas de nuestras soluciones siguen siendo tan poco operables en otras soluciones como lo eran entonces. Hoy tendemos a construir frameworks, tecnológicos o de negocio, que nos ayuden a empaquetar mejor la lógica de nuestros proyectos para que pueda ser más fácilmente reusable. Pero ¿no hacíamos ya esto hace veinte años en librerías de funciones o clases? Lo hacemos ahora con tecnologías diferentes, pero en el fondo estamos ante un proceso similar y donde no termino de encontrar el salto cualitativo necesario. Hoy hablamos del proceso de industrialización al que intentamos llevar el desarrollo de software. En esto se encuentra la posibilidad de contratar piezas de nuestro sistema a proveedores que se encuentren en India o en otros lugares. Las factorías de software son una pieza clave en este entramado, sean propias o subcontratadas con terceros. Pero ¿hemos conseguido de verdad hacer del proceso de fabricación de software algo industrializable? La administración pública española, por ejemplo, sigue contratando la mayor parte de sus servicios como asistencias técnicas in situ por parte de personal cedido por los proveedores y que desarrolla sus proyectos en las oficinas públicas bajo el control de los coordinadores de las distintas entidades. Si hubiéramos logrado de una forma correcta habilitar procesos industriales para la fabricación de software, hoy habríamos vencido a dicha tendencia y los ministerios y las comunidades autó- <<dotNetManía Antonio Quirós es colaborador habitual de dotNetManía. Cofundador de las revistas clippeRManía, FOXManía y Algoritmo. Actualmente es director de operaciones en Alhambra-Eidos. Siguiendo con los signos que nos indican si una empresa dedicada al desarrollo de software presenta el suficiente grado de madurez, hoy nos toca analizar lo que este artículo denomina el viejo mito de la reusabilidad. La denominación de viejo mito viene determinada por el hecho de que construir software que no sólo sirva para un propósito sino también para otros posibles futuros no deja de ser una de las aspiraciones de los equipos que desarrollan software desde que lo hacían en lenguaje binario. 7 << dnm.opinión <<dotNetManía …si queremos hacer una política activa dirigida a crear código reusable, lo primero que hemos de hacer es convencer a nuestros clientes de que la fabricación industrial de software es posible… 8 nomas nos pedirían proyectos y productos en lugar de personas. En la reutilización no solo tiene importancia el factor de componentización del que estamos hablando, sino que también lo tiene, y mucha, el relativo a la gestión del conocimiento y a su difusión. Me refiero a que no solo podemos reutilizar piezas de software, sino también conocimientos. Hoy tenemos Internet con potentes buscadores, blogs llenos de información, foros donde compartimos nuestros conocimientos, sistemas de comunicación de todo tipo que realmente han conseguido acelerar esa faceta de la reutilización de la información. Hace veinte años teníamos elementos parecidos, pero aquí el salto cualitativo sí que ha sido enorme. Es cierto que existían BBS como Compuserve y Fidonet, pero la universalización del mundo de las comunicaciones ha hecho que hoy dispongamos de tal cantidad de material, con tantas facilidades para su acceso y consulta que las situaciones realmente no son comparables, máxime si contrastamos los precios, las velocidades de conexión que logramos, las posibilidades inalámbricas, etc. Como casi siempre, los grandes avances en el mundo de las soluciones de software vienen más de la mano de lo que los sistemas y las comunicaciones nos ofrecen que de aquello que los desarrolladores inventamos para mejor hacer nuestro trabajo. Ante todo este panorama, no queda otra que ponerse a hacer un serio planteamiento empresarial donde hagamos de la reusabilidad un concepto a perseguir de forma constante. Las empresas de desarrollo de software nos quejamos permanentemente de que nuestros clientes cada vez nos aprietan más en los precios mientras que los recursos técnicos que necesitamos para hacer los proyectos son cada vez más caros y más difíciles de conseguir y fidelizar. Es por ello que sólo podemos perseguir la mejora a través de la eficiencia, y en este ámbito la posibilidad real de la reusabilidad ensombrece cualquier otro elemento digno de tenerse en cuenta. Pero si queremos hacer una política activa dirigida a crear código reusable, lo primero que hemos de hacer es convencer a nuestros clientes de que la fabricación industrial de software es posible, de que si dejan de contratarnos personas y pasan a contratarnos proyectos o productos tendrán muchas ventajas operativas que de otro modo se perderán. Podrán, por ejemplo, exigirnos cumplimiento de plazos, estándares de calidad, niveles de servicio, etc. Pero en esto podría estar la base para permitirnos a las empresas que desarrollamos software mejorar nuestra sistemática de producción, industrializar más nuestro sistema, avanzar auténticamente en el concepto de reusabilidad. Y todo ello revertirá, sin duda, en mejoras importantes relativas a los plazos, la fiabilidad de lo entregado y los precios. No quiero terminar estas consideraciones de forma que parezcan contener sólo un tono pesimista sobre los posibles avances en el ámbito de la reusabilidad. Creo que realmente estamos en el camino correcto, sólo que los pasos son lentos. Hoy las empresas comenzamos a hablar de activos del ciclo de vida, comienzan a abundar las herramientas para crear, gestionar y, por tanto, reutilizar todos estos activos. Y el avance respecto a la concepción clásica de la reusabilidad viene determinado por el hecho de que hoy consideramos reusable no sólo un pedazo de código sino también un requisito, un caso de uso o cualquier otro componente, sea de código o de puro reflejo documental del conocimiento. Pero, además, no sólo tenemos herramientas, sino que también existe una fuerte convicción por parte del sector empresarial del software de que el único resquicio que tenemos para manejar mejor la eficiencia es la industrialización del desarrollo a través de la reutilización. A este impulso se unen los hechos de tener herramientas adecuadas, al modelo SOA hoy dominante en el mercado y al auge de las factorías de software, no como un capricho sino como la plasmación de una realidad, la de que tenemos que deslocalizar la fabricación de software, ya que donde surge la necesidad del cliente no tenemos suficiente personal técnico que la satisfaga. Estoy convencido de que todo esto hará imparable el proceso hacia la reusabilidad y que las empresas maduras serán aquellas que mejor sacarán partido de dicho proceso. En conclusión, y quizá dado lo disperso de la argumentación que he empleado, me gustaría resumir el hilo conductor de la misma, que en resumen es: • En los últimos veinte años construyendo software, sólo hemos hecho pequeños avances en el ámbito de la reusabilidad y éstos están más centrados en cuanto a cómo gestionamos el conocimiento necesario para construir soluciones. • La posibilidad de hacer código reusable es la base del avance hacia un proceso más industrializable en lo que a la construcción de software se refiere. • Un freno importante a este avance lo ponen las empresas (lideradas en España por la administración pública) y los sistemas de contratación con los que trabajan, más centrados en las personas que se contratan que en los productos que se pretenden conseguir y/o en la gestión de los proyectos necesarios para el desarrollo de dichos productos. • El avance en la noción de reusabilidad impulsaría fuertemente la tendencia hacia la construcción industrial de software y de ello se derivarían mejoras de eficiencia, calidad y precio en los sistemas que se construyen. asp.net Eduardo Quintás Visualización de grandes conjuntos de datos en ASP.NET Estamos acostumbrados a leer ejemplos de ASP.NET con ADO.NET en los que nos enseñan las características más espectaculares de .NET Framework con sencillez y elegancia, pero en pocas ocasiones éstos hacen referencia al escalado o al uso real en una aplicación empresarial. Eduardo Quintás es ingeniero en Informática y trabaja como técnico superior en el área de innovación tecnológica de la Universidad de La Coruña. Tiene amplia experiencia en el desarrollo de soluciones Web, especialmente aplicaciones ASP.NET y bases de datos. Realiza labores de development advisor para Plain Concepts. Colabora habitualmente en geeks.ms. Un ejemplo típico de lo dicho sucede cuando usamos un GridView paginado y/o ordenado que obtiene sus datos de un DataSet proveniente de una base de datos. En los ejemplos de la documentación nos encontramos con soluciones que solo funcionarían aceptablemente con un número pequeño de conjunto de datos; cuando el número de tuplas crece, el tiempo de respuesta crece exponencialmente. Haciendo un símil, es como si nos diesen un Ferrari rojo, una bala de plata, para transportar un solo kilogramo de patatas a gran velocidad. Sin embargo, en el mundo real hay que transportar más patatas. Con 4 toneladas de tubérculo no nos vale el Ferrari; necesitamos un tráiler, que suele ser más lento aunque efectivo. Este artículo enseña cómo podemos transportar 4, 8 ó 16 toneladas de patatas a casi la misma velocidad que si fuera un solo saco en un Ferrari ligeramente modificado. También sirve de introducción a las principales características disponibles para ASP.NET 2.0, como AJAX y la internacionalización de aplicaciones, y por otra parte hace uso de las características de Visual Studio 2005 for DB Professionals para la generación de conjuntos de datos de prueba. Supongamos el escenario en el que una aplicación web ASP.NET debe mostrar en un GridView un origen de datos extenso. Por ejemplo, una lista de mensajes a un usuario, unas entradas de un log, etc. Queremos que el usuario pueda acceder a los datos paginados y a la vez ordenarlos y filtrarlos por algún criterio. El requisito principal es que el usuario tenga acceso a todo el conjunto de datos desde la rejilla del modo más rápido posible. La solución más simple y la que encontramos en la documentación y tutoriales de .NET pasa por enlazar un origen de datos (ObjectDataSource, SqlDataSource) a un GridView. Las características de paginado y ordenación se pueden aplicar automáticamente. Incluso el SqlDataSource genera la consulta SQL por ti. Mientras tengamos un número reducido de usuarios concurrentes y pocos datos todo irá como la seda, pero esta solución fácil y rápida escala muy mal a poco que aumente la carga de peticiones o el número de datos a devolver. Con el enfoque anterior, el servidor tiene que descargarse el DataSet completo desde el origen de datos para, al final, mostrar solo una parte de él. Os podéis imaginar que es algo tremendamente ineficiente en cuanto crezca el número de datos o de peticiones. Por otra parte, una solución basada en caché ASP.NET de servidor no parece apropiada por dos motivos: 1. DataSet extensos implican un gran uso de la memoria física del servidor, la afinidad suele ser por sesión de cliente. 2. Que los datos recuperados dependan de algún parámetro intrínseco al cliente y su sesión (por ejemplo, una colección de emails de un usuario) y la caché, a nivel de aplicación, no pueda aplicarse. << dnm.asp.net En este artículo voy a proponeros una solución elegante a la visualización eficiente de grandes conjuntos de datos que escala razonablemente bien. Vamos a conseguir que el acceso a los datos paginados a través de la rejilla dependa lo mínimo posible del número de tuplas de la tabla, intentando mantener los tiempos de respuesta estables dentro de la regla de los 7 segundos, que estipula que una página Web nunca debería de tardar más de ese tiempo en dar una respuesta al usuario. Figura 1. Aspecto de la interfaz de la aplicación de ejemplo Para nuestros propósitos, definiremos un escenario bastante común. Supongamos que necesitamos mostrar una bandeja de entrada de mensajes, mostrando un icono de estado por mensaje así como la fecha de envío, el remitente y el asunto. Los datos mostrados podrán ordenarse ascendente o descendentemente por cualquier columna. Por defecto, y por comodidad, los mensajes se mostrarán ordenados descendentemente por fecha de envío. A la bandeja se le podrá aplicar un filtro temporal sencillo, pudiendo ver solo los mensajes recibidos en el día, en la semana, en el mes o todos los mensajes. También ha de ser posible la selección del número de mensajes por página. Diseño e implementación Siguiendo los requisitos del escenario, la aplicación ASP.NET “de juguete” tiene el aspecto de la figura 1. Podéis descargar el proyecto VS2005 original desde www.dotnetmania.com o desde mi blog [1]. En la capa de vista se han empleado las tecnologías de Microsoft AJAX y las características de Temas e Internacionalización (i18n) de ASP.NET 2.0. También se ha utilizado el control ObjectDataSource. Respecto a AJAX, se empleó un UpdatePanel, que contiene los controles principales del ejemplo, incluido el GridView. También se usa un ProgressPanel para aquellos postbacks que duren más de medio segundo. En cuanto los temas, se incluye el tema WhiteCushion, que define todo los atributos de visualización de la página en el archivo de estilo WhiteCushion.css, y el archivo de pieles WhiteCushion.Skin. Destacamos el estilo asociado con el UpdatePanel para presentar un mensaje de espera, que se muestra centrado en la página, mientras se espera la respuesta del servidor. #UpdateProgressPanel { padding: 1em; border: solid 1px gray; position: Absolute; top: 45%; left: 40%; width: 20%; height: 2.5em; background-color : White; vertical-align : middle; } #UpdateProgressPanel #ProgressPanelContainer { padding: 0.5em 1em 0.5em 4em; background: white url(‘../../images/progress_mini.gif’) no-repeat left; } La página está disponible para la cultura es-ES y, por defecto, para enUS. La cultura se elige en función de las preferencias del navegador. En IE7, las preferencias de idioma pueden editarse en “Herramientas” | “Opciones de Internet” | “General” | “Idiomas”. Se utiliza la localización implícita, por lo que en los archivos de recursos (Default.aspx.es.resx y Default.aspx.resx) hay que incluir el atributo a localizar del control junto con la clave de localización. También se hace uso de controles <asp:Localized> para los literales de la página. Por ejemplo: Default.aspx <%@ Page Language=”C#” UICulture=”Auto” Culture=”Auto” AutoEventWireup=”true” Codebehind=”Default.aspx.cs” Inherits=”GridViewSample._Default” meta:resourcekey=”PageResource” %> … <asp:Localize ID=”l3” runat=”server” meta:resourcekey=”LiteralNoData” /> Default.aspx.cs Figura 2. Mensaje de espera para el control AJAX UpdateProgress GetLocalResourceObject( “StatusInfoTimeElapsed”).ToString(); <<dotNetManía Escenario 11 << dnm.asp.net Default.aspx.es.resx <data name=”LiteralNoData.Text” xml:space=”preserve”> <value>No hay datos disponibles</value> </data> <data name=”StatusInfoTimeElapsed” xml:space=”preserve”> <value>Tiempo: <b>{0}</b><br/>Filas totales: <b>{1}</b></value> </data> Para mostrar los datos se utilizó un control GridView; pueden observarse los atributos de internacionalización implícita y el uso de DataImageUrlFormatString, en la primera columna, para mostrar un pequeño icono. <asp:ObjectDataSource ID=”MessageDataSource” runat=”server” TypeName=”GridViewSample.MessageBusinessLogic” EnablePaging=”true” SelectMethod=”Select” SortParameterName=”sortExpression” MaximumRowsParameterName=”pageSize” SelectCountMethod=”GetRowsCount” StartRowIndexParameterName=”startRow” OnSelected=”MessageDataSource_Selected”> <SelectParameters> <asp:ControlParameter PropertyName=”SelectedIndex” Name=”preFilter” ControlID=”DDLFilter”/> <asp:Parameter Name=”timeElapsed” /> </SelectParameters> </asp:ObjectDataSource> define una suscripción al evento OnSelected, que servirá para actualizar el control con las estadísticas de la consulta. Los parámetros definidos en la sección SelectParameters permiten establecer una configuración extra para rea- <asp:GridView ID=”MessageGridView” DataSourceID=”MessageDataSource” RunAt=”Server” PageSize=”20” AutoGenerateColumns=”False” AllowPaging=”True” AllowSorting=”True” BorderWidth=”0”> <PagerSettings Mode=”NumericFirstLast”/> <Columns> <asp:ImageField DataImageUrlField=”Flag” AlternateText=”Flag” DataImageUrlFormatString=”images/MessageStatus{0}.jpg” /> <asp:BoundField DataFormatString=”{0:d}” DataField=”Sent” ItemStyle-Wrap=”false” SortExpression=”Sent” meta:resourcekey=”BoundFieldSent”/> <asp:BoundField DataField=”FromAddress” SortExpression=”FromAddress” meta:resourcekey=”BoundFieldFrom”/> <asp:BoundField HtmlEncode=”False” DataField=”Subject” SortExpression=”Subject” meta:resourcekey=”BoundFieldSubject” /> </Columns> <EmptyDataTemplate> <asp:Localize ID=”l3” runat=”server” meta:resourcekey=”LiteralNoData” /> </EmptyDataTemplate> </asp:GridView> <<dotNetManía El GridView está enlazado a un ObjectDataSource llamado MessageDataSource. Esta clase de DataSource, intro- 12 ducido en ASP.NET 2.0, permite crear fuentes de datos de objetos empresariales, incluidos DataSet, sin necesidad de implementar una clase controladora en la interfaz que ponga de acuerdo los controles visuales con las fachadas de nuestra lógica de negocio. En el control ObjectDataSource se puede indicar el método y el tipo que resolverá la obtención de datos y el número de columnas devueltas, así como si acepta paginado y ordenación. También lizar la selección de datos; en nuestro caso, un filtro temporal sobre los datos. El parámetro timeElapsed es actualiza- Figura 3. Mensaje de tiempo empleado y filas sobre las que se ha paginado do por el método que resuelve la sentencia SELECT y contiene información estadística sobre la consulta. Todos los parámetros definidos bajo SelectParameters son de entrada y salida. Aunque existe un atributo (Direction) para indicar la dirección de los parámetros, ASP.NET no lo usa y está reservado para futuros usos, como indica la documentación. El tipo GridViewSample.MessageBusinessLogic (figura 4) es la fachada de nuestra lógica de negocio, y gracias a ObjectDataSource podemos invocarla sin necesidad de escribir una clase intermedia, controladora de interfaz. En el control ObjectDataSource se hace referencia a los métodos públicos Select y GetRowsCount de MessageBusinessLogic. El primero realiza la selección de los datos, mientras que el segundo protected void MessageDataSource_Selected(object sender, ObjectDataSourceStatusEventArgs e) { if (e.OutputParameters!=null && e.ReturnValue!=null) StatusInfo.Text = String.Format(GetLocalResourceObject(“StatusInfoTimeElapsed”).ToString(), e.OutputParameters[“timeElapsed”], e.ReturnValue.ToString()); } << dnm.asp.net El método privado ConfigureDateFilter establece los valores de los pará- devuelve el número de filas totales de la consulta para poder calcular el número de páginas. En este ejemplo, por motivos de rendimiento, se aprovecha la conexión en el método Select para obtener el número de tuplas devueltas. También se retrasa la apertura de la conexión lo máximo posible y se cierra en cuanto es posible, siguiendo así las buenas prácticas asociadas a los esquemas de acceso a datos con concurrencia optimista. El control ObjectDataSource ejecuta primero Select y luego GetRowsCount; para que timeElapsed tenga un valor válido, es necesario devolverlo en GetRowsCount y no en el método Select . En caso de tener métodos sobrecargados, ObjectDataSource siempre elegirá aquellos cuya firma coincide con los parámetros por defecto y los definidos por el desarrollador en SelectParameters. public DataSet Select(string sortExpression, FilterByDate preFilter, int startRow, int pageSize, string timeElapsed) { DateTime start = DateTime.Now; DataSet ds = new DataSet(); using (SqlConnection conn = new SqlConnection( ConfigurationManager.ConnectionStrings[“SampleDB”].ConnectionString)) { string query = String.Format( SQL_GETMESSAGES, (sortExpression == string.Empty) ? SQL_DEFAULTORDER : sortExpression); SqlCommand cmd = new SqlCommand(query, conn); cmd.CommandTimeout = COMMAND_TIMEOUT; cmd.Parameters.AddWithValue(“@FirstRow”, startRow); cmd.Parameters.AddWithValue(“@LastRow”, startRow + pageSize); ConfigureDateFilter(cmd, preFilter); // GridView data query conn.Open(); (new SqlDataAdapter(cmd)).Fill(ds); // Counts total rows cmd.CommandText = SQL_ROWCOUNT; rowsCount = (int)cmd.ExecuteScalar(); conn.Close(); } this.timeElapsed = (DateTime.Now - start).ToString(); return ds; } public int GetRowsCount(FilterByDate preFilter, out string timeElapsed) { timeElapsed = this.timeElapsed; return rowsCount; } cmd.Parameters.Add(“@param_string”, SqlDbType.NVarChar, 64); cmd.Parameters[0].Value = “abc”; Una explicación más detallada del problema puede consultarse en el blog [3]. Pasemos a hablar de la consulta SQL. Puesto que nuestro GridView debe ser capaz de ordenar los datos por columnas de forma ascendente y descendente, necesitamos pasarle ese parámetro a la consulta SQL. El problema es que el orden no puede parametrizarse como se hace con los valores en una cláusula WHERE, por lo que es necesario construir una nueva consulta cuando se cambia el orden. Este hecho genera un plan de ejecución distinto para cada orden. En nuestro caso, siguiendo un escenario real, los datos son ordenados por el criterio más relevante, concretamente por el atributo Sent (fecha de envío) de forma descendente. Primero se realiza un SELECT utilizando ROW_NUMBER() , una columna especial de Transact SQL que devuelve el número secuencial de una fila de una partición de un conjunto de resultados, pudiendo definir el orden de los resultados. Sobre este conjunto de resultados se aplica una restricción, usando @FirstRow y @LastRow , para devolver solo las tuplas que toca ver en la página actual. <<dotNetManía Figura 4. Diagrama de la clase MessageBusinessLogic metros de la consulta SQL relacionados con la fecha para obtener los mensajes de: hoy, esta semana, este mes y todos los mensajes. Hay que prestar especial atención al uso del método para definir parámetros SQL AddWithValue. Un mal uso de este método puede provocar que se creen y se almacenen en la caché de SQL Server varios planes de ejecución. Podemos usar AddWithValue siempre que el tipo de dato tenga un tamaño preestablecido, como ocurre en los casos de int, DateTime, etc. Pero nunca si es un string; en ese caso es necesario definir previamente el tamaño con el búfer máximo esperado, por ejemplo: 13 << dnm.asp.net private const string SQL_DEFAULTORDER = “Sent DESC”; private const string SQL_ROWCOUNT = @”SELECT COUNT(*) FROM dbo.Message WHERE Sent ” + “BETWEEN @minDate AND @maxDate”; private const string SQL_GETMESSAGES= @“ WITH PagedMessage AS ( ” + “ SELECT Id, Flag, Sent, FromAddress, Subject, ” + “ ROW_NUMBER() OVER (ORDER BY {0}) AS RowNumber ” + “ FROM dbo.Message WHERE Sent BETWEEN @minDate AND @maxDate) ”+ “ SELECT Id, Flag, Sent, FromAddress, Subject ” + “ FROM PagedMessage ” + “ WHERE RowNumber BETWEEN @FirstRow AND @LastRow ” + “ ORDER BY RowNumber ASC ”; Para aquellos que no estén muy familiarizados con los gestores relacionales de base de datos, les recomiendo que ejecuten cada consulta de su desarrollo en Microsoft SQL Server Management Studio (vale la versión Express, que es gratuita para ciertos usos) y comprueben el plan de ejecución. Las operaciones principales deberían optimizarse como Index Seek o Clustered Index Seek y evitarse los Sequential Scan o Table Scan. La edición de Visual Studio 2005 for DB Professionals permite entre otras cosas generar pruebas de unidad sobre nuestro esquema y, lo más interesante Figura 5. Plan de ejecución para la consulta principal La consulta SQL_ROWCOUNT obtiene el número de tuplas totales para el filtro temporal actual. Este valor es necesario para que el ObjectDataSource pueda indicarle al GridView cuál es el volumen de paginado y mostrar adecuadamente los enlaces a las páginas. Para mostrar todos los mensajes, abrimos el intervalo temporal entre la fecha más pequeña y la más grande. Nunca se han de utilizar las propiedades MaxDate y MinDate de System.DateTime con SQL Server, pues se lanzaría una excepción ya que la fecha máxima y mínima de SQL Server difieren de la de las del tipo DateTime definidas en .NET Framework. Es necesario utilizar SqlDateTime.MinDate y SqlDateTime.MaxDate. <<dotNetManía Esquema de datos del ejemplo 14 Para crear el esquema y generar los datos de prueba se empleó Visual Studio 2005 Team Edition for DB Professionals. Para crear las consultas y revisar los planes de ejecución estimados y reales, se utilizó Microsoft SQL Server Management Studio Express. private static void ConfigureDateFilter(SqlCommand cmd, FilterByDate filter) ... case FilterByDate.Today: cmd.Parameters.AddWithValue(“@mindate”, DateTime.Today); cmd.Parameters.AddWithValue(“@maxdate”, DateTime.Today.AddDays(1)); break; default: cmd.Parameters.AddWithValue(“@mindate”, SqlDateTime.MinValue); cmd.Parameters.AddWithValue(“@maxdate”, SqlDateTime.MaxValue); break; ... El esquema de datos del ejemplo se presenta a continuación. Los índices son obligatorios. Sin ellos, SQL Server no podría realizar una planificación óptima de la consulta. en nuestro caso, generar conjuntos de datos de prueba. Creando un proyecto para SQL Server 2005 se puede crear en la carpeta del proyecto Data Generation Plans un plan de generación de CREATE TABLE dbo.Message ( Id INT IDENTITY PRIMARY KEY, Flag INT NOT NULL, Sent DATETIME NOT NULL, FromAddress VARCHAR(64) NOT NULL, Subject VARCHAR(128) NOT NULL, ); CREATE INDEX idxFromAddress ON dbo.Message ( FromAddress ASC); CREATE INDEX idxSent ON dbo.Message ( Sent DESC); CREATE INDEX idxSubject ON dbo.Message ( Subject ASC); << dnm.asp.net Figura 6. Configuración para un plan de generación de datos en Visual Studio 2005 for DB Professionals SATA, Windows XP SP2, Visual Studio 2005 for DB Professionals y SQL Server Express 2005. Se hizo una prueba con 8.388.608 tuplas y los tiempos se incrementaron notablemente, pero ello no puede ser achacable a la capa de acceso de datos sino a la configuración, sin ningún tipo de optimización de SQL Server 2005 Express, y la carga de mi sistema junto con sus limitaciones hardware; los índices ocupaban algo más de 1 Gb, al igual que la tabla física, y el sistema estaba paginando la memoria virtual continuamente. Respecto a la planificación de consultas, para el primer y segundo escenario se genera un plan para cada una de las dos consultas. En el tercer escenario se crea un plan para una sola consulta. Los planes para las consultas dependientes del ObjectDataSource son del tipo Prepared, y la del SqlDataSource es Adhoc. datos. En él podremos especificar el número de tuplas que deseamos y también las plantillas de generación de valores. Para cada columna se puede indicar el rango de valores, la distribución e incluso se puede aplicar una expresión regular para generar los atributos alfanuméricos o enlazar a un conjunto de datos reales. Pruebas y resultados Gráfica 1. Resultados para el ObjectDataSource correctamente paginado frente al acceso clásico con un SqlDataSource. En el eje X, los números de tuplas. En el eje Y, el tiempo en segundos Gráfica 2. Resultados para el ObjectDataSource correctamente paginado con y sin índices adecuados en la tabla. En el eje X, los números de tuplas. En el eje Y, el tiempo en segundos. <<dotNetManía Para probar la aplicación de juguete se crearon tres escenarios; para los dos primeros se aplica la técnica descrita basada en ObjectDataSource, uno con índices adecuados en la tabla Message y en el otro sin índices. En el tercer escenario se utiliza un GridView con un SqlDataSource, el ejemplo más común de visualización de datos con ASP.NET. Para cada uno ellos se midieron tiempos con 65.536, 262.144, 524.288 y 1.048.576 tuplas. Se tomaron dos medidas a partir de la media de tres muestras. La primera medida toma el tiempo que le lleva mostrar páginas al GridView del principio del conjunto de datos. La segunda medida toma el tiempo en las páginas finales del GridView. El entorno de software y hardware de pruebas ha sido el siguiente: Pentium IV a 3Ghz con 2GB de RAM, disco 15 << dnm.asp.net Del artículo [3] se desprende que las consultas Adhoc, o de corto periodo de vida, se comportan mal en cuanto a rendimiento cuando el número de usuarios es alto. Este hecho no ocurre con las consultas parametrizadas de los dos primeros escenarios. Hay que señalar que la consulta autogenerada por el control SqlDataSource no utiliza nombres totalmente calificados a la hora de referenciar a las tablas. El uso de éstos mejoraría ligeramente el rendimiento, ya que facilitaría la resolución de la localización de la tabla en la base de datos. nario 3 es exponencialmente más lento que el 1 ó el 2. Si bien para un conjunto pequeño de tuplas (65.536) las diferencias no parecen grandes (algo menos de un segundo), en cuanto crece en tamaño la tabla (1M de tuplas) las diferencias se disparan a más de 100 segundos, un tiempo imposible de aceptar en un entorno Web e incluso en una aplicación de escritorio. Se ha encontrado una diferencia de tiempo para los escenarios 1 y 2 dependiendo de si se consultan las páginas iniciales de la tabla o las finales. Esta diferencia de tiempo parece que crece linealmente Consulta Escenarios 1 y 2 basados en ObjectDataSource y paginado óptimo Escenario 3, consulta autogenerada por un SqlDataSource Tipo (@FirstRow int,@LastRow Compiled Plan int,@mindate datetime,@maxdate datetime)SELECT COUNT(*) FROM dbo.Message WHERE Sent BETWEEN @minDate AND @maxDate Prepared (@FirstRow int,@LastRow int,@mindate datetime,@maxdate datetime) WITH PagedMessage AS ( SELECT Id, Flag, Sent, FromAddress, Subject, ROW_NUMBER() OVER (ORDER BY Sent DESC) AS RowNumber FROM dbo.Message WHERE Sent BETWEEN @minDate AND @maxDate ) SELECT Id, Flag, Sent, FromAddress, Subject FROM PagedMessage WHERE RowNumber BETWEEN @FirstRow AND @LastRow ORDER BY RowNumber ASC Compiled Plan Prepared SELECT * FROM [Message] ORDER BY [Sent] DESC Compiled Plan Adhoc Tabla 1. Planes de consulta generados para los escenarios 1 y 2 (las dos primeras filas) y para el escenario 3 (última fila) Para todos aquellos interesados en consultar las vistas del sistema relacionadas con el plan de ejecución de consultas y procedimientos almacenados, os remito de nuevo a los post en el blog [2]. Para un vistazo rápido a la caché de planes de ejecución podéis ejecutar la siguiente consulta: <<dotNetManía select text, cacheobjtype, objtype, usecounts from sys.dm_exec_cached_plans cp cross apply sys.dm_exec_sql_text(plan_handle) 16 Discusión Las gráficas del apartado anterior no dejan lugar a dudas. En cuanto el conjunto de datos crece, el esce- con el número de tuplas. Sin poder encontrar una explicación definitiva, intuyo que probablemente se debe a que SQL Server tarda en recorrer índices tan extensos (cerca de 512 Mb). Este retraso es aceptable en el sentido de que es extraño que el usuario acceda a las páginas finales. Porque podría invertir el orden, que es una operación muy rápida, o filtrar más los datos. Otra opción es suprimir el botón de acceso a la última página, algo bastante común cuando se necesita devolver gran cantidad de datos paginados, tal como hace el buscador Google. El motivo de incluir dos escenarios con ObjectDataSource, uno con índices y otro sin ellos, nos sirve para demostrar la importancia del uso de índices en un gestor de base de datos relacional. Solo hay que << dnm.asp.net ver cómo se incrementa exponencialmente la diferencia de tiempos a medida que crece el número de tuplas (gráfica 2) que, sin ser tan exagerada como la del escenario 3, es un tiempo considerable (unos 12 segundos de diferencia para un 1M de tuplas). Han de crearse los índices justos y necesarios, dependiendo de la frecuencia y el tipo de consulta que se hagan con los datos. Un número excesivo de índices implica un mayor mantenimiento de los mismos por parte de SQL Server, ralentizando las operaciones de inserción, modificación y borrado de tuplas. Respecto al diseño de los escenarios 1 y 2, el GridView es alimentado por un ObjectDataSource que tiene un modo elegante de comunicarse con una fachada de la lógica de negocio. La interfaz de usuario que controla el paginado y el orden de los datos mostrados está ya implementada y podemos centrarnos en lo importante, en la lógica de negocio. Siendo una funcionalidad de las más productivas y útiles de ASP.NET 2.0. He utilizado objetos DataSet sin tipar. Los que me conocen saben que soy amigo del modelo de lógica de negocio basado en objetos empresariales. Sin embargo, en este escenario, es necesario renunciar a ellos y buscar la estructura de datos más equilibrada y flexible que tenemos en .NET para que la lógica de negocio pueda enviar rápidamente datos a la capa de vista. Esta idea está reflejada en el patrón de diseño FastLane Reader [4]. Los DataSet de ADO.NET son los contenedores ideales para implementar este patrón de forma óptima y sin necesidad de instanciar una gran cantidad de objetos, que si bien es una acción rápida en .NET Framework en comparación con Java, no es un tiempo desdeñable que evitamos en gran medida utilizando DataSet. Tened presente que el uso de FastLane Reader no excluye, sino más bien complementa, el tener un modelo conceptual de objetos con sus correspondientes DAO y fachadas como forma normal de hacer operaciones CRUD con nuestros objetos empresariales. Una lección aprendida, que debemos recordar y aplicar del mundo real, es que si nuestro cliente tiene una operación común y recurrente con un tiempo excesivo de respuesta, poco le va a importar que le razonemos que tiene el código y diseño lógico más puro que una gota de rocío; su insatisfacción será enor- me. Generalmente en un software en producción el requisito principal es el rendimiento por encima de una arquitectura rígida de objetos empresariales. Equilibrar la arquitectura y el rendimiento no es fácil y tiene más arte que ingeniería. Con la debida disciplina y a través del patrón FastLane Reader conseguimos rendimiento en componentes de visualización críticos con alta tasa de accesos, aunque sintamos que sacrificamos una arquitectura homogénea. Está totalmente justificado. En cuanto a la base de datos, deciros que también se planteó la posibilidad de utilizar procedimientos almacenados, aprovechando que se compilan y su plan de ejecución nunca se descarta de una caché. Pero en nuestro caso tendríamos que construir uno por criterio de ordenación o bien crear una consulta dinámica con EXEC ‘query’ dentro de un solo procedimiento, perdiendo las ventajas de la compilación. Se creó uno con orden por fecha descendente y arrojó los mismos tiempos que la consulta. Una vez consultada la documentación en el blog [2] de cómo SQL Server trata la caché de planes de consulta y preguntando a MVP de SQL Server llegamos a la conclusión de que en este escenario, de alto número de accesos, es muy probable que las consultas estén siempre en la caché de planes de ejecución y no compensa crear los procedimientos. Una crítica a lo expuesto es que ha sido probado con un alto número de tuplas pero con solo un usuario concurrente. Para tener unas conclusiones más amplias se debería probar una ejecución masiva con varios clientes lanzando peticiones concurrentes y estudiar los tiempos de respuesta. También sería adecuado probarlo con un SQL Server 2005 Standard, con el servidor de aplicaciones y de base de datos separados. Concluimos que, atendiendo a la regla que dice que una página Web debería cargar en menos de 7 segundos para que el usuario no la abandone, solo el escenario 1 serviría. De los presentados es el único escalable según aumenta el número de tuplas. Con pequeñas variantes, debería ser el enfoque utilizado por desarrolladores que implementen aplicaciones que necesiten visualizar grandes conjuntos de datos, especialmente en aplicaciones ASP.NET. Ya tenéis un tráiler a velocidad de Ferrari. A disfrutarlo con muchas patatas. Referencias [ 1 ] Blog de Eduardo Quintás en geeks.ms: http://geeks.ms/blogs/quintas/. programming practices”. SQL Programmability & API Development Team Blog, en http://blogs.msdn.com/sqlprogrammability/archive/2007/01/13/6-0-best-programming-practices.aspx. [ 3 ] Documento sobre el rendimiento de las consultas Adhoc, en http://support.microsoft.com/kb/243588. [ 4 ] Patrón de diseño FastLane Reader [Java Blue Prints], en FastLaneReader-detailed.html. http://java.sun.com/blueprints/patterns/ <<dotNetManía [ 2 ] “Best 17 plataforma.net Luis Miguel Blanco Aplicación de formato y selección manual de columnas sobre el control DataGridView Cuando obtenemos información de un origen de datos para visualizarla en un control de nuestro formulario, en gran número de ocasiones precisamos de un retoque o formateo de dichos datos –fecha y numéricos habitualmente–, que nos permita pulirlos y adecentarlos un poco antes de presentarlos a nuestros usuarios. En este artículo mostraremos las técnicas que a tal efecto pone a nuestra disposición el control DataGridView, así como la posibilidad de implementar manualmente la selección y ordenación de columnas, además de su posicionamiento. Preparando el escenario de trabajo Supongamos que nos encontramos desarrollando un formulario en el que necesitamos visualizar dentro de un DataGridView la consulta SQL del listado 1. SELECT ProductKey, SpanishProductName, ListPrice, EndDate, Color FROM DimProduct WHERE ListPrice IS NOT NULL Listado 1 Luis Miguel Blanco es redactor de dotNetManía. Es consultor en Alhambra-Eidos.Ha escrito varios libros y decenas de artículos sobre la plataforma .NET (lalibreriadigital.com) Pero como siempre ocurre en estos casos, los requerimientos de la aplicación dictan que ciertos campos de la tabla deberán mostrarse y formatearse de un modo distinto al estándar, con el agravante de que estas operaciones no podrán ser realizadas para todos los casos, sino en función de ciertas condiciones, determinadas por los valores de algunos campos de la tabla. Es por ello que no podremos recurrir directamente a la solución que a priori sería la más rápida: la creación de estilos para las columnas nece- sarias, configurando en dichos estilos las propiedades oportunas para alterar el aspecto que los datos tienen por defecto. Sí que emplearemos estilos para conseguir nuestros propósitos, pero será dando un rodeo que nos llevará por CellFormatting, un utilísimo y versátil evento perteneciente a DataGridView. CellFormatting. Estilo y formato condicionales Se trata de un evento que se produce para cada celda del control DataGridView que precise ser dibujada, por lo que en el método manipulador de evento que escribamos añadiremos el código encargado de realizar las comprobaciones oportunas, para de esta manera aplicar cuando sea necesario los cambios de estilo sobre aquellas columnas que lo requieran. La información complementaria que necesitemos manejar, como puede ser el índice de columna o fila sobre la que se está produciendo el evento, la recibiremos en un parámetro de tipo DataGridViewCellFormattingEventArgs. Una vez descrito brevemente este evento, pasemos a continuación a exponer las diferentes técnicas de formato que utilizaremos, en función del << dnm.plataforma.net valor con el que debamos trabajar en cada ocasión. Aplicar formato en función del valor de la celda actual Figura 1. Formato aplicado a números y fechas Formato indirecto La siguiente técnica que mostraremos se basa en aplicar un formato de forma indirecta, o lo que es lo mismo, cuan- private void dgvGrid_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e) { // si la celda a dibujar es ListPrice... if (this.dgvGrid.Columns[e.ColumnIndex].Name == “ListPrice”) { // ...y se cumple esta condición if ((decimal)e.Value > 1000) { // modificar propiedades del estilo de la celda e.CellStyle.Alignment = DataGridViewContentAlignment.MiddleCenter; e.CellStyle.BackColor = Color.DarkTurquoise; e.CellStyle.ForeColor = Color.WhiteSmoke; e.CellStyle.Font = new Font(“Comic Sans MS”, 12); e.CellStyle.Format = “#,#.#0”; } } // si la celda a dibujar es EndDate... if (this.dgvGrid.Columns[e.ColumnIndex].Name == “EndDate”) { // ...y se cumple esta condición if (e.Value != System.DBNull.Value && ((DateTime)e.Value).Year == 2002) { e.CellStyle.Font = new Font(“Comic Sans MS”, 8 , FontStyle.Italic | FontStyle.Bold);); e.CellStyle.Format = “dddd, dd-MMMM-yyyy”; } } //.... } Listado 2 do se vaya a dibujar la celda de la columna ProductKey, comprobamos el valor de otra celda —la que se halla en la columna EndDate—, y según el año que tenga esa fecha, cambiamos o no el estilo de la celda de ProductKey. En este caso, además, no accedemos directamente a los miembros de CellStyle, sino que creamos previamente un objeto de estilo que finalmente asignamos a esta propiedad, como vemos en el listado 3. Podemos comprobar el efecto en la figura 2. Emplearemos estilos, pero será dando un rodeo que nos llevará por el utilísimo y versátil evento CellFormatting <<dotNetManía Esta técnica consiste en comprobar la columna a la cual pertenece la celda que se va a dibujar, así como su valor, que nos facilita la propiedad Value del objeto DataGridViewCellFormattingEventArgs. En el caso de que se cumpla una determinada condición sobre el valor, utilizaremos la propiedad CellStyle del mencionado objeto para modificar el aspecto habitual de la celda, resaltando ciertas características de su estilo. Este caso queda ilustrado en el listado 2, donde aplicamos esta operación sobre los campos ListPrice y EndDate del origen de datos. La figura 1 muestra la apariencia resultante de esta operación. 19 << dnm.plataforma.net private void dgvGrid_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e) { //.... // si la celda a dibujar es ProductKey... if (this.dgvGrid.Columns[e.ColumnIndex].Name == “ProductKey”) { DateTime dtEndDate; // comprobar el valor del campo EndDate, if (DateTime.TryParse( this.dgvGrid.Rows[e.RowIndex].Cells[“EndDate”].Value.ToString(), out dtEndDate)) { // si se cumple esta condición if (dtEndDate.Year == 2003) { // crear un estilo... DataGridViewCellStyle styCelda = new DataGridViewCellStyle(); styCelda.BackColor = Color.PaleVioletRed; styCelda.ForeColor = Color.LightYellow; styCelda.Font = new Font(“Castellar”, 12, FontStyle.Bold); // ...y asignarlo a la celda e.CellStyle = styCelda; } } } //.... } private void dgvGrid_CellFormatting( object sender, DataGridViewCellFormattingEventArgs e) { //.... //si la celda a dibujar es Color... if (this.dgvGrid.Columns[ e.ColumnIndex].Name==“Color”) { // cambiar el texto de la celda switch (e.Value.ToString()) { case “Black”: e.Value = “Negro”; break; case “Blue”: e.Value = “Azul”; break; case “Grey”: e.Value = “Gris”; break; //.... } } //.... } Listado 3 fuente de datos. Dado que la propiedad DataGridViewCellFormattingEventArgs.Value es de lectura y escritura, vamos a tomar su valor original, volviéndolo a asignar ya traducido, como vemos en el listado 4. La figura 3 muestra el resultado de esta traducción. Figura 2. Modificar el aspecto de una celda en base al valor de otra <<dotNetManía Cambiando el valor original de la celda 20 Seguidamente ilustramos la capacidad de cambiar el valor que originalmente debería mostrar la celda por uno propio; en este caso sin alterar para nada los estilos de la columna —aunque perfectamente podríamos haberlo hecho—. La columna a manipular corresponderá al campo Color de la Figura 3. Cambiando (traduciendo) el valor de las celdas Listado 4 Observando el resultado al ejecutar el formulario, nos percataremos de que las cabeceras muestran el texto “a sus anchas”. Quiero con esto decir que no ocurre como en otras ocasiones, donde el espacio dedicado a la cabecera de columna era extremadamente justo, mostrándose incluso algunos títulos recortados. ¿Cómo logramos esta característica? Pues mediante la utilización de dos líneas de código en el evento Load del formulario: la primera para indicarle al control que ajuste automáticamente el tamaño de las celdas con la propiedad AutoSizeColumnsMode; y la segunda en la propiedad Padding del estilo de la cabecera, donde usando un objeto de dicho tipo, conseguimos establecer un espacio alrededor del título y los límites de la celda de cabecera. Veámoslo en el listado 5. << dnm.plataforma.net // establecer el tamaño automático para las celdas this.dgvGrid.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.DisplayedCells; //.... // estilo para la cabecera DataGridViewCellStyle styCabecera = new DataGridViewCellStyle(); //.... styCabecera.Padding = new Padding(10); this.dgvGrid.ColumnHeadersDefaultCellStyle = styCabecera; Listado 5 Listado 6 Cuando utilizamos un estilo aplicado a una columna que contiene valores nulos, podemos conseguir, a través de la propiedad NullValue del estilo, que estos se muestren con un texto informativo. Sin embargo, en la consulta SQL que estamos usando para estos ejemplos, el campo SpanishProductName contiene registros con valores que no son nulos, sino vacíos, por lo cual no tendría efecto el uso de la propiedad NullValue para el estilo de dicha columna. Nos encontramos pues ante un candidato ideal para tratar mediante el evento CellFormatting. Todo lo que tenemos que hacer es añadir el código del listado 6 al manipulador de este evento, para que muestre un literal en el caso de que la celda de esta columna no vaya a mostrar valores, y de paso cambiamos su color de fondo. Si queremos otorgar mayor vistosidad a la información mostrada, podría interesarnos visualizar diferentes imágenes en private void Form1_Load(object sender, EventArgs e) { //.... // columna para mostrar imágenes DataGridViewImageColumn colDinero = new DataGridViewImageColumn(); colDinero.Name = “colDinero”; this.dgvGrid.Columns.Insert(3, colDinero); } private void dgvGrid_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e) { //.... //.... // si la celda a dibujar corresponde a la columna de imágenes // añadir a la celda la imagen en función del valor del campo ListPrice if (this.dgvGrid.Columns[e.ColumnIndex].Name == “colDinero”) { string sImagen; if ((decimal)this.dgvGrid.Rows[e.RowIndex].Cells[“ListPrice”].Value>1000) { sImagen = @”\euro.jpg”; } else { sImagen = @”\dolar.jpg”; } e.Value = new Bitmap(new Bitmap(Application.StartupPath + sImagen), this.dgvGrid.Rows[e.RowIndex].Cells[“colDinero”].Size); } } Listado 7 <<dotNetManía // si la celda a dibujar es // SpanishProductName... if (this.dgvGrid.Columns[ e.ColumnIndex].Name == “SpanishProductName”) { if (e.Value.ToString() == string.Empty) { e.CellStyle.BackColor = Color.Aqua; e.Value = “Producto sin descripción”; } } Asignar una imagen a la celda las celdas de una columna, en función de una condición dada por el valor de uno de los campos, por ejemplo ListPrice. Para manipular imágenes en un DataGridView disponemos de la clase DataGridViewImageColumn, que como su nombre indica, permite crear columnas cuyo contenido sea dicho tipo de dato. Por lo que en el evento Load procederemos a instanciar un objeto de esta clase, insertándolo en la colección de columnas de la cuadrícula, al lado de ListPrice. Posteriormente, en el evento CellFormatting, cuando detectemos que la celda de esta nueva columna va a ser dibujada, le asignaremos una imagen creando un objeto Bitmap a partir de un archivo gráfico, como vemos en el listado 7. Podemos ver la apariencia del control con esta nueva columna en la figura 4. 21 << dnm.plataforma.net Figura 4. DataGridView con columna de imagen <<dotNetManía Selección de celdas por columna 22 A poco que hayamos utilizado el control DataGridView, nos habremos percatado de que, por defecto, permite realizar una selección múltiple de todas las celdas de una fila con un solo clic en la cabecera de dicha fila. Sin embargo, en algunas situaciones sería deseable disponer de la capacidad de seleccionar todas las celdas de una columna al hacer clic en la cabecera de las mismas. El comportamiento de este control, en lo que a selección múltiple de celdas se refiere, se encuentra gobernado por su propiedad SelectionMode, que corresponde al tipo enumerado DataGridViewSelectionMode, cuyos valores determinarán el modo en que las celdas son seleccionadas al hacer clic el usuario sobre ellas. La tabla 1 muestra una descripción de los miembros de esta enumeración. Como reza el título de este apartado, si queremos seleccionar todas las celdas de una columna al hacer clic en su cabecera, asignaremos el valor DataGridViewSelectionMode.ColumnHeaderSelect a la propiedad SelectionMode del control. El efecto más inmediato que probablemente obtendremos será un tremendo error al intentar ejecutar la aplicación. Esto es debido al hecho de que los objetos columna del control tienen por defecto en su propiedad SortMode el valor Automatic, lo cual es incompatible con la capacidad de selección por columna. Propiedad Descripción CellSelect Selección por celdas independientes. Permite seleccionar una o varias. ColumnHeaderSelect Se seleccionará la columna al completo al hacer clic en su cabecera. También podemos seguir seleccionando celdas independientes. FullColumnSelect Se seleccionará la columna al completo al hacer clic en su cabecera, o bien en cualquier celda de esa columna. FullRowSelect Se seleccionará la fila al completo al hacer clic en la cabecera de fila, o bien en cualquier celda de esa fila. RowHeaderSelect Se seleccionará la fila al completo al hacer clic en su cabecera de fila. También podemos seguir seleccionando celdas independientes. Tabla 1.Valores de la enumeración DataGridViewSelectionMode. // cambiar el modo de ordenación de cada // columna a manual foreach (DataGridViewColumn oColumna in this.dgvGrid.Columns) { oColumna.SortMode=DataGridViewColumnSortMode.Programmatic; } // ahora ya se puede establecer la selección por columna this.dgvGrid.SelectionMode = DataGridViewSelectionMode.ColumnHeaderSelect; Listado 8 Figura 5. Selección por columna en el control << dnm.plataforma.net Para solucionar este entuerto, bastará con recorrer la colección de columnas de la cuadrícula y cambiar el valor de esta propiedad a Programmatic, lo cual implicará que sea el código de la aplicación quien se encargue de las operaciones de ordenación. Ver el listado 8. De este modo, como vemos en la figura 5, ya será posible la selección por columna. Hemos conseguido pues, que al hacer clic en la celda de cabecera de una columna, esta quede seleccionada. Pero hemos ganado una funcionalidad y perdido otra: la ordenación automática de columnas, lo que quiere decir que nos veremos obligados a implementar este aspecto por código. El punto más idóneo para realizar estas operaciones será el evento ColumnHeaderMouseClick, producido al hacer clic sobre la cabecera de una columna; donde en primer lugar obtendremos, gracias al parámetro DataGridViewMouseCellEventArgs y su propiedad ColumnIndex, la columna seleccionada para ordenar. // establecer un orden por defecto ListSortDirection xDireccionOrden = ListSortDirection.Ascending; // // // if { calcular qué orden vamos a aplicar: —————————————————si no hay columna ordenada actualmente se ordenará en ascendente (colOrdenActual == null) xDireccionOrden = ListSortDirection.Ascending; } else { // si hay columna ordenada actualmente: // —————————————————— // si se ha pulsado la misma columna que ya estaba ordenada // se invierte el orden actual if (colPulsada == colOrdenActual && this.dgvGrid.SortOrder == SortOrder.Ascending) { xDireccionOrden = ListSortDirection.Descending; } else { // en cualquier otro caso se establece orden ascendente xDireccionOrden = ListSortDirection.Ascending; } } // ordenar la columna this.dgvGrid.Sort(colPulsada, xDireccionOrden); // si había una columna previamente seleccionada // y es distinta de la que acabamos de ordenar if (colOrdenActual != null && colOrdenActual.Name != colPulsada.Name) { try { // quitar el estado de selección // de la columna anterior colOrdenActual.Selected = false; } catch { } } } Listado 9 <<dotNetManía Mediante el valor Programmatic indicamos que será el código de la aplicación quien se encargue de las operaciones de ordenación private void dgvGrid_ColumnHeaderMouseClick(object sender, DataGridViewCellMouseEventArgs e) { // obtener las columnas pulsada y actualmente ordenada DataGridViewColumn colPulsada = this.dgvGrid.Columns[e.ColumnIndex]; DataGridViewColumn colOrdenActual = this.dgvGrid.SortedColumn; 23 << dnm.plataforma.net <<dotNetManía Figura 6. Seleccionar una columna y ordenarla 24 La columna que estaba previamente ordenada, si es que había alguna, se encontrará en la propiedad SortedColumn del control. Como siguiente paso, determinaremos la dirección en la que se van a ordenar los datos, pero sin ordenarlos realmente aún. Lo que haremos será guardar en una variable de tipo ListSortDirection esta dirección para el orden. A continuación ordenaremos la columna correspondiente a la cabecera pulsada utilizando el método DataGridView.Sort, al que pasaremos como parámetro la columna a ordenar y su dirección. Por último, en el caso de que existiese una columna anteriormente ordenada, y por lo tanto seleccionada, quitaremos dicho estado de selección asignando false a su propiedad Selected. Curiosamente, esta operación provocará una excepción, pero controlándola mediante un bloque try…catch, el estado de selección de la columna en cuestión finalmente quedará quitado. Podemos ver estas operaciones en el listado 9, y en la figura 6 volvemos a observar el control en ejecución, permitiéndonos ya tanto seleccionar la columna como ordenarla. Posicionamiento y bloqueo de columnas Las clases DataGridView y DataGridViewColumn ofrecen al desarrollador un conjunto de propiedades destinadas a la manipulación de las posiciones de las columnas dentro del control. Comenzaremos por la propiedad DataGridView.AllowUserToOrderColumns, de tipo lógico. Asignándole el valor true , conseguiremos que el usuario al hacer clic y arrastrar en la cabecera de una columna cambie la posición de la misma con respecto a las demás. Cuando añadimos un nuevo control de este tipo al formulario, por defecto no podremos cambiar las posiciones de sus columnas, ya que el valor de esta propiedad es false. Puede, sin embargo, que en lugar de dejar al usuario el movimiento de las posiciones de columnas, queramos establecerlo nosotros por código. Para ello contamos con la propiedad DataGridViewColumn.DisplayIndex, que permite asignar a la columna la posición de visualización, independientemente de la posición real que dicha columna tenga en la colección de columnas del control. Por otro lado, también disponemos de la capacidad de bloquear o congelar una columna mediante su propiedad Frozen, consiguiendo, al asignarle el valor true, que dicha columna y las que se encuentren a su izquierda se mantengan fijas cuando el usuario utilice la barra de desplazamiento horizontal del DataGridView. Como ejemplo ilustrativo de estas características, vamos a crear un formulario que contenga un TextBox por cada una de las columnas de la rejilla, y un botón que al ser pulsado asignará nuevas posiciones a las columnas a través de su propiedad DisplayIndex, Figura 7. Cambio de posición visual y bloqueo de columnas. << dnm.plataforma.net // cambiar las posiciones de visualización de las columnas // utilizando su propiedad DisplayIndex private void btnPosicionar_Click(object sender, EventArgs e) { // si hay una columna bloqueada, no se puede // aplicar el cambio de posición con DisplayIndex if (this.dgvGrid.Columns[1].Frozen) { MessageBox.Show(“Desbloquear columnas”); } else { // recorrer los controles, obtener el valor de los textbox // y asignarlo como nueva posición de columna en el grid foreach (Control oControl in this.Controls) { if (oControl.GetType() == typeof(TextBox)) { TextBox txtTexto = oControl as TextBox; this.dgvGrid.Columns[txtTexto.Name.Substring(3)].DisplayIndex = int.Parse(txtTexto.Text) - 1; } } } } // bloquear-desbloquear las dos primeras columnas, // establecer un grosor para el borde de una columna // y color para las líneas del control private void chkBloquear_CheckedChanged(object sender, EventArgs e) { if (this.chkBloquear.Checked) { this.dgvGrid.Columns[1].Frozen = true; this.dgvGrid.Columns[1].DividerWidth = 10; this.dgvGrid.GridColor = Color.DarkOrchid; } else { this.dgvGrid.Columns[0].Frozen = false; this.dgvGrid.Columns[1].Frozen = false; this.dgvGrid.Columns[1].DividerWidth = 0; this.dgvGrid.GridColor = SystemColors.ControlDark; } } en base a los números que hayamos introducido en los mencionados controles de edición. También añadiremos un control CheckBox al formulario, que al ser marcado, bloqueará las dos primeras columnas del DataGridView. Para hacer este efecto más palpable visualmente, ampliaremos la anchura de la segunda columna utilizando su propiedad DividerWidth. Finalmente, como mero efecto estético, cambiaremos el color de las líneas de la cuadrícula usando la propiedad GridColor del control. Todo ello lo vemos en el listado 10, donde debemos aclarar que los nombres que hemos dado a los controles TextBox del formulario están compuestos por el prefijo “ txt ” más el nombre del campo; de ahí que al recorrer la colección de controles, cada vez que encontremos un TextBox, extraigamos el nombre del campo aplicando el método Substring a la propiedad Name del TextBox. Resulta interesante, una vez que hemos bloqueado las columnas, hacer un desplazamiento de las mismas, observando cómo la primera de ellas que no está bloqueada se va ocultando al ser desplazada a la izquierda; efecto que vemos en la figura 7. Listado 10 En el presente artículo hemos abordado diversas características del control DataGridView relacionadas con su capacidad de presentación de datos. Estas características nos permiten realizar operaciones tales como la aplicación de formato sobre los valores de las celdas, selección de columnas y cambio en el orden predeterminado en que las mismas se muestran al usuario, tanto por código como debido a la acción del propio usuario sobre los elementos del control en tiempo de ejecución. Confiamos en que todas estas posibilidades para obtener un mayor beneficio visual en nuestra cuadrícula de datos resulten interesantes a los lectores que necesiten implementar este tipo de mejoras en sus aplicaciones. <<dotNetManía Conclusiones 25 plataforma.net Javier Roldán Tablet PC SDK (I) Reconocimiento de escritura manual Los dispositivos conocidos como Tablet PC van adquiriendo día a día un mayor peso específico en el mercado de los dispositivos móviles. Estos híbridos, a medio camino entre los clásicos portátiles y las PDA, deben su éxito en gran medida a su capacidad para interactuar con el usuario mediante el uso de la escritura manual, permitiendo una interacción hombre-máquina mucho más intuitiva y natural. Fco. Javier Roldán Huecas es ingeniero superior informático. Con una experiencia laboral de ocho años como profesional del sector de las TI, en la actualidad trabaja como ingeniero de sistemas y responsable de proyectos en una importante empresa industrial de ámbito nacional. Puede contactar con él en: [email protected] Desde que el ser humano diseñó y construyó su primera herramienta, se vio en la inevitable necesidad de tomar en consideración la mejor manera de interactuar con ella, y por tanto de sacarle el máximo provecho. De este modo, observó que uniendo una empuñadura de madera a la afilada piedra usada en sus tareas diarias no sólo mejoraba la efectividad de la herramienta, sino que además podía utilizarla en nuevos campos de aplicación. Poco a poco la complejidad de las herramientas aumentó, y con ella aparecieron de nuevas y mucho más elaboradas formas de interacción. Así los medios de transporte de tracción animal requerían de un sencillo sistema para la puesta en marcha, parada y control de la dirección del vehículo basada en la domesticación del animal. Sin embargo, los sofisticados medios de transporte actuales, como por ejemplo los aviones, requieren de complejos sistemas de información y control. Y es a partir del excesivo aumento de la complejidad de las herramientas utilizadas cuando la necesidad de interactuar con las máquinas de manera intuitiva y natural se convierte en una meta para la comunidad científica. De este modo, la aparición de las computadoras como herramientas de una gran complejidad, pero a su vez de una gran capacidad de cálculo, alimentó desde sus comienzos las expectativas de una fluida interacción hombre-máquina, basada principalmente en la comunicación oral y escrita. Es precisamente esta última, la comunicación escrita, la que trataremos de abordar en el presente artículo. En concreto, veremos la manera de integrar en nuestras aplicaciones .NET la funcionalidad de reconocimiento de escritura. El entorno de desarrollo Para empezar a programar aplicaciones con capacidades de reconocimiento de escritura manual, deberemos tener instalados en nuestra máquina de desarrollo los siguientes componentes: 1. Microsoft Windows XP Tablet PC Edition Software Development Kit 1.7. 2. Microsoft Windows XP Tablet PC Edition 2005 Recognizer Pack. Microsoft Windows XP Tablet PC Edition Software Development Kit 1.7 Microsoft Windows XP Tablet PC Edition Software Development Kit 1.7 es la última versión disponible del kit de desarrollo para la plataforma Tablet PC. Este kit de desarrollo se compone de bibliotecas administradas y de automatización que permiten el desarrollo de aplicaciones con reconocimiento de escritura manual y de voz, así como de desplazamiento mediante el uso del lápiz óptico. << dnm.plataforma.net Recursos Web para la preparación del entorno de desarrollo Descarga de los entornos de desarrollo Visual Studio 2005 Express Edition: http://msdn.microsoft.com/vstudio/express/downloads Preguntas frecuentes relacionadas con Visual Studio 2005 Express Edition: http://msdn.microsoft.com/vstudio/express/support/faq un mensaje de error aparecerá casi al instante indicando que no existen reconocedores de texto manuscrito instalados y que necesitamos al menos uno de ellos para ejecutar el ejemplo (figura 1). Precisamente esto es lo que realizaremos a continuación. Microsoft Windows XP Tablet PC Edition Software Development Kit 1.7: Microsoft Windows XP Tablet PC Edition 2005 Recognizer Pack: http://www.microsoft.com/downloads/details.aspx?FamilyID=080184dd-5e924464-b907-10762e9f918b&DisplayLang=en Para instalar el kit de desarrollo, procederemos en primer lugar a descargarlo desde la página Web habilitada al efecto (ver cuadro de recursos Web). Una vez descargado, procederemos a su instalación en nuestro equipo, para lo que ejecutamos el fichero Setup.exe . Siempre es interesante seleccionar la instalación personalizada, ya que pinchando sobre cada una de las opciones disponibles podremos ver una pequeña descripción de cada una de ellas. Antes de continuar, y con el objeto de entender un poco mejor el siguiente paso a realizar, accederemos a “Inicio” | “Programas” | “Microsoft Tablet PC Plattform SDK” y ejecutaremos “Samples and Source Code”, tras lo cual lanzaremos el ejemplo “Ink Recognition” de la sección “Recognition”. Una sencilla aplicación de ejemplo nos invita a escribir cualquier texto sobre un panel titulado “Ink Here”, para luego pulsar sobre el botón “Recognize Ink”. Sin embargo, si ejecutamos tal acción, Figura 1. Error al no haber reconocedores instalados Microsoft Windows XP Tablet PC Edition 2005 Recognizer Pack Microsoft Windows XP Tablet PC Edition 2005 Recognizer Pack es lo que permitirá a nuestras aplicaciones reconocer el texto escrito de manera manual mediante el uso de la tinta digital y convertirlo a texto estándar (entendiendo como texto estándar aquel que el ordenador almacena como una cadena de caracteres). Actualmente el reconocedor de escritura manual soporta los siguientes idiomas: chino (tradicional y simplificado), inglés (US y UK), francés, alemán, italiano, japonés, coreano y por supuesto español. Para instalarlo, procederemos una vez más a descargarlo desde el sitio Web de Microsoft (ver cuadro de recursos Web). En esta ocasión, la instalación personalizada nos dará la opción de elegir que idiomas queremos instalar. Para seguir los ejemplos de este artículo con instalar el idioma español es suficiente. Si ahora ejecutamos de nuevo el ejemplo “Ink Recognition”, veremos cómo al pulsar sobre el botón “Recognize Ink” la aplicación ya no solo no nos mostrará el error, sino que entenderá a la perfección el texto introducido (figura 2). Aquí podemos probar a escribir diferentes tipos de texto manuscrito y comprobar la eficacia del reconocedor incluso con textos cuyas letras se enlazan unas con otras en un solo trazo. Sin embargo, este ejemplo no es de los mejores en cuanto a eficacia de reconocimiento de escritura, que se puede aumentar exponencialmente si de antemano sabemos el tipo de información que va a ser escrita, mediante el uso de diferentes técnicas que serán abordadas en detalle en el próximo artículo. <<dotNetManía http://www.microsoft.com/downloads/details.aspx?FamilyId=B46D4B83-A82140BC-AA85-C9EE3D6E9699&displaylang=en 27 << dnm.plataforma.net <<dotNetManía Antes de poder utilizar los controles InkEdit e InkPicture, debemos hacerlos disponibles en el entorno de desarrollo. Para ello, es conveniente crear inicialmente una nueva categoría (que denominaremos “Microsoft Ink”) en el Cuadro de herramientas, y en ella colocaremos los componentes InkEdit e InkPicture (figura 3). Figura 2. Reconocedor funcionando El control InkEdit es el más sencillo de todos los controles existentes dentro del Tablet PC SDK. El control InkEdit Tan sencillo que permite capturar trazos de tinta digital y hacer su reconocimiento de manera automática sin que Ha llegado el momento de empezar a tengamos que escribir ni una sola línea desarrollar nuestras propias aplicaciones de código. Como era de esperar, es con de reconocimiento de escritura manual. diferencia el menos flexible de todos Para ello, abrimos Microsoft Visual C# ellos. Pero veamos cómo se utiliza. 2005 Express Edition y creamos un nueDesde el Cuadro de herramientas vo proyecto mediante la opción de menú pincharemos y arrastraremos el control “File” | “New Project”. Al aparecer el asistente de creación de nuevo proyecto, InkEdit hasta situarlo sobre el formuseleccionamos “Windows Application” y lario de la aplicación, como haríamos le ponemos un nombre al proyecto (por con cualquier control que quisiéramos ejemplo, InkEditTest). Por defecto, Visual situar sobre nuestro formulario. ObserStudio nos mostrará el formulario Form1 vamos que su aspecto es muy similar al en su vista de diseño. del clásico control RichTextBox, y de 28 Figura 3. Seleccionamos los controles InkEdit e InkPicture para añadirlos al Cuadro de herramientas hecho comparte muchas de sus propiedades. No podía ser de otro modo, ya que la clase InkEdit deriva directamente de este último. Pero si observamos las propiedades del control, veremos que dispone de una categoría específica, denominada Ink; esta categoría es la que precisamente nos va a permitir determinar la manera en que va a comportarse el control InkEdit en cuanto a lo que tinta digital y reconocimiento de escritura manual se refiere. Las propiedades son las siguientes: • Cursor. Determina la forma del cursor al pasar sobre el control. • Factoid. Esta propiedad acepta una cadena de caracteres que determina el tipo de información que va a recoger el control y que aumenta considerablemente el ratio de aciertos de reconocimiento. • InkInsertMode. Existen dos modos: • InsertAsText. En este modo el control InkEdit reconoce los trazos escritos y los inserta como texto. Es el modo por defecto. • InsertAsInk. Utilizado en los casos en los que deseemos que los trazos escritos no sean reconocidos y sustituidos por su equivalente en texto (por ejemplo, si queremos capturar una firma). Suele usarse en combinación con el método SaveFile. Este modo sólo funciona en las versiones Tablet PC del sistema operativo Windows XP. • InkMode. Ofrece tres valores: • Disabled. No permite la introducción de tinta digital. • Ink. Permite la introducción de tinta digital, únicamente para ser reconocida como texto. • InkAndGesture. Permite la introducción de tinta digital, tanto para ser reconocida como texto, como para ser reconocida como gestos. Los gestos son unos trazos especiales que permiten entre otras cosas borrar un carácter (backspace), borrar todo el texto (scratch), introducir un salto de línea (intro), etc. << dnm.plataforma.net Inicialmente pondremos la propiedad UseMouseForInput a true y dejaremos el resto de las propiedades con su valor por defecto. Ejecutamos la aplicación y probamos a escribir algún texto mediante el uso del ratón o tableta digitalizadora, si disponemos de ella (figura 4). Al cabo de dos segundos, el texto será reconocido e insertado como texto en el control InkEdit (figura 5). Y todo ello sin una sólo línea de código. Puede probar a cambiar las diferentes propiedades del control para estudiar cómo se comporta. Figura 4. Probando el control InkEdit Figura 5. Reconocimiento automático El control InkPicture A diferencia del control InkEdit, el control InkPicture deriva de la clase Picture y está pensado principalmente para mostrar imágenes sobre las que poder escribir con tinta digital, pero en las que no necesitemos reconocimiento de escritura manual. Sus aplicaciones prácticas pasarían desde la captura de firmas de clientes hasta la señalización por parte de un perito de las zonas dañadas tras un siniestro directamente sobre la fotografía del vehículo. El control InkPicture está pensado principalmente para mostrar imágenes sobre las que poder escribir con tinta digital ¿Significa esto que no es posible reconocer los trazos introducidos sobre el control InkPicture y convertirlos en texto? No, simplemente significa que no lo hará de manera automática, aunque ello es perfectamente posible mediante programación. A continuación veremos cómo, pero antes exploremos las propiedades que diferencian al control InkPicture de su clase padre: • AutoRedraw. Indica si la tinta digital trazada sobre el control será repintada cuando el control sea invalidado. Un control se invalida cuando deja de ser visible; por ejemplo, al posicionar temporalmente una ventana sobre él. Su valor por defecto es true. • CollectionMode. Equivalente a la propiedad InkMode del control InkEdit, determina si se detectarán los trazos como tinta, gestos o ambos. • DynamicRendering. Valor lógico que indica si los trazos se muestran o no conforme son dibujados. Si ponemos esta propiedad a false, ello equivaldría a dibujar con “tinta invisible”. • EditingMode. Especifica si el puntero creará trazos, los borrará o seleccionará para su escalado y posicionamiento. Esta propiedad es de gran utilidad si en vez de ser establecida en tiempo de diseño se hace mediante programación. • EraserMode. Esta propiedad determina si al borrar se eliminarán trazos enteros o solamente la sección del trazo por donde pase el puntero. • EraserWidth. Grosor del puntero de borrado. Esta propiedad es relevante únicamente en el caso de que el valor de EraserMode sea PointErase. • InkEnabled. Indica si se encuentra activa la recolección de trazos. En caso de estar desactivada, no podremos pintar sobre el control. <<dotNetManía • RecoTimeout. Es el tiempo que nuestro control tarda desde que ha recibido el último trazo de tinta digital hasta que intenta realizar el reconocimiento. • UseMouseForInput. Si queremos usar nuestro ratón como si de un puntero de Tablet PC se tratase y por tanto probar nuestra aplicación, deberemos poner esta propiedad a true. 29 << dnm.plataforma.net using using using using using using using using System; System.Collections.Generic; System.ComponentModel; System.Data; System.Drawing; System.Text; System.Windows.Forms; Microsoft.Ink; namespace InkPictureTest { public partial class FormInkPicture : Form { public FormInkPicture() { InitializeComponent(); } Figura 6. Potencia del modo Select private void buttonEscribir_Click(object sender, EventArgs e) { this.inkPictureTest.EditingMode = InkOverlayEditingMode.Ink; } private void buttonBorrarTrazo_Click(object sender, EventArgs e) { this.inkPictureTest.EraserMode = InkOverlayEraserMode.StrokeErase; this.inkPictureTest.EditingMode = InkOverlayEditingMode.Delete; } private void buttonBorrarPunto_Click(object sender, EventArgs e) { this.inkPictureTest.EraserMode = InkOverlayEraserMode.PointErase; this.inkPictureTest.EditingMode = InkOverlayEditingMode.Delete; } private void buttonSeleccionar_Click(object sender, EventArgs e) { this.inkPictureTest.EditingMode = InkOverlayEditingMode.Select; } } } <<dotNetManía Listado 1. Entendiendo mejor algunas de las propiedades del control InkPicture 30 • MarginX. Esta propiedad establece un margen de seguridad en el eje X sobre el que se podrá pintar aún a pesar de estar fuera de los límites del control InkPicture. • MarginY. Equivalente a la propiedad MarginX pero en el eje Y. • SupportHighContrastInk. Especifica si la tinta será representada en alto contraste cuando el sistema se encuentre configurado en modo de contraste alto, utilizado por personas con deficiencias visuales. • SupportHighContrastSelectionUI. Especifica si el cuadro de manipulación de trazos (EditingMode = Select) será representado en alto contraste cuando el sistema se encuentre configurado ese modo. Para entender mejor algunas de estas propiedades, veremos un sencillo ejemplo en el que crearemos un simple editor de trazos utilizando el control InkPicture. Para ello, arrastraremos dicho control sobre un formulario en blanco y crearemos cuatro botones: uno para crear trazos, otro para seleccionarlos y modificar su tamaño y posición, otro para borrar trazos enteros y otro para borrar segmentos de trazo. El código correspondiente a este ejemplo puede verse en el listado 1. Al ejecutar la aplicación podemos observar claramente las diferencias existentes entre los diferentes valores de EditingMode (figura 6) y EraserMode. Reconociendo texto manuscrito con el control InkPicture Hasta aquí hemos visto brevemente la sencillez y potencia del control InkPicture para crear trazos, seleccionarlos, modificarlos y borrarlos de diferentes modos. Sin embargo, no hemos visto la manera de reconocer los trazos introducidos como texto manuscrito, que es al fin y al cabo el objetivo primordial de este artículo. << dnm.plataforma.net Como comentaba al principio de la presente sección, el control InkPicture no proporciona ningún método para el reconocimiento automático de la escritura manual, sin embargo con unas pocas líneas de código es posible efectuar esta tarea de manera sencilla. Para ello, añadiremos al formulario del ejemplo anterior un botón, cuyo evento Click contendrá toda la lógica del reconocimiento y un cuadro de texto donde mostraremos el resultado (podemos ver el resultado final en la figura 7). private void buttonReconocimiento_Click(object sender, EventArgs e) { Recognizers reconocedores = new Recognizers(); if (reconocedores.Count != 0) { Recognizer reconocedor = reconocedores.GetDefaultRecognizer(); RecognizerContext contextoReconocedor = reconocedor.CreateRecognizerContext(); RecognitionResult resultadoReconocedor; RecognitionStatus estatusReconocimiento; contextoReconocedor.Strokes = inkPictureTest.Ink.Strokes; if (contextoReconocedor.Strokes.Count > 0) { resultadoReconocedor = contextoReconocedor.Recognize( out estatusReconocimiento); if (estatusReconocimiento==RecognitionStatus.NoError && resultadoReconocedor != null) { String resultado = resultadoReconocedor.TopString; if (resultado != null && resultado.Length > 0) { this.textBoxTextoReconocido.Text = resultado; } } Figura 7. Reconocimiento en InkPicture A continuación analizaremos el código a añadir asociado al nuevo botón de reconocimiento, y que puede verse completo en el listado 2. } Listado 2. Código de reconocimiento de escritura • En primer lugar, creamos una nueva instancia de la clase Recognizers (reconocedores) que no es ni más ni menos que el conjunto de reconocedores de texto instalados. • Una vez creada dicha instancia, comprobamos si existe o no algún reconocedor instalado en el sistema; si no es así mostramos un mensaje y finalizamos la ejecución. <<dotNetManía El control InkPicture no reconoce automáticamente los trazos introducidos, pero ello es perfectamente posible mediante programación } } else { MessageBox.Show(“No existen reconocedores instalados”); } 31 <<dotNetManía << dnm.plataforma.net 32 • A continuación, obtenemos el reconocedor por defecto mediante el uso del método GetDefaultRecognizer de la clase Recognizers, el cual puede aceptar un parámetro de tipo int que indica el código identificador de idioma (LCID), o lo que es lo mismo, el idioma para el que estamos obteniendo el reconocedor por defecto. En caso de que no se proporcione dicho parámetro, el idioma se determina en función del Idioma predeterminado del dispositivo de entrada configurado a través del Panel de control, Configuración regional y de idiomas (pestaña “Idiomas”, botón “Detalles”). • Asimismo, creamos un contexto de reconocimiento (RecognizerContext), el cual permitirá en última instancia el reconocimiento de escritura manual que esperamos, y definimos un resultado de reconocimiento (RecognitionResult) que almacenará el resultado del reconocimiento y un estatus de reconocimiento (RecognitionStatus) que guardará el estado generado tras el reconocimiento. • A través de la propiedad Ink.Strokes, obtenemos los trazos realizados en el control InkPicture y los asignamos a los trazos del contexto reconocedor. Si existe por lo menos algún trazo, efectuamos el reconocimiento mediante una llamada al método Recognize del contexto y lo asignamos al resultado de reconocimiento. El estatus de reconocimiento se obtiene como parámetro por referencia. • Por último, de todas las posibles alternativas de reconocimiento almacenamos en una cadena de caracteres la que el reconocedor ha clasificado como las más probable (TopString), y la mostramos en el cuadro de texto habilitado al efecto. En la siguiente entrega En este artículo hemos preparado el entorno de desarrollo necesario para la programación de aplicaciones capaces de aceptar tinta digital para más tarde reconocerla como escritura manuscrita. Así mismo, hemos analizado y utilizado los principales elementos que para este fin proporciona Microsoft Windows XP Tablet PC Edition Software Development Kit. Sin embargo, han quedado sin tratar muchos temas interesantes relacionados con el reconocimiento de la escritura manual mediante el uso de Microsoft Windows XP Tablet PC Edition SDK, como por ejemplo la posibilidad de añadir características de tinta digital a cualquier control que decidamos mediante el uso de la clase InkOverlay, la mejora del reconocimiento mediante el uso de Factoids (cadenas predefinidas que proporcionan al reconocedor información sobre el tipo de texto a reconocer) y WordLists (listas predefinidas de palabras), o por ejemplo el uso de Gestures (gestos especiales no reconocidos como palabras y que permiten la ejecución de acciones a medida). Trataremos todos estos temas en detalle en la próxima entrega; espero no nos falléis y veros de nuevo el mes que viene. plataforma.net Daniel Seara Herramientas genéricas para los componentes En este artículo describimos cómo pueden implementarse funcionalidades genéricas que sean útiles a cualquier tipo de aplicación que debamos programar, considerando elementos de diseño como su pertenencia a un componente específico o como simples rutinas. Dentro de estas definiciones, aclararemos cómo es posible definir configuraciones específicas para cada componente que diseñemos, así como una estructura genérica que permita llevar una bitácora histórica de los errores dentro de las aplicaciones. Quienes me conocen saben que soy casi fanático de encapsular, montar componentes, generalizar (sin exagerar). Para que esto funcione adecuadamente, hay que diseñar antes que codificar, y hacerse de algunas “reglas guía” que en muchos lugares se mencionan como patrones (cuidado, si tienen ganas, algunos comentarios sobre patrones están en mi blog en http://blogs.solidq.com/ES/dseara/… digo, para que se me entienda como pienso las cosas). Ahora bien, hay algunas cosas que casi cualquier componente podría llegar a necesitar. En algunos casos, podrían ser otro componente aparte… en otros no. Comencemos pues. Definiendo la configuración de nuestro componente Daniel Seara es mentor de Solid Quality Mentors y director del área de desarrollo .NET. Es MVP desde 2003 y ponente habitual de INETA y Microsoft. Uno de los elementos importantes a tener en cuenta respecto de los componentes es la definición adecuada de sus configuraciones (en el número 41 hay un excelente artículo de mi amigo Guille acerca del tema de las configuraciones). El mejor modo, y más ordenado, de hacer esto para componentes, es crear una clase de configuración que pueda persistir la misma en el archivo correspondiente de la aplicación o el sitio Web. Además, dada la estructura jerárquica de los elementos de configuración, se podrá definir la con- figuración, por ejemplo, directamente en machine.config, y de esa forma utilizar la misma para múltiples aplicaciones. Definimos entonces una clase que herede de System.Configuration.ConfigurationSection. [ NOTA Para poder hacer esto, es necesario agregar una referencia al ensamblado System.Configuration.dll. ] Imports System.Configuration Public Class Configuration Inherits System.Configuration.ConfigurationSection Una vez heredada, debemos crear nuestro propio conjunto de propiedades. Las clases de configuración exponen los distintos valores a través de una colección de tipo específico: ConfigurationPropertyCollection. Para poder definir las nuestras propias, debemos declarar un objeto de dicho tipo en nuestra clase y expo- << dnm.plataforma.net Nótese que es posible, mediante atributos, aplicar validadores a estas propiedades. Private Shared mProperties As ConfigurationPropertyCollection Protected Overrides ReadOnly Property Properties() _ As ConfigurationPropertyCollection Get Return mProperties End Get End Property A continuación, procederemos a definir campos compartidos para cada una de las propiedades. Para que sean persistibles en el archivo de configuración, estos campos deben declararse como ConfigurationProperty. Además, la propia declaración define el tipo de dato, el valor predeterminado y atributos que determinan, por ejemplo, si la propiedad es obligatoria y demás. Por ejemplo: Otro ejemplo de validador sería el de expresiones regulares, como vemos en el siguiente ejemplo: Private Shared mEmail As New _ ConfigurationProperty( _ “Email”, GetType(String), _ “[email protected]”, _ ConfigurationPropertyOptions.None) <RegexStringValidator(_ “^[a-zA-Z\.\-_]+@([a-zA-Z\.\-”+_ “_]+\.)+[a-zA-Z]{2,4}$”)>_ Private Shared mFileNameandPath As _ New ConfigurationProperty( _ “fileName”, GetType(String), _ “c:\SolidLog.log”, _ ConfigurationPropertyOptions.None) Y luego lo exponemos como propiedad: <StringValidator( _ InvalidCharacters:=_ ” ~!@#$%^&*()[]{}/;’””|”, _ MinLength:=1,_ MaxLength:=600)> _ Public Property _ FileNameAndPath() As String Get Return CStr(Me(“fileName”)) End Get Set(ByVal value As String) mIsModified = True Me(“fileName”) = value End Set End Property Public Property Email() As String Get Return CStr(Me(“Email”)) End Get Set(ByVal value As String) Me(“Email”) = value mIsModified = True End Set End Property La idea entonces es tener una clase de configuración para cada componente que la necesite, de manera que queden secciones en el archivo de configuración según necesitemos. Teniendo todas las propiedades definidas, debemos, en el constructor de la clase, agregar las mismas a la propertyCollection. De esa forma, si existen propiedades configuradas en el archivo de configuración de la aplicación, o en machine.config, se cargarán automáticamente con sus valores. En caso contrario, retornarán los valores definidos como predeterminados. Sub New() MyBase.New() mProperties = New _ ConfigurationPropertyCollection mProperties.Add(mFileNameandPath) mProperties.Add(mDestination) mProperties.Add(mEmail) mProperties.Add(mEventLogName) End Sub Definiendo los valores en el archivo de configuración Para que nuestro componente “encuentre” los valores de configuración, se debe cambiar el archivo config según lo siguiente: Primero, se debe agregar la definición del sectionGroup y el sectionName que utilizamos: <configuration> <configSections> <sectionGroup name=”Solid” type=”System.Configuration. ConfigurationSectionGroup, System.Configuration, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a” > <section name=”ExceptionHandler” type=”Solid.Tools.ExceptionHandler. Configuration, Solid.Tools.ExceptionHandler, Version=3.0.0.0, Culture=neutral, PublicKeyToken=b856d8a5d9f2c2be”/> </sectionGroup> </configSections> Una vez hecho esto, agregar el grupo y la sección correspondientes: <Solid> <ExceptionHandler fileName=”c:\Solid.log” Destination=”LogFile” Email=”[email protected]” EventLogName=”Solid Log” /> </Solid> <<dotNetManía nerlo sobrescribiendo un método de la clase Configuration, como se ve en el código siguiente: 35 << dnm.plataforma.net ¿Cómo asegurarnos de que estamos haciendo las cosas bien? Para tener siempre la definición correcta en el archivo de configuración, basta con crear una instancia de nuestra clase de configuración, asignarle valores a todas sus propiedades, y guardarla en la configuración de una aplicación Windows, la cual puede persistirse en cualquier archivo de texto: es casi imposible definir desde el inicio todos los errores que podrían producirse, nada mejor que un objeto de error genérico, del cual hereden todos y cada uno de nuestros errores personalizados. De esa forma, cualquier otro componente definirá sus propias clases de excepción, heredando de ésta, y así se conseguirá individualizar excepciones específicas, y al mismo tiempo, mantener una bitácora centralizada de los errores. With System.Configuration.ConfigurationManager.OpenExeConfiguration( _ Configuration.ConfigurationUserLevel.None) Dim conf As New Solid.Tools.Configuration With conf .Email = “[email protected]” .FileNameAndPath = “c:\Solid.log” .EventLogName = “Solid Log” End With Dim grp As New Configuration.ConfigurationSectionGroup() .SectionGroups.Add(“Solid”, grp) grp.Sections.Add(“Mi configuración”, conf) .SaveAs(“c:\Solid.config”) ‘Se graba en C:\ para ubicarla fácilmente Un componente de registro de errores Requerimientos Estamos hablando entonces de crear únicamente una clase, base para todas las excepciones, y que fuerce a que deba heredarse. La idea es tener una clase de configuración para cada componente que la necesite El objeto Nuestro objeto, BaseException, hereda obviamente de System.Exception, para que pueda entenderse, por parte del CLR, que se trata de excepciones. Expone las mismas propiedades que Exception, pero sobrescribe Message. Esto nos permite personalizar el mensaje de cada nueva excepción que herede de la nuestra. <<dotNetManía Cada error producido en la aplicación ha de quedar registrado de alguna forma. • La implementación de dicho registro ha de ser centralizada, de modo de desligar al programador de implementar mecanismos en cada parte del proceso de la aplicación. • El registro debe ser configurable, de modo tal que se pueda determinar el o los mecanismos a través de los cuales dicho registro se realiza. • Dicha configuración puede ser modificable por el programador o un administrador del área de sistemas de la compañía, pero no por el usuario, para asegurar integridad. 36 Modelo Es obvio que en este caso, la herencia es nuestra mejor herramienta. Como Figura1 << dnm.plataforma.net Creamos en este caso, una clase de configuración que exponga las propiedades que podemos ver en el listado 1. <Flags()> Public Enum Destination As Integer LogFile EventViewer Email End Enum Private Shared mDestination As New ConfigurationProperty( _ “Destination”, GetType(Destination), _ Destination.EventViewer.ToString, _ ConfigurationPropertyOptions.IsRequired) Public Property Destination() As Destination Get Return CType(Me(“Destination”), Destination) End Get Set(ByVal value As Destination) Me(“Destination”) = value mIsModified = True End Set End Property BaseException Public MustInherit Class _ BaseException Inherits System.Exception Para poder manejar mejor el mensaje (por ejemplo, podríamos exponerlo en múltiples idiomas) lo definimos sobrescribiendo la propiedad Message: Private Shared mFileNameandPath As New ConfigurationProperty( _ “fileName”, GetType(String), _ “c:\SolidLog.log”, _ ConfigurationPropertyOptions.None) Dim mMessage As String = “” Public Overrides ReadOnly _ Property Message() As String Get If mMessage = “” Then mMessage = MyBase.Message End If Return mMessage End Get End Property <StringValidator( _ InvalidCharacters:=” ~!@#$%^&*()[]{}/;’””|”, _ MinLength:=1, MaxLength:=600)> _ Public Property FileNameAndPath() As String Get Return CStr(Me(“fileName”)) End Get Set(ByVal value As String) mIsModified = True Me(“fileName”) = value End Set End Property Private Shared mEventLogName As New ConfigurationProperty( _ “EventLogName”, GetType(String), _ “Solid Components”, _ ConfigurationPropertyOptions.None) Debemos también definir las distintas formas de los constructores, para poder hacer el registro en la bitácora correspondiente. Private Shared mEmail As New ConfigurationProperty( _ “Email”, GetType(String), _ “[email protected]”, _ ConfigurationPropertyOptions.None) <RegexStringValidator(“^[a-zA-Z\.\-_]+@([a-zA-Z\.\-_]+\.)+[a-zA-Z]{2,4}$”)>_ Public Property Email() As String Get Return CStr(Me(“Email”)) End Get Set(ByVal value As String) Me(“Email”) = value mIsModified = True End Set End Property Listado 1 Nuestro objeto, BaseException, hereda de System.Exception, para que pueda entenderse por parte del CLR que se trata de excepciones <<dotNetManía <StringValidator( _ InvalidCharacters:=” ~!@#$%^&*()[]{}/;’””|”, _ MinLength:=10, MaxLength:=40)> _ Public Property EventLogName() As String Get Return CStr(Me(“EventLogName”)) End Get Set(ByVal value As String) Me(“EventLogName”) = value mIsModified = True End Set End Property 37 << dnm.plataforma.net El constructor sin argumentos lo haremos privado, para que no pueda utilizarse nunca desde afuera, ni siquiera por parte de las clases que lo hereden. Private Sub New() MyBase.New() End Sub Y definimos los otros constructores como protegidos, para que sólo puedan ser utilizados por quienes heredan esta clase base. Protected Sub New(ByVal message_ As String) MyBase.New(message) LogThis() End Sub Protected Sub New( _ ByVal message As String, _ ByVal innerException As _ Exception) MyBase.New(message, _ innerException) If message = “” Then message =_ innerException.Message mMessage = message LogThis() End Sub LogThis Finalmente, definiremos el método que procede al registro de la excepción, LogThis. [ ] <<dotNetManía NOTA 38 Hay temas de seguridad de acceso al código que hay que tener en cuenta, como que no todos los usuarios pueden escribir en la bitácora de eventos, o en archivos físicos en disco…. Eso ya es otra cuestión. Private Sub LogThis() ‘Obtiene la configuración Dim conf As Configuration = _ CType( _ System.Configuration.ConfigurationManager.OpenExeConfiguration( _ System.Configuration.ConfigurationUserLevel.None).GetSectionGroup( _ “Solid”).Sections( _ “ExceptionHandler”), _ Configuration) Dim logName As String = conf.EventLogName Dim sfilename As String = conf.FileNameAndPath Dim sEmail As String = conf.Email ‘Obtiene el nombre de la aplicación Dim sApp As String = _ System.Reflection.Assembly.GetEntryAssembly.FullName.Split(“,”c)(0) ‘Arma el mensaje Dim sError As String = String.Format(“{0},{1},{2}”, Me.Message, _ Me.HResult, Me.StackTrace) ‘Registra la excepción dependiendo de los valores de configuración If CBool( _ conf.Destination Or CInt( _ Destination.EventViewer = Destination.EventViewer) _ ) Then ‘Si no existe el log, lo crea If Not System.Diagnostics.EventLog.Exists(logName) Then System.Diagnostics.EventLog.CreateEventSource( _ logName, logName) ‘Es necesario esperar hasta que el log esté disponible While Not System.Diagnostics.EventLog.Exists(logName) Threading.Thread.Sleep(1000) End While End If System.Diagnostics.EventLog.WriteEntry( _ logName, sError, EventLogEntryType.Error) End If If CBool( _ conf.Destination Or CInt( _ Destination.LogFile = Destination.LogFile) _ ) Then Dim fs As New System.IO.StreamWriter(sfilename, True) fs.Write(Now) fs.Write(“,”) fs.Write(sApp) fs.Write(“,”) fs.WriteLine(sError) fs.Close() fs.Dispose() End If If CBool( _ conf.Destination Or CInt( _ Destination.Email = Destination.Email) _ ) Then Try Dim sbody As New System.Text.StringBuilder(“<Table>”) sbody.AppendFormat(“<tr><td>Message: </td><td>{0}</td></tr>”, _ Me.Message) sbody.AppendFormat(_ “<tr><td>Stack Trace: </td><td>{0}</td></tr>”, _ Me.StackTrace) sbody.Append(“</table>”) With New System.Net.Mail.SmtpClient Dim m As New System.Net.Mail.MailMessage( _ sEmail, _ sEmail, _ “ERROR in “ & sApp, _ sbody.ToString) m.IsBodyHtml = True .Send(m) End With Catch ex As Exception End Try End If End Sub Joan Llopart José Luis Montes metodologías Microsoft Operations Framework (y III) La vida del software después del desarrollo según Microsoft Para cerrar esta serie de artículos de metodología, presentamos Microsoft Operations Framework, la propuesta de Microsoft para planificar, desplegar y mantener soluciones de servicio. Esta metodología complementa a Microsoft Solutions Framework, que hemos presentado en los artículos anteriores, y juntas forman la propuesta de Microsoft para la gestión del ciclo de vida de los proyectos de TI. Joan Llopart es Project Manager y arquitecto de Raona. Joan es MCSE, MCDBA, MCT, ITPro y MSF Certified. Joséé Luis Montes es Software Architect de Raona. José es MCSD.Net y MSF Certified Frecuentemente, en los departamentos de desarrollo, existe la idea que el proceso de “creación” de un software finaliza en el momento que se realiza el último requerimiento del cliente y las pruebas sobre la solución tienen un resultado satisfactorio en su entorno de trabajo. Por tanto, una vez se cumplen estos requisitos se tiene la sensación que el aplicativo debe ser implantado inmediatamente en el entorno real del cliente. A veces es difícil de entender por qué una vez el trabajo ha terminado y los resultados de verificación son plenamente satisfactorios se ha de esperar, por ejemplo, cuatro días, para verlo en el entorno real, teniendo incluso que realizar las mismas pruebas en un entorno previo de preproducción. Si realizamos este razonamiento desde el punto de vista de la persona que ha implementado el software, parece lógico pensar que se está perdiendo el tiempo y que se están repitiendo pasos innecesarios. Es muy habitual escuchar en los equipos de desarrollo la frase: ¿Para qué necesitan cuatro días para implantar nuestro aplicativo si yo lo hago en diez minutos? Para dar respuesta a esta pregunta deberemos hacer el esfuerzo de ponernos el traje del equipo de sistemas, y entonces veremos que no es tan descabellado este tiempo de espera. La idea de un buen departamento de sistemas es aquel que es proactivo, es decir, un departamento que se prepara para reaccionar eficazmente ante una posible emergencia. Un departamento de sistemas que se pierde en el trabajo del día a día, y que acaba invirtiendo la mayoría de su tiem- po en reaccionar ante emergencias imprevistas, es un departamento mal organizado, poco productivo, y que puede acabar en un caos, provocando grandes pérdidas a su cliente. Para evitar esta situación, es necesaria una buena planificación de las tareas y una buena organización del departamento. Pensemos entonces en qué se ha de hacer con el software que el equipo de desarrollo les proporciona: ¿implantarlo directamente en el entorno real del cliente? Parece claro que no. Lo más habitual es que este nuevo software conviva con otros ya existentes en la empresa; ¿qué pasaría si la implantación de este nuevo software afectara al correcto funcionamiento de los existentes? Parece lógico un periodo de pruebas en un entorno igual al real, que nos permita ver si todo funciona correctamente sin poner en peligro el resto de sistemas. Por otro lado, parece necesario estudiar cómo reaccionar ante paradas o errores del software, y cómo minimizar las pérdidas del cliente. ¿Sigue pareciendo mucho cuatro días? Parece claro que si se quiere hacer un buen trabajo final, no. Hemos visto la necesidad de organizar los departamentos de TI siguiendo alguna metodología. En este artículo se mostrará cómo Microsoft Operations Framework (MOF) ––la solución que propone Microsoft– puede ayudarnos a mejorar la calidad de los servicios que ofrecemos a nuestros clientes y, consecuentemente, a reducir esa sensación de descontrol subyacente en algunos departamentos de TI. Asimismo, espero que ayude a responder a la pre- << dnm.metodologías gunta: ¿Para qué necesitan cuatro días para implantar nuestro aplicativo si yo lo hago en diez minutos? MSF y MOF, el ciclo de vida de un proyecto de TI Microsoft, basándose en su experiencia en consultorías, en la realización de proyectos en clientes de diferentes tamaños, y en el gran conocimiento de su tecnología, presenta, para dar solución a los grandes retos que afrontan las empresas de hoy en día, un conjunto de propuestas para diseñar, desarrollar, implementar, operar y dar soluciones de forma eficiente. La propuesta de Microsoft consiste en dos metodologías complementarías: Microsoft Solutions Framework (MSF) y MOF, que si se integran adecuadamente proporcionan un conjunto de directrices para gestionar el ciclo de vida de los proyectos de TI. MSF es la metodología Microsoft para el desarrollo de software y su posterior implantación con éxito. MSF, mediante un conjunto de principios, modelos, disciplinas, conceptos, directrices y prácti- cas, intenta ayudar a los equipos y organizaciones a obtener soluciones de éxito en sus clientes. MSF define, mediante una metodología, cómo: • Poner al mismo nivel los objetivos del negocio y la tecnología. • Establecer claramente los objetivos del proyecto, los roles que intervienen y las responsabilidades de cada rol. • Desarrollar iterativamente, basándose en un sistema de hitos intermedios. • Gestionar el riesgo de manera proactiva; es decir, se ha de trabajar para intentar evitar los riesgos y en obtener una solución antes que sucedan. • Responder eficazmente a los cambios. MOF consiste en un conjunto de directrices sobre la manera de planificar, desplegar y mantener procesos operativos de TI para el soporte de soluciones de servicio. MOF tiene una estructura flexible basada en: • La experiencia en consultoría y trabajo en clientes. Figura 2. El ciclo de vida de un proyecto de TI con MSF y MOF • La IT Infrastructure Library (ITIL). • La ISO 15504 (también denominado SPICE), que proporciona un enfoque normalizado para evaluar la madurez de procesos de software. Como se puede observar en la figura 2, la combinación de MSF y MOF en el ciclo de vida de un proyecto de IT intenta ayudar a los equipos de desarrollo y de operaciones para dar solución a los tres puntos clave de las necesidades del cliente: • Entender las necesidades del cliente. • Desarrollar y desplegar la solución eficientemente, ocasionando la menor perturbación posible en el cliente. • Ofrecer un buen servicio de gestión de la solución una vez implantada. El ciclo de vida de IT con MSF y MOF sigue cuatro pasos básicos: Figura1. MSF y MOF las soluciones Microsoft 1. Planificar la solución siguiendo las directrices de MSF y MOF. 2. Desarrollar la solución siguiendo las directrices de MSF. 3. Desplegar la solución siguiendo las directrices de MOF y MSF. 4. Gestionar la solución siguiendo las directrices de MOF. En el ciclo de vida de los proyectos de TI, MOF entra plenamente en juego cuando termina MSF; es decir, una vez <<dotNetManía Microsoft Solutions Framework (MSF) y Microsoft Operations Framework (MOF) son metodologías independientes entre sí, pero complementarias, y juntas forman la propuesta de Microsoft para ofrecer una visión completa del ciclo de vida de las soluciones en el ámbito de los sistemas de información. MSF es la propuesta de Microsoft para el desarrollo de proyectos, mientras que MOF es una metodología independiente para gestionar los servicios de TI. Aunque queda fuera del ámbito de este artículo hablar de MSF, recomiendo la lectura de la información que Microsoft presenta en la web http://www.microsoft.com/technet/solutionaccelerators/msf/default.mspx y los enlaces relacionados. De esta forma se podrá entender mejor la relación entre ambas metodologías. 41 << dnm.metodologías que se desarrolla una solución de servicio y ésta se despliega en el entorno real. Por tanto, MOF se activa cuando aparece la necesidad de mantener la solución en condiciones óptimas de funcionamiento. Relación con ITIL La Biblioteca de Infraestructuras de Informática (ITIL) es un conjunto completo y coherente de recomendaciones para la administración de servicios informáticos. MOF es una adaptación del estándar ITIL a la plataforma Microsoft para la gestión de servicios de TI basados en tecnologías Microsoft. Si se detecta alguna carencia en MOF, siempre se puede consultar ITIL para ver cómo se aborda el problema en un nivel más general. Para más información acerca de ITIL, puede consultar su Web oficial: http://www.itil.co.uk. Entrando en MOF Una vez vistos los orígenes de MOF y su relación con MSF, solo nos queda dar el paso de entrar a conocer MOF por dentro. Creo que ya es hora de responder a la pregunta que da nombre a este artículo: ¿qué es MOF? Como ya he comentado anteriormente, MOF es un conjunto de recomendaciones, principios y modelos. Esta guía proporciona a los equipos de operaciones un conjunto de herramientas para realizar su trabajo de gestión de los entornos de producción de una manera más eficiente, con mayor seguridad, y reduciendo riesgos de fallos inesperados y críticos. Un punto que hay que resaltar es que MOF es un modelo genérico, lo que significa que para implantarlo en una organización deberá ser adaptado a las necesidades concretas de la misma. MOF se basa en tres conceptos fundamentales: el modelo de procesos, el modelo de equipos y la gestión del riesgo. <<dotNetManía Modelo de procesos 42 Tal y como se puede observar en la figura 3, el modelo de procesos de MOF es cíclico y se divide en cuatro cua- Figura 3. MOF Process Model drantes (etapas): Changing, Operating, Supporting y Optimizing. Asimismo, al final de cada cuadrante se produce una revisión o hito: Operations, SLA, Release Approved y Release Readiness; siendo las dos primeras de carácter periódico y las otras dos por cada versión final que se realice. Como ya he comentado anteriormente, el ciclo de MOF entra en juego en el momento que finaliza el proceso de desarrollo de una solución y se inicia su fase de despliegue en el entorno del cliente; este punto corresponde al cuadrante Changing. El paso inicial de este cuadrante ha de ser la comunicación entre el departamento de desarrollo y el de explotación para acordar los requisitos que garanticen el correcto funcionamiento de la solución en el entorno real. Este conjunto de requisitos puede producir cambios en los entornos de trabajo; este punto se conoce como Change Management. Una vez detectados los requisitos necesarios para desplegar la solución, se pasa a la fase de localizar los recursos necesarios para lograr cumplir los requisitos marcados. Este punto, conocido como Configuration Management, incluye un punto a veces olvidado por muchos equipos de trabajo: la documentación de los componentes del entorno y su interrelación. El punto final de este cuadrante es la gestión de versiones, ya que como hemos comentado anteriormente este modelo es cíclico, y por tanto nos podemos encontrar con la necesidad de realizar el ciclo para varias versiones de la solución. Al final de este cuadrante es donde se obtiene el permiso para el arranque real del despliegue, concretamente en el hito Release Readiness Review, y es cuando se ejecuta el despliegue en sí, proceso llamado Release Management, donde además se hacen las pruebas de despliegue y la validación del prototipo final. Es en este momento cuando la solución está en el entorno real del cliente. Una vez la solución está operativa en el entorno real del cliente, es necesario realizar un conjunto de tareas que permitan garantizar el correcto funcionamiento de la solución en todo momento. Estas tareas pertenecen al cuadrante Operating. Este conjunto de tareas está orientado a hacer una ejecución más eficiente de las tareas diarias. Este cuadrante se centra en tareas como: • El seguimiento del estado del servicio mediante la monitorización del mismo (Service Monitoring & Control). • El mantenimiento de los sistemas de mensajería y la coordinación de los equipos de TI (System Administration). • Tareas de mantenimiento físico de la red (Network Administration). • Tareas de mantenimiento de Active Directory (Directory Services Administration). << dnm.metodologías Este cuadrante finaliza al conseguir el hito Operations Review y consiste en revisiones periódicas para revisar el trabajo hecho y toda la documentación generada. Aunque en el cuadrante anterior se haya creado un conjunto de actividades para mantener operativa la solución, en el día a día surgen problemas ante los que hay que reaccionar, ya que en un entorno crítico de producción se ha de minimizar al máximo el tiempo de respuesta ante un error del sistema. La mayoría de empresas cuenta con un departamento Service Desk, el cual se encarga de recibir y solucionar la petición de cambio o solución de problema, o redirigirla a la persona adecuada para que lo haga. Otras dos funciones que encontramos en este cuadrante son Incident Management y Problem Management. La primera función se encarga de dejar constancia de las peticiones que se reciben, mientras que la segunda haría referencia a cómo gestionar la solución al problema basándose en la información que se ha proporcionado. Estas tres funciones pertenecen al cuadrante Supporting. Este cuadrante finaliza al conseguirse el hito SLA Review, que consiste en reuniones periódicas entre clientes, proveedores y los responsables del departamento de TI para revisar el estado del servicio y estudiar posibles vías de mejora. Una vez tenemos desplegada la solución, hemos sentado las bases para intentar que esté operativa siempre, y en caso contrario hemos creado medidas para reaccionar lo más rápido posible y hemos estructurado un mecanismo de soporte ante peticiones de cambio; solo queda un punto a tratar: cómo optimizar el servicio. Este punto se trata en el cuadrante Optimizing, el cual presenta un conjunto de procesos que buscan cómo obtener más rendimiento, capacidad y disponibilidad con menos coste. Concretamente tenemos: • Service Level Management, que mide la relación entre el servicio prestado y el acordado. • Capacity Management, que observa las tendencias de rendimiento y estima las futuras necesidades de recursos para tenerlas en cuenta en los presupuestos y en los SLA. • Availability Management, que mide la relación entre la disponibilidad de la oferta de servicios respecto a su coste. • Security Management, que define y comunica todos los temas relacionados con la seguridad. • Infrastructure Engineering, que consiste en un conjunto de estándares para mejorar la interoperabilidad y reducir el riesgo de fracaso en los despliegues de soluciones. • Financial Management, que gestiona el presupuesto de TI. • Workforce Management, que trata de gestionar la disponibilidad de recursos para intentar permitir que el recurso humano necesario para cumplir con los SLA siempre esté disponible. • Service Continuity Management, que trata todo lo relacionado con cómo debe actuar un departamento de TI frente a errores críticos imprevistos. En este punto acaba el ciclo de vida, pero nos encontramos con diversas posibilidades: 1. Se acepta la versión actual, con lo cual se para momentáneamente el ciclo. 2. Se requieren cambios de desarrollo, con lo cual entraría en juego MSF y cuando acaba el desarrollo se vuelve a iniciar MOF en el cuadrante Changing con una nueva versión. Modelo de equipo El Modelo de equipo que plantea MOF y los clústeres de funciones asociadas aportan un conjunto de directrices para intentar asignar a las personas más capacitadas a las funciones operativas y de esta forma crear equipos de trabajo más eficientes. El modelo se basa en un conjunto de roles, concretamente seis: Release, Operations, Support, Partner, Infrastructure y Security. En la figura 4 podemos ver los diferentes roles que se mencionan y los dife- Figura 4. MOF Team Model <<dotNetManía • La confidencialidad, integridad y disponibilidad de los datos (Security Administration). • Todas las tareas referentes al almacenamiento de datos (Storage Management). • Tareas de mantenimiento programadas que que intentan minimizar el impacto sobre el entorno (Job Scheduling). 43 << dnm.metodologías rentes objetivos que tienen asignados. Asimismo, cada rol está asignado a uno o varios de los cuadrantes del Modelo de Procesos; concretamente, Release y Operations están claramente vinculados a los cuadrantes Changing y Operating, respectivamente; Support y Partner están presentes en los cuadrantes Supporting y Optimizing; el rol Infrastructure está en el cuadrante Optimizing; y el de Security en Optimizing y Operating. Un punto importante de este modelo es el hecho que existe la posibilidad de que una persona participe en diversos roles, pero no todas las combinaciones son posibles, ya que ciertas combinaciones podrían desestabilizar el correcto funcionamiento del equipo. En la figura 5 podemos ver una matriz donde se muestran las combinaciones Posibles, No deseadas y No recomendadas. Figura 6. MOF y la gestión del riesgo La segunda etapa consiste en analizar la lista de riesgos detectada en la etapa anterior, midiéndose la probabilidad de que ocurra y el impacto que supondría. Figura 5. Matriz de combinación de roles <<dotNetManía Gestión del riesgo 44 En este apartado MOF describe el proceso para identificar un riesgo y cómo actuar ante él. En la figura 6 se puede observar que este proceso consta de cinco etapas: La primera etapa consiste en la detección del riesgo. Pero para detectar un riesgo, primero es necesario saber qué es. Un riesgo es el daño potencial que puede surgir por un proceso presente o evento futuro. El resultado de esta etapa es una lista de riesgos. Una vez realizado el análisis de impacto, se realiza la planificación de acciones para intentar mitigar el impacto de que suceda el riesgo detectado. Todos los riesgos detectados y documentados necesitan un seguimiento que actualice los datos que se tengan documentados (Risk Assessment Document). En la etapa de control se evalúa cómo actuar ante los riesgos existentes. Conclusiones Cada día tiene más importancia una gestión eficiente del tiempo y de los recur- sos, así como la reducción al máximo de los errores críticos no controlados y del tiempo de respuesta ante una eventualidad. Acabamos de ver MOF, la propuesta de Microsoft para lograr estos propósitos, y aunque parezca que si se siguen los pasos que ésta marca en el trabajo diario perderemos el tiempo, es todo lo contrario. Es cierto que parece más rápido desplegar una solución directamente en el entorno real del cliente que pasar por un periodo de prueba en un entorno de preproducción. También parece más sencillo esperar a que ocurra un problema que invertir horas en prepararse ante un posible error futuro que tal vez nunca suceda. Pero estoy seguro que todos podemos recordar algún momento de crisis en un departamento de TI en el cual se ha detectado un error crítico que ha parado completamente al cliente, y que para resolverlo se han tenido que dedicar varios días sin dormir. Esto lo único que aporta es una mala imagen al cliente y deja al departamento de TI como un grupo de ingenieros desorganizados y que solo se implican cuando tienen un problema. Aunque hoy en día cada vez existen menos departamentos de TI con “poca organización”, se ha de seguir trabajando hacia departamentos de TI proactivos. Es ahí donde MOF entra en juego, conjuntamente con MSF, como una herramienta muy útil para conseguirlo. Cuando tomamos decisiones, necesitamos información; esa información nos ayuda a gestionar mejor los procesos de nuestras organizaciones y a ser más competitivos, dándonos mejores posibilidades de supervivencia en el corto, medio y largo plazo. Aproximadamente el noventa por ciento de la información que necesitamos está dentro de nuestra propia organización, pero por desgracia la dispersión de la información (múltiples fuentes de datos, múltiples formatos, calidad del dato mostrado, etc.) hace que solo el treinta por ciento esté accesible en el formato y en el tiempo adecuado; el resto son datos por tratar o información no estructurada... si no lo tienes claro... GR d La revista de la Gestión del Rendimiento La revista para la Gestión del Rendimiento www.gestiondelrendimiento.com todonet@qa [email protected] Dino Esposito Dino Esposito es mentor de Solid Quality Learning. Es ponente habitual en los eventos de la industria a nivel mundial.Visite su blog en: http://weblogs. asp.net/despos. (todotNet.QA@ dotnetmania.com) Enlazándonos a LINQ El término LINQ proviene de Language Integrated Query, tecnología creada por Microsoft para solucionar la necesidad de las aplicaciones de acceder a datos desde un alto nivel de abstracción mediante un conjunto de herramientas poderoso pero sencillo. LINQ se creó para independizar las aplicaciones del motor de acceso a datos y sus opacas cadenas de conexión y comandos, reemplazándolos por sintaxis nativa incorporada a los lenguajes C# y VB .NET. De forma que estos compiladores soportan en .NET Framework 3.5 una nueva sintaxis extendida (las expresiones de consulta) que hace posible consultar un almacén de datos sin recurrir a una API específica como ADO.NET. LINQ suministra un conjunto de métodos estándar (conocidos como operadores de consulta estándar), que se corresponden con una API subyacente de cada tipo de almacén a consultar. Por tanto, hay muchas modalidades de LINQ: concretamente, LINQ to Objects para colecciones .NET en memoria, LINQ to DataSets para objetos data- set de ADO.NET, LINQ to SQL para tablas relacionales de SQL Server y LINQ to XML para documentos XML. Este mes responderé a varias preguntas frecuentes en relación a LINQ y LINQ to SQL, saltándome la más popular: “He oído hablar de LINQ, ¿cómo puedo utilizarlo?”. ¿Cuál es la diferencia entre el uso de las nuevas palabras reservadas como from, select y where y los métodos con nombres similares que se llaman sobre el mismo objeto al que se va a consultar? ¿Cuál es preferible? Hay dos formas de expresar operaciones LINQ. Puedes usar expresiones de consulta a través de un conjunto de nuevas instrucciones específicas del lenguaje, tales como las mencionadas en la pregunta (from, select, where, y también orderby, group y join). Como alternativa pueden utilizarse los métodos del objeto de consulta. Lo primero a tener en cuenta es que las expresiones de consulta son una especie de metáfora sintáctica para hacer más fácil la consulta. Todas las expresiones de consulta que utilizan las palabras reservadas se harán corresponder, en tiempo de compilación, con llamadas a métodos. Esto significa que las dos opciones son correctas y equivalentes desde el punto de vista funcional y de rendimiento. De forma que, al final, utilizar una u otra es más bien una cuestión de preferencia personal. Claramente, una expresión que utilice una sintaxis libre del tipo from…select es, normalmente, más legible y menos prolija que una larga letanía de llamadas a métodos en cadena. Como ejemplo, consideremos la siguiente expresión: // Uso de expresiones LINQ YourDataContext db = new YourDataContext(connString); var data = from c in db.Customers where c.Country == “Spain” select c; Esta sentencia es totalmente equivalente a la llamada a método que se muestra a continuación. La conversión de la primera en la segunda es totalmente una labor del compilador, y las reglas de conversión entre las palabras reservadas y los ope- // Uso de llamadas a operadores de consulta estándar var data = db.Customers.Where( c => c.Country == “Spain”) La palabra reservada var es nueva en .NET Framework 3.5 e indica la inferencia del tipo de las variables locales. El tipo de la variable que sigue al operador se determina en tiempo de compilación en función del valor que se le asigna. ¿Pero, qué es db.Customers? Aparte del hecho de que el ejemplo utiliza LINQ to SQL, la expresión db.Customers puede ser considerada como un contenedor para un objeto de consulta. Un tipo .NET es consultable si implementa IEnumerable<T> o una interfaz derivada, tal como IQueryable<T>. Array, List, Dictionary y cualquier otro tipo de colección en .NET Framework, incluyendo las clases de colección personalizadas son inherentemente consultables, porque son enumerables. No se requiere ningún tipo de transformación para hacer estos objetos consultables. Por otra parte, un documento XML o un DataSet no tipado no se pueden consultar de forma inmediata, y por ello requieren un tratamiento especial. Para consultar un DataSet no tipado desde LINQ, hay que invocar previamente el método AsEnumerable sobre el DataSet. De la misma forma, hay que cargar un fichero XML mediante el método XDocument.Load. Una vez que esos métodos retornan, disponemos de objetos válidos que pueden ser usados como fuente para la consulta integrada. ¿Qué sucede con el mundo de las bases de datos? Digamos antes que LINQ to SQL solo soporta –de momento– SQL Server. Para permitir que LINQ trabaje sobre una base de datos, debemos crear una clase heredera de DataContext; en Visual Studio 2008, el diseñador O/R lo hace por nosotros (ver figura 1). La clase contiene propiedades que representan a las tablas físicas de la base de datos. El tipo de esas propiedades es System.Data.Linq.Table<T>, que es obviamente un tipo consultable. En el código anterior, la expresión db.Customers hace referencia a Table<Customer>, desde el que se van a seleccionar los clientes residentes en España. Cualquier objeto consultable dispone de un conjunto de métodos/operadores de consulta; por ejemplo, Where. En la comparación de las dos sentencias funcionalmente idénticas, no puedo ignorar la extraña expresión pasada como argumento al método Where. El operador => indica una expresión lambda, que es una expresión en línea que puede utilizarse siempre que se espere la presencia de un delegado. El operador => se lee como “de forma que”, o “implica que”, igual que en la jerga del análisis matemático. La parte izquierda de la expresión indica los parámetros de entrada. A la derecha se sitúa la expresión o bloque de sentencias a ejecutar. Las expresiones lambda se utilizan en LINQ como una manera conveniente de crear delegados. Será necesario crear expresiones lambda en LINQ principalmente si trabajamos con llamadas a métodos. Es improbable, pero no imposible, que haya que usar estas expresiones si se utiliza la sintaxis del lenguaje. Y, por último, una expresión de consulta que utilice la sintaxis integrada es perfectamente equivalente a una consulta expresada mediante métodos; lo contrario, sin embargo, no es siempre cierto. Por ejemplo, no se puede expresar una consulta de tipo SELECT DISTINCT usando la sintaxis de C#. Del mismo modo, no puede seleccionarse un objeto dado sin recurrir a los métodos. Veamos cómo hacer una consulta SELECT DISTINCT: var data = (from c in dataContext.Customers select c.Country).Distinct(); Mediante la inclusión de la expresión from…select entre paréntesis, disponemos de su resultado como una secuencia (consultable) de elementos de un tipo anónimo, sobre la que podremos llamar a cualquiera de los operadores de consulta soportados.seleccionar un objeto que cumpla una condición específica, podríamos tener que especificar el criterio usando una función lambda: var data=(from c in dataContext.Customers select c).First(x => x.Orders.Count()>10); Figura 1: Diseñador de objetos relacionales en Visual Studio 2008 ¿Qué hace la consulta anterior? Selecciona el primer cliente que tiene más de 10 pedidos. ¿Qué es mejor entre operadores y métodos? A mí me gusta la inmediatez de los operadores, pero –al mismo tiempo– no me preocupa utilizar lambdas donde veo que es necesario. <<dotNetManía radores de consulta están establecidas en las especificaciones del lenguaje. [email protected] [email protected] << dnm.todonet@qa 47 <<dotNetManía T o d o t N e t . q a @ d o t n e t m a n i a . c o m T o d o t N e t . q a @ d o t n e t m a n i a . c o m << dnm.todonet@qa 48 He visto que LINQ me permite seleccionar un elemento en una posición particular en la secuencia. Me gustaría seleccionar, pongamos, el undécimo registro de la tabla de clientes. Cuando lo hago, la aplicación falla, con un mensaje indicando que estoy utilizando una característica no soportada. Pero esto funciona estupendamente para DataSet y colecciones. Estoy utilizando Visual Studio 2008 Beta 2 y .NET Framework 3.5. ¿Es un bug? No, no es un bug. Lo que indicas tiene sentido en un escenario LINQ to SQL. No es que no haya forma de lograr eso, pero no es nada extraño que una sentencia falle por el hecho de que una característica no esté soportada. Me explico. LINQ to SQL traduce la sintaxis de expresiones de consulta en llamadas a los operadores estándar, que a fin de cuentas generan comandos T-SQL. Sin embargo, los operadores de LINQ se definen para secuencias ordenadas de elementos. El lenguaje T-SQL (del mismo modo que cualquier otro lenguaje basado en SQL) funciona devolviendo un conjunto no ordenado de valores. ¿Qué significa “no ordenado” aquí? No me refiero al hecho de que puedas o no usar la cláusula ORDER BY, sino a que no existe ninguna noción de orden en la secuencia que obtienes de SQL Server. La identidad de un elemento en un conjunto de resultados SQL se establece por los valores de sus campos, y no por su posición en la secuencia. No es un problema de LINQ o SQL Server: es uno de los pilares del lenguaje SQL. A causa de esta diferencia sustancial entre colecciones en memoria y conjuntos de resultados, ciertos operadores estándar de LINQ no funcionan apropiadamente o simplemente no están soportados en LINQ to SQL. Por eso es que operadores como Take o Skip pueden funcionar, pero a costa de ejecutar comandos T-SQL más complejos. Otros operadores, como TakeWhile, SkipWhile, Last, Reverse y, especialmente, ElementAt, no se traducen a T-SQL y no están soportados por LINQ to SQL. En LINQ to SQL, para recuperar un registro deberías especificar una cláusula where y localizarlo por su clave primaria o por los valores de algunas de sus columnas. Es la forma en que funcionan las bases de datos relacionales, no es un tema de LINQ. Dicho esto, alguien podría argumentar que SQL Server 2005 soporta el operador ROWCOUNT para establecer algún orden en un conjunto no ordenado de datos. Hasta donde yo sé, LINQ to SQL no aprovecha esa característica, ni siquiera cuando la base de datos de consulta es SQL Server 2005. Pero esto es algo que podría cambiar en el futuro. ¿Puede destacar la diferencia entre los métodos Select y SelectMany en LINQ? Ambos devuelven una selección de registros y ambos pueden devolver cero, uno o muchos registros. ¿Así pues, cuál es la diferencia? Como has dicho “brevemente”, trataré de resumir. Imagina un escenario donde tenemos una relación entre dos tablas, digamos, Customers y Orders. La siguiente consulta utiliza el operador estándar Select y devuelve una secuencia de objetos con el conjunto de propiedades indicado. var data = from c in dataContext.Customers where c.Country == “Spain” select new { c.CompanyName, c.Orders }; Los elementos de la secuencia resultante consisten de objetos cuyo tipo tiene una propiedad de cadena CompanyName y una secuencia de objetos Order. La propiedad Orders, por supuesto, contendrá todos los objetos Order correspondientes a los pedidos de cada cliente dado; pero los datos devueltos no son tabulares y la mayoría de los controles enlazados (por ejemplo, el DataGrid), no podrán mostrarlos. La misma consulta puede expresarse en forma tabular, con alguna inevitable redundancia de datos, mediante el uso del método SelectMany: var data = (from c in dataContext.Customers where c.Country == “Spain” select c).SelectMany(c => c.Orders); En este caso, el resultado es un conjunto de datos “aplanado”. Traducido al castellano por Marino Posadas Octavio Hernández Laboratorio.net VistaDB v3.2 Este mes presentamos un asombroso motor de bases de datos relacionales,VistaDB 3.2, de VistaDB Software, desarrollado completamente en código manejado y que ofrece un excelente rendimiento y un alto nivel de compatibilidad con SQL Server 2005 en una insignificante huella de memoria (aprox. 750 KB), lo que lo hace ideal incluso para el desarrollo de aplicaciones para dispositivos móviles. Ficha técnica Nombre: VistaDB Versión: 3.2 Fabricante: VistaDB Software Sitio Web: http://www.vistadb.net/ Categoría: Motores de bases de datos Precio por desarrollador: • Sin suscripción: 199 USD/desarrollador. • Con suscripción anual: 299 USD/desarrollador* • Con código fuente y suscripción anual: 1.499 USD/desarrollador * - descuentos especiales para múltiples licencias. Octavio Hernández es Development Advisor de Plain Concepts, editor técnico de dotNetManía y tutor de campusMVP. Es MVP de C# desde 2004, MCSD y MCT. VistaDB Software lleva más de una década dedicándose al desarrollo de motores de bases de datos embebibles. Con el surgimiento de .NET Framework, la empresa decidió no portar el código no manejado de la versión anterior (que aún sigue a la venta), sino rediseñar y volver a programar todo el producto (incluyendo el núcleo del motor, las clases de su librería DDA –Direct Data Access–, el procesador de consultas compatible con Transact SQL, el proveedor ADO.NET y las herramientas externas) utilizando única y exclusivamente código C#. El resultado de ese esfuerzo es VistaDB 3, un motor de bases de datos relacionales basado al 100% en código manejado que garantiza una integración completa con .NET Framework, .NET Compact Framework y Mono. Creo sinceramente que los equipos que estén desarrollando aplicaciones que utilicen bases de datos de pequeño o mediano tamaño deberían echar un vistazo a este excelente producto. Características principales Una simple enumeración de la impresionante lista de características de VistaDB 3 debería ser suficiente para despertar la curiosidad de la mayoría de los lectores de la revista: • Ejecución nativa en .NET, Compact Framework 2 y Mono, como hemos mencionado anteriormente. De aquí se desprende el soporte para todas las plataformas para las que estas implementaciones funcionan. • Arquitectura 100% manejada y segura en cuanto a tipos, como puede comprobarse ejecutando PVerify. Esto implica que el código de VistaDB funcionará sin problemas en entornos de confianza parcial, como el hosting compartido o desde una unidad de DVD bajo Windows Vista. • Soporte multiusuario para el acceso a bases de datos en una unidad local, unidad de red compartida e incluso en memoria local, gracias a la tecnología DDA. • Ínfima huella de memoria, con menos de 1 Mb de redistribución total, lo que garantiza un mínimo tiempo de descarga para los programas que utilicen VistaDB y hace posible su incorporación a aplicaciones para Pocket PC y otros dispositivos con memoria reducida. • Ninguna necesidad de desarrollar instaladores complicados. Solo será necesario copiar una DLL de menos 1 MB con su ejecutable. Tampoco será necesario tener privilegios de administrador o instalar servicios durante la instalación de un software basado en VistaDB. << dnm.laboratorio.net • Embebible al 100% si se utiliza ILMerge. En caso de que se combine el ensamblado de VistaDB con la aplicación que lo utiliza, ni siquiera habrá que redistribuir por separado ese ensamblado. dotNetManía ya dedicó hace unos meses un artículo, escrito por mi compañero Jorgito Serrano [2], a ILMerge. • Bases de datos implementadas en un único fichero, para simplificar al máximo el despliegue y la ya de por sí casi innecesaria administración posterior. • Compatibilidad con Microsoft SQL Server en los tipos de datos y la sintaxis de las sentencias DML en Transact SQL. Esto simplifica enormemente el desarrollo simultáneo para ambas bases de datos. • Soporte para procedimientos almacenados y disparadores en código manejado (C#, VB, etc.). Una futura versión (finales de 2007) añadirá soporte para procedimientos almacenados y triggers en Transact SQL. • Modelo de programación sencillo y familiar a los desarrolladores .NET, basado al 100% en el modelo de objetos de ADO.NET 2.0, como ejemplificaremos más adelante. • Integración completa en Visual Studio 2005. Desde el Explorador de servidores y la ventana de Orígenes de datos de VS 2005 es posible visualizar las bases de datos de VistaDB 3, y usar las mismas técnicas de “arrastrar y soltar” disponibles para SQL Server y otras bases de datos. • Conjunto completo de herramientas visuales auxiliares, incluyendo Data Builder, para la creación y modificación interactiva de bases de datos y Data Migration Wizard, para la migración a Vista DB de bases de datos SQL Server, Oracle, Access, FoxPro y otras. • Muy avanzado el soporte para Visual Studio 2008 y .NET Framework 3.5. De hecho, el ejemplo que se presenta a continuación ha sido desarrollado bajo la Beta 2 de VS 2008. También se trabaja actualmente en un proveedor LINQ y el soporte para los nuevos servicios de sincronización. y las numerosas aplicaciones de ejemplo que lo acompañan, y que muestran la utilización de VistaDB para el desarrollo de aplicaciones de escritorio, Web o para dispositivos móviles. Figura 1. Grupo de programas de VistaDB Migración de datos Comenzaremos nuestro proceso de familiarización con VistaDB por la migración de una base de datos de SQL Server; en este caso utilizaremos la base de datos FUTBOL2006, que almacena información sobre los equipos que tomaron parte en la Liga española de fútbol 2006-2007 y en la que se basa una buena parte de los ejemplos de mi libro “C# 3.0 y LINQ” [3]. Tanto la base de datos original como la resultante de la migra- Un recorrido rápido La instalación de VistaDB en un equipo de desarrollo es inmediata y no presenta problema alguno. Como resultado de la instalación, se añade al menú “Inicio” (figura 1) un grupo de programas desde el que se puede lanzar las herramientas que forman parte al producto, así como explorar la completa documentación Figura 2. Migración de la base de datos FUTBOL2006 (1). <<dotNetManía Instalación 51 << dnm.laboratorio.net Para el desarrollo visual arrastrando y soltando basta con registrar la base de datos ante el Explorador de Servidores y la ventana de Orígenes de datos; la figura 5 muestra los detalles del proceso. A partir de ese momento, se puede seguir una metodología similar a la que se utiliza en el caso de otras bases de datos. Por ejemplo, la figura 6 muestra el resultado de la ejecución de la aplicación terminada; la navegación por la tabla de futbolistas ha sido obtenida sin escribir una sola línea de código. Figura 3. Migración de la base de datos FUTBOL2006 (2). ción y todo el código de ejemplo pueden descargarse del sitio Web de dotNetManía. Para migrar la base de datos, lanzamos la herramienta Data Migration Wizard. Como puede verse en las figuras 2, 3 y 4, el asistente reconoce perfectamente los metadatos y datos de SQL Server y ejecuta una migración perfecta. Desde el paso final del asistente es posible lanzar Data Builder, que nos permite examinar la base de datos de VistaDB resultante (figura 4). Figura 5. Agregando una conexión a la base de datos VistaDB Figura 4.VistaDB Data Builder en acción <<dotNetManía Desarrollo de aplicaciones 52 La programación de aplicaciones que utilicen bases de datos de VistaDB es, gracias a la disponibilidad del proveedor ADO.NET y la excelente integración con Visual Studio, muy similar a si se tratara de bases de datos de SQL Server, Access u otra de las bases de datos soportadas directamente por .NET Framework. La programación de aplicaciones que utilicen bases de datos de VistaDB es muy similar a si se tratara de bases de datos de SQL Server << dnm.laboratorio.net cómo a nivel de programación también el trabajo contra una base de datos de VistaDB es muy similar a si se tratara de una de las bases de datos “habituales”, y cómo VistaDB soporta funciones predefinidas de SQL Server como DateDiff o GetDate. Conclusiones Figura 6. La aplicación de ejemplo en ejecución. Pero como no solo de desarrollo visual vive el programador, hemos agregado a la interfaz de usuario de la aplicación un botón cuyo gestor de eventos ejecuta, a través de una conexión diferente, una sencilla sentencia SQL; el código (listado 1) permite comprobar A lo largo de este artículo hemos intentado mostrar las principales ventajas que puede ofrecer a los desarrolladores la utilización en sus proyectos que necesiten bases de datos de pequeño o mediano tamaño de un motor de base de datos ligero y embebible como VistaDB. Recomendamos sin duda alguna al lector que descargue del sitio Web del fabricante [1] la versión de evaluación y compruebe por sí mismo las bondades de este producto. // using VistaDB.Provider; private void button1_Click(object sender, EventArgs e) { using (VistaDBConnection con = new VistaDBConnection( “Data Source=C:\\DNM42\\FUTBOL2006.vdb3;Password=futbol2006”)) { con.Open(); using (VistaDBCommand cmd = new VistaDBCommand( “SELECT AVG(DateDiff(year, FechaNacimiento, GetDate()))” + “ FROM Futbolista”, con)) { int media = (int) cmd.ExecuteScalar(); MessageBox.Show(“La media de edad es de “ + media.ToString()); } } } Referencias [ 1 ] Sitio Web de VistaDB: http://www.vistadb.net. [ 3 ] Hernández O., “C# 3.0 y LINQ”, ISBN: 978-84-935489-1-9, Krasis Press (http://www.krasispress.com). <<dotNetManía [ 2 ] Serrano J., “Combinando ensamblados .NET con ILMerge”, en dotNetManía nº 36, abril de 2007. 53 comunidad.net MVP Open Day y TTT 2007 Grupos de usuarios .NET, DotNet Clubs y MVP, juntos por un día <<dotNetManía Ambos eventos se han celebrado conjuntamente este año, reuniendo a los profesionales y estudiantes más involucrados en la difusión de tecnologías Microsoft. 54 El día 5 de octubre se celebró en las oficinas de Microsoft Ibérica en Madrid el “MVP Open Day y TTT”, un evento que este año congregó a los MVP y a los componentes de grupos de usuarios y los clubs universitarios (dotnet clubs), reuniendo así a un buen número de entusiastas de tecnologías Microsoft de toda España, todos ellos con vocación de aportar talento a la comunidad de desarrolladores y profesionales IT en nuestro país. Las ponencias se dividieron en tres tracks: desarrollo, sistemas y Office. En el track de desarrollo asistimos a las charlas “Meigas… haberlas haylas. ADO.NET Synch Services” de Unai Zorrilla; “Codigo VIVO. LIVE Services for Developers”, de David Salgado; y “Y se hizo la luz. Silverlight” de Isabel Gómez. Ya por la tarde asistimos a reuniones particulares de los grupos de usuarios y dotnet clubs y de los MVP, con la presencia de Çigdem Akin, EMEA MVP Team Manager de Microsoft, Steve Alter, MVP Program Manager de Microsoft y Cristina González, MVP Lead de Microsoft. Finalizó la jornada con una cena a la que Microsoft Ibérica nos invitó amablemente, y en la que todos tuvimos la ocasión de confraternizar con compañeros a los que habitualmente solo vemos en el ciberespacio. Cambios para MSDN Muy interesante estuvo la presentación de MSDN Online, que además de estrenar nueva imagen, ofrecerá próximamente la traducción inmediata de los contenidos tanto de los centros de desarrollo americanos como de la revista MSDN Magazine y MSDN Library. Además, se han mejorado las búsquedas, con indexación de contenidos incluidos en blogs y foros; habrá cambios de infraestructura de los foros (con fotos de participantes, por ejemplo) y nuevos cursos online, entre los que tendremos uno sobre Silverlight para noviembre y otro de Orcas para principios del año que viene. Se hizo mucho hincapié en la importancia de la participación de toda la comunidad de profesionales y estudiantes en la elaboración de contenidos en castellano. << dnm.comunidad.net opciones, finalmente se ha decidido crear nuestra propia versión, que verá la luz a finales Fotografía reciente de Capitán Tomate de este año natural. En Capitán Capitán Tomate Tomate se publicarán contenidos multimedia, vídeos con entrevistas, series técnicas, eventos de grupos de Además, David Salgado, evangelista usuarios, screencasts, podcasts, además de desarrolladores de Microsoft, presentó de otros recursos en castellano. la iniciativa que divertidamente han denominado Capitán Tomate, con una filosofía similar a Channel 9, pero en casteAlto o disparo… llano. Después de reunirse con la dirección de Channel 9 (http://channel9. Por último, el día 6 de octubre pudimsdn.com) en USA y valorando otras mos resolver todas nuestras diferencias David Salgado, evangelista de desarrolladores de Microsoft, atacado por los feligreses a balazos en un divertido paintball que nos dejó con algún que otro moratón, pero satisfechos al haber podido ajustar cuentas pendientes ☺. III Aniversario Gusenet Santa Pola, 23, 24 y 25 de noviembre Como en ocasiones anteriores, hemos preparado una combinación de actividades técnicas con actividades lúdicas. A continuación tenéis la agenda prevista. Tened en cuenta que podéis elegir las actividades que queráis, desde venir solos al evento e iros al finalizar, hasta pasar un fin de semana completo con la familia. Agenda Viernes Llegada y registro en el hotel (para los que quieran venirse con tiempo). Sobre las 21:00, salida de cañas y cena por Santa Pola. Paralelamente a las actividades técnicas, habrá una visita organizada a las salinas de Santa Pola para los acompañantes. Sábado Lugar: Baluarte del castillo. 09:00 Desayuno, inscripción y registro. 10:00 Desarrollo Windows con Visual Studio 2008. El Guille (Solid Quality Mentors). 12:15 Desarrollo Web con Visual Studio 2008. David Salgado (Microsoft Ibérica). 14:00 Cierre de la mañana y salida al restaurante. Comida todos juntos. 16:30 Mesa redonda sobre el grupo de usuarios. Ofertas de empleo y otros. 18:30 Cierre, entrega de goodies y retirada al hotel. 20:30 Salida de cañas y cena. Domingo 10:00 Salida hacia la isla de Tabarca y comida ya de vuelta en Santa Pola. Hotel Hotel Patilla, Santa Pola. Habitación doble, sin desayuno: 62€ (IVA incluido) Más información y detalles actualizados en www.gusenet.com. Salvador Ramos - Gusenet eventos.eventos.eventos.eventos.eventos.eventos << dnm.comunidad.net October .NET Conference <<dotNetManía • 56 Chad Hower también presentó dos trabajos, “Una introducción seria a WPF”, sobre los fundamentos de esta nueva tecnología de presentación, y “Silverlight 1.1”, en la que nos adelantó las Durante los días 15 y 16 de octubre se llevó a principales posibilidades que pondrá a cabo en las instalaciones del Hotel Siken “Puerta nuestra disposición la de Málaga” de esa ciudad andaluza el evento próxima versión de Silverlight. October .NET Conference, organizado por el • Tanto las ponencias de .NET User Group de Málaga y patrocinada por Michael Li, “Defienda sus aplicaciones ASP. iMeta y MSDN, además de otras empresas. NET de los ocho ata ques más comunes” y “Escalabilidad de aplicaciones Contando un elenco de speakers realASP.NET” como las de Dino Espomente sobresaliente, que incluía tanto a sito, “ASP.NET para valientes” y “Por figuras de relieve internacional como qué el renderizado parcial no es Neal Ford (ThoughtWorks), Chad AJAX”, y las de José Manuel Alarcón, Hower (Woo-Hoo), Michael Li (Info“Diseño avanzado de controles con Can), Hadi Hariri (AtoZed Software) o ASP.NET 2.0” y “Creación de páginuestro columnista habitual Dino Esponas modulares personalizables sito (Solid Quality Mentors), como a mediante WebParts” estuvieron dediponentes españoles de amplio reconocicadas a temas relacionados con el desamiento como José Manuel Alarcón rrollo de aplicaciones Web ASP.NET. (CampusMVP), Guillermo “El Guille” • Otro tema que estuvo ampliamente Som (Solid Quality Mentors), David representado en la conferencia fue el Salgado (Microsoft), Martín López relacionado con las novedades que (Adesis Netlife) y Unai Zorrilla y un serintroducirán las próximas versiones de vidor (Plain Concepts), el evento atrajo .NET Framework y Visual Studio en la atención de más de 150 entusiastas parlos lenguajes de programación y las ticipantes, que disfrutaron de 20 presenposibilidades que ofrecerá la tecnotaciones relacionadas con los más diverlogía LINQ, donde “El Guille” y un sos aspectos del desarrollo de aplicacioservidor presentamos (uno orientánnes para la plataforma .NET: dose a Visual Basic 9.0 y el otro, a C# • Neal Ford presentó dos ponencias, 3.0), dos ponencias cada uno: una “Polyglot programming”, sobre la dedicada a las novedades en el lenguaje conveniencia y las vías para combinar y la otra, a la utilización desde él de la durante el desarrollo diferentes paratecnología LINQ. digmas y lenguajes de programación • Hadi Hariri también presentó dos trapara aprovechar las ventajas relativas bajos, “Lo que todo desarrollador debe de cada uno, y “Ruby on Rails en conocer sobre MS Build”, sobre las .NET”, una introducción al desarroposibilidades que ofrece esta potente llo de aplicaciones utilizando este cada herramienta, y “Uso del CLR desde vez más popular marco de trabajo. • • • • SQL Server”, sobre cómo aprovechar las ventajas del código .NET dentro de SQL Server 2005. Otro invitado extranjero, Pawel Glowacki de CodeGear, mostró las posibilidades que ofrece la recién estrenada versión de RAD Studio 2007 (cuyo nombre interno era Highlander), que permite desarrollar aplicaciones tanto para Win32 (incluyendo Vista) como para .NET en diversos lenguajes de programación. David Salgado, de Microsoft, en su ponencia “Depuración en entornos de producción”, ofreció múltiples recomendaciones prácticas en relación con un aspecto tan esencial en nuestro trabajo diario como es la depuración. Unai Zorrilla deleitó a los asistentes con sus profundos conocimientos sobre Windows Workflow Foundation y mostró las ventajas que ofrece este potente framework para modelar procesos de negocio. Por último, en otra ponencia muy interesante que contó con un alto nivel de participación, Martín López mostró las posibilidades que se abren ante los desarrolladores que utilicen SharePoint gracias a la versión 3.0 de Windows SharePoint Services. Todas las presentaciones están disponibles para su descarga en el sitio Web del evento, http://www.octoberconference.net/content. Resumiendo, quiero plasmar aquí la impresión que comparto con otros ponentes y asistentes con los que tuve la oportunidad de charlar de que el evento fue un rotundo éxito en todos los sentidos y manifestar nuestro sincero agradecimiento a Málaga .NET User Group y en particular a su coordinador, Hadi Hariri, por todo el trabajo desarrollado para hacerlo posible. Esperamos que el año próximo se repita. Octavio Hernández biblioteca.net 3D Programming for Windows Charles Petzold Editorial: Microsoft Press Páginas: 448 Publicado: julio 2007 ISBN: 978-0735623941 Idioma: inglés Tener un libro de Petzold sobre el escritorio es, para muchos de nosotros (al menos, los de mayor edad), algo que con los años se ha hecho tradicional. Esta vez el Maestro vuelve con un tema relacionado con la programación de interfaces de usuario, hasta cierto punto continuación de su anterior obra, “Applications = Code + Markup”, y no nos defrauda en modo alguno. Asumiendo del lector el dominio previo de los fundamentos de programación para Windows Presentation Foundation, este libro presenta los conocimientos necesarios para trabajar con mallas geométricas (la base sobre la que se apoyan los gráficos 3D en WPF) y muestra cómo utilizar los recursos que este componente esencial de .NET 3.0 pone a disposición de los desarrolladores para crear de una manera efectiva aplicaciones que incluyan gráficos tridimensionales. Modelando procesos de negocio con Workflow Foundation Unai Zorrilla Castro Editorial: Krasis Press Páginas: 242 Publicado: octubre 2007 ISBN: 978-8493548926 Idioma: castellano novedades Unai Zorrilla es ponente habitual en los eventos que realiza Microsoft por toda España, y una de las personas con conocimientos más amplios y profundos acerca de la plataforma .NET y todo lo que la rodea que conozco. En este, su primer libro, Unai nos ofrece un excelente acercamiento a las posibilidades que ofrece Workflow Foundation, otro de los nuevos pilares introducidos con .NET Framework 3.0, para modelar los más diversos procesos de negocio. La primera parte del libro presenta el concepto de flujo de trabajo e introduce las piezas clave de la infraestructura asociada. A continuación se describen detalladamente, con ayuda de ejemplos, las actividades o pasos de los flujos de trabajo que WF provee por defecto, así como los servicios que ofrece (persistencia, planificación y otros). Varios capítulos posteriores, de un corte más avanzado, se refieren a la creación de actividades personalizadas, con ejemplos completos de proyectos de este tipo. Finalmente, el libro dispone de dos apéndices, dedicados respectivamente a la implementación de máquinas de estados dentro de WF y a las novedades que introducirán en esta tecnología .NET Framework 3.5 y Visual Studio 2008 en un futuro ya muy próximo. Recomendado tanto a aquellos que deseen familiarizarse con las posibilidades que ofrece WF como a quienes deseen comprender las interioridades de su funcionamiento. Programación Web 2.0 Eric Van der Vlist, Danny Ayers, Eric Bruchez y otros. Editorial: Anaya Multimedia/Wrox. Páginas: 560. Publicado: septiembre 2007. ISBN: 978-84-415-2252-7.Idioma: castellano. Desarrollo de aplicaciones Web Ralph Moseley. Editorial: Anaya Multimedia/Wrox. Páginas: 400. Publicado: septiembre 2007. ISBN: 978-84-415-2265-7. Idioma: castellano. TEXTO: OCTAVIO HERNÁNDEZ Marino Posadas Windows XP Service Pack 3, con muchas novedades de seguridad Según un análisis realizado por NeoSmart, el nuevo Service Pack 3 para XP no solo incluirá nuevos drivers, optimización y arreglos de bugs, sino muchas características innovadoras, en especial respecto al tema de la seguridad, con la ventaja de un rendimiento excelente, según los analistas. Una de ellas, Network Access Protection (NAP), impide el acceso a Windows Server 2008 sin pasar un mínimo de garantías de seguridad, respecto a la presencia de otros SP y ciertos parches, deshabilitando el acceso hasta que se produzca la actualización. Otro cambio importante sería la inclusión del nuevo Kernel Mode Cryptographic Module (KMCM), que permite a los administradores implementar una segunda capa de políticas para comunicaciones cifradas, asumiendo que los algoritmos Triple-DES estén accesibles a través del kernel. Otras mejoras se refieren a la pila de direcciones IP. Independientemente de esto, también existirán algunas mejoras y novedades operativas, algunas de ellas fruto de la experiencia obtenida en la implantación de Windows Vista: no será necesario teclear la clave del producto durante el proceso de instalación, existirán varias mejoras en el rendimiento y fiabilidad, así como arreglos y actualizaciones de drivers. noticias.noticias.noticias <<dotNetManía Google propone la creación de Multiversos Virtuales 58 A pesar de que el furor creado por “Second Life” parece remitir, los mundos 3D están de moda, y lo van a estar más aún si tenemos en cuenta esta propuesta de Google, Arquitectural Wonders, que permitirá a los usuarios crear “universos interactivos 3D on-line”. Se podrá partir de una base geográfica como Google Maps, e incluir elementos de un repositorio de modelos 3D. Por ejemplo, podremos partir de un terreno, como un barrio de nuestra ciudad, incluir modelos de edificios en 3D, e incluso crear los propios del usuario para generar un auténtico mundo virtual por donde pasear e interactuar, pudiendo añadir toda clase de contenidos personalizados. Para más información, ver el artículo de Daniel Terdiman en CNet News “Google tools to power virtual worlds” (http://www.news.com/Google-tools-to-powervirtual-worlds/2100-1043_3-6212325.html?tag=nefd.lede). noticias.noticias.noticias desván documentos en la red Introduction to ASP.NET 2.0 SqlDataSource Control de Michael Youssef. En el sitio Web http://www.aspfree.com/c/a/ ASP.NET/Introduction-to-ASPNET-20-SqlDataSource-Control encontramos este interesante artículo sobre el control SqlDataSource y su forma de utilizarlo en ASP.NET. Pero no es el único artículo que recomendamos de este sitio. También es notable el de Jagadish Chaterjee, “Developing Business Logic using the WCF Service Library with VS2K8 and ASP.NET 3.5”, disponible en http://www.aspfree.com/c/a/ASP.NET/Developing-Business-Logic-using-theWCF-Service-Library-with-VS2K8-and-ASPNET-35/. Petzold Book Blog. Charles Petzold, uno de nuestros más admirados autores de literatura técnica, opina (¡y cómo!) sobre el oficio de escribir y las penurias, dificultades y escasa remuneración y recompensas de este noble arte. Ya hemos recomendado su blog en esta sección, pero quienes se planteen algo así deberían echar un vistazo a sus consejos en “Hard Work, No Pay: What's the Point?”, disponible en http://www.charlespetzold.com/blog/2007/ 10/081247.html Humor (informático) en la red Inauguramos esta sección con unos toques de humor —que la informática da para todo— que hemos visto navegando por ahí. El primero es un documento sobre la salud de los programadores (o la falta de ella, debido a nuestra actividad). Se trata de “Geek Diet and Exercise Programs”, disponible en Coding Horrors (http://www.codinghorror.com/ blog/archives/000970.html). El otro es un chiste gráfico muy bueno. A los especialistas en seguridad les encantará… (http://imgs.xkcd.com/comics/exploits_of_a_mom.png). utilidades del mes Pipelines by TenFiftyTwo. Se trata de una utilidad para programar modificaciones individuales o masivas en ficheros de texto. Mediante una sintaxis muy simple, podemos programar estas modificaciones a voluntad, tanto en ficheros locales como en red. Es una utilidad gratuita disponible en http://www.tenfiftytwo.co.uk/pipelines. En la página del sitio disponemos de una detallada explicación de uso. Script Converter. Interesante utilidad que permite convertir código HTML en equivalentes en otros lenguajes, como PHP, ASP, JSP, Perl, Python y VBScript. Se puede descargar del sitio http://www.ethicdevelopment.com/freesoftware, donde el visitante encontrará también otras utilidades de interés.