# WPF para el tipo Web una historia de dolor, desesperación y aprendizaje

<datetime class="hidden">2010-08-16T00:00</datetime>

<!-- category -- mostlylucidcouk, Imported -->
Bueno, OK estoy sobre dramatizarlo un poco :) Como usted puede haber notado por mi [Último puesto](http://mostlylucid.net/archive/2010/01/07/1331.aspx) He comenzado a trabajar en algunas cosas que están fuera de mi zona de confort. Recientemente me uní a una empresa llamada [cozwecan](http://blog.cozwecan.com), trabajando para un tipo llamado [@RobertTheGrey](http://twitter.com/robertthegrey), el primer proyecto en el que estamos trabajando es un sitio de ventas de fotos... usted sabe algo así como Getty Images / Smugmug / Flickr excepto para las ventas directas de fotógrafos-clientes. [desarrollador a tiempo completo](http://blog.cozwecan.com/2010/01/year-and-team-members.html) (¡hasta ahora!) Me han encargado la construcción de la gran mayoría de las aplicaciones (ya sean web, escritorio, etc.) que necesitamos para que el negocio funcione.

Obviamente uno de los bits más críticos para un sitio de comercio electrónico es material para vender! En nuestro caso este es un stock de fotografías de muy alta calidad tomadas por fotógrafos profesionales de todo el mundo. Primera pregunta; ¿cómo conseguimos estas imágenes en el sitio, y cómo dejamos que los fotógrafos agreguen información a sus fotografías para permitir que sean encontradas por la gente que quiere comprarlas? Bueno, ese es el primer problema que me han pedido que resuelva!

Al trabajar en los requisitos, rápidamente se hizo evidente que esto tenía que ser una aplicación de escritorio, que necesitaba para permitir operaciones en fotografías multi-megabyte, necesario para permitir ‘etiquetado’ fuera de línea. Especialmente durante la carga inicial, hay 10-100s de miles de imágenes trabajando en... forzando esto para estar en línea añade un retraso desagradable y hace que la tarea un dolor en el culo para completar. Sobre todo, tenía que ser fiable!  
Ahora, en mi cabeza de desarrollo esto llevó a una serie de decisiones sobre las características que la aplicación necesitaría para apoyar:

1. Interfaz de usuario intuitiva y receptiva; los usuarios no son necesariamente expertos en computación, necesitan usar metáforas de interfaz de usuario obvias y hacerlo lo suficientemente rápido como para ser agradable de usar.
2. Necesita hacer un poco de procesamiento de imágenes fuera de línea; estamos planeando alojar esto en la nube....CPU tiempo cuesta dinero en efectivo :)
3. Debe funcionar fuera de línea
4. Necesita ser construido alrededor de la carga confiable de archivos multi-meg (a través de anchos de banda de conexión potencialmente pequeños).

Por lo tanto, estas son las necesidades básicas  técnicamente esto me lleva a algunas opciones de tecnología:

1. Multi-hilo / concurrencia (como con cualquier aplicación de escritorio real) es clave como es una tecnología de escritorio decente
2. Ver arriba, tenemos que ‘ver’ para los nuevos archivos que llegan, hacer miniaturas de forma transparente, etc
3. Por lo tanto, necesita algún tipo de persistencia ... junto con otros requisitos de la aplicación no-núcleo (por ejemplo, etiquetado) es probable que necesitemos un poco, ligero DB.
4. Necesita cargas basadas en ‘bloques’ con reintento / verificación de elementos subidos

Por suerte he sido un dev por un buen tiempo y tenía al menos alguna idea de por dónde empezar  el mayor desafío sería aprender!  
En primer lugar, la plataforma de escritorio obvio de jour es WPF... esto es en esencia el reemplazo actual para WinForms, es muy personalizable, pero tiene una curva de aprendizaje algo infamemente empinada. He gastado un poco de dinero en [libros](http://www.amazon.co.uk/Pro-WPF-2008-Presentation-Foundation/dp/1590599551/ref=sr_1_1?ie=UTF8&s=books&qid=1264019165&sr=8-1) el [WPF](http://www.amazon.co.uk/WPF-Action-Visual-Studio-2008/dp/1933988223/ref=sr_1_1?ie=UTF8&s=books&qid=1264019184&sr=8-1), siguió todas las presentaciones del PDC, podcasts etc... en el tema. Después de 3 semanas finalmente me estoy poniendo cómodo con él...  
En segundo lugar, necesitaba un DB...necesario para ser ligero, necesario para apoyar a LINQ (qué, me gusta LINQ :)), necesitaba para soportar algún tipo de tecnología ORM.  
La elección más obvia fue [SQL Server Compact Edition](http://www.microsoft.com/Sqlserver/2008/en/us/compact.aspx), una especie de servidor SQL de corte que es ideal para aplicaciones de escritorio ... sin embargo ... no pude conseguir que funcione! Resulta que es muy sensible a las versiones de varios componentes instalados . Esto era una luz roja para mí ... Necesitaba que la DB para ser lo más independiente posible ! Eso dejó una opción obvia :  
SQLite, desde el sitio web:

‘SQLite es una biblioteca de software que implementa un [autocontenido](http://www.sqlite.org/selfcontained.html), [serverless](http://www.sqlite.org/serverless.html), [configuración cero](http://www.sqlite.org/zeroconf.html), [transaccional](http://www.sqlite.org/transactional.html) Motor de base de datos SQL. SQLite es el [Desplegados más ampliamente](http://www.sqlite.org/mostdeployed.html) Motor de base de datos SQL en el mundo. El código fuente para SQLite está en el [dominio público](http://www.sqlite.org/copyright.html).’

Perfecto! Para el apoyo de .NET, utilicé el [Proveedor SQLite ADO.NET](http://sqlite.phxsoftware.com/) (hay un [C# sólo puerto de SQLite](http://code.google.com/p/csharp-sqlite/), pero es más lento y no es necesario para mi escenario. El despliegue es, literalmente, incluir una referencia y crear un archivo... ahora tiene una limitación bastante grave que voy a cubrir más tarde :)

Mis opciones ORM eran [nHibernato](https://www.hibernate.org/343.html) o EF... hay soporte para SQLite en ambas plataformas.  
Ahora, he usado nHibernate para un [proyecto reciente](http://www.trainyourbusinessbrain.com/business-brain-training/) (descargo de responsabilidad, me fui bastante temprano en el proyecto :)) y una de las cosas que me molestaba era el gran número de dependencias, y la aparente fragilidad a través de las versiones de estas dependencias. Para una aplicación de escritorio de bajo impacto, esto era una huella demasiado grande y un riesgo demasiado grande.  
Esto me dejó con una opción[Marco de las entidades](http://en.wikipedia.org/wiki/ADO.NET_Entity_Framework). Ahora, esta no fue una elección fácil... he usado la FE en el pasado (pequeña con ella) y la encontré una experiencia dolorosa... (otros también tienen que [llevó a esto](http://efvote.wufoo.com/forms/ado-net-entity-framework-vote-of-no-confidence/)Ahora, no me estoy metiendo en la política detrás de la elección de MS para empujar a EF (y sí, fue una opción política), pero V1 es bastante buggy, y para mí al menos bastante antinatural. Esto llevó a mi segundo aprendizaje ‘oportunidad’... Marco de Entidad Maestra! Al escribir esto, son literalmente 2 horas desde que trabajé mi último poco de dolor de EF... lo que llevó a este modelo:

[![Capturar](Capture_thumb_3C08CB45.jpg "Capture")](http://www.mostlylucid.net/Images/WPFfortheWebguyastoryofpaindespairandlea_126C4/Capture.jpg)

Parece simple... bueno, sí... pero me ha llevado AGES para llegar a esto... Conozco SQL muy bien, C# REALMENTE bien, pero la mezcla de conceptos oscuros, documentación pobre y mensajes de error obfuscado hace que trabajar / aprender EF manera demasiado difícil! EF 4 (en VS 2010 / .NET 4) mejora esto... y no puede llegar muy pronto!

Por lo tanto, eso ha funcionado  Espero :)

Por lo tanto, he mencionado anteriormente que SQLite tiene un poco de un problema... no le gusta la concurrencia. Bueno, eso es una exageración... es un sistema de escritura simple de múltiples lecturas. Una vez que te das cuenta de que es un problema es bastante fácil de resolver. Uso este patrón:

```
public static void AddRange(IEnumerable<UploadFile> files)
        {
            using (PixEntities ent = new PixEntities())
            {
                using (new ReadLock(entityLock))
                {

                    using (new WriteLock(entityLock))
                    {
                        foreach (var file in files)
                        {
                            ent.AddToUploadFiles(file);
                            ent.SaveChanges();
                        }
                    }
                }
            }
        }
```

```
 
```

Como has visto, esto tiene dos elementos principalesUtilizo un ReadLock (donde normalmente leería \*a partir de\* el DB, en este caso umm, yo no :)) entonces utilizo un WriteLock para envolver cualquier operación donde escribo a la DB. El observador también notará lo que parece un error... Lo hago ent.SaveChages() para cada iteración del bucle... esto es otra peculiaridad SQLite, no soporta actualizaciones por lotes... y da un error extraño sobre bloqueos de archivos si intentas :)

La pequeña clase de bloqueo que uso es de [aquí](http://dotnetcommandos.com/blogs/brianr/archive/2008/09/26/thread-safe-dictionary-in-net.aspx), pero lo he puesto a continuación para su uso  de todos modos, esto es parte de varias partes de mis experiencias en el trabajo en [cozwecan](http://blog.cozwecan.com)¡Quédate afinado!

```
  public static class Locks
    {
        public static void GetReadLock(ReaderWriterLockSlim locks)
        {
            bool lockAcquired = false;
            while (!lockAcquired)
                lockAcquired = locks.TryEnterUpgradeableReadLock(1);
        }

        public static void GetReadOnlyLock(ReaderWriterLockSlim locks)
        {
            bool lockAcquired = false;
            while (!lockAcquired)
                lockAcquired = locks.TryEnterReadLock(1);
        }

        public static void GetWriteLock(ReaderWriterLockSlim locks)
        {
            bool lockAcquired = false;
            while (!lockAcquired)
                lockAcquired = locks.TryEnterWriteLock(1);
        }

        public static void ReleaseReadOnlyLock(ReaderWriterLockSlim locks)
        {
            if (locks.IsReadLockHeld)
                locks.ExitReadLock();
        }

        public static void ReleaseReadLock(ReaderWriterLockSlim locks)
        {
            if (locks.IsUpgradeableReadLockHeld)
                locks.ExitUpgradeableReadLock();
        }

        public static void ReleaseWriteLock(ReaderWriterLockSlim locks)
        {
            if (locks.IsWriteLockHeld)
                locks.ExitWriteLock();
        }

        public static void ReleaseLock(ReaderWriterLockSlim locks)
        {
            ReleaseWriteLock(locks);
            ReleaseReadLock(locks);
            ReleaseReadOnlyLock(locks);
        }

        public static ReaderWriterLockSlim GetLockInstance()
        {
            return GetLockInstance(LockRecursionPolicy.SupportsRecursion);
        }

        public static ReaderWriterLockSlim GetLockInstance(LockRecursionPolicy recursionPolicy)
        {
            return new ReaderWriterLockSlim(recursionPolicy);
        }
    }

    public abstract class BaseLock : IDisposable
    {
        protected ReaderWriterLockSlim _Locks;

        public BaseLock(ReaderWriterLockSlim locks)
        {
            _Locks = locks;
        }

        public abstract void Dispose();
    }

    public class ReadLock : BaseLock
    {
        public ReadLock(ReaderWriterLockSlim locks)
            : base(locks)
        {
            Locks.GetReadLock(this._Locks);
        }

        public override void Dispose()
        {
            Locks.ReleaseReadLock(this._Locks);
        }
    }

    public class ReadOnlyLock : BaseLock
    {
        public ReadOnlyLock(ReaderWriterLockSlim locks)
            : base(locks)
        {
            Locks.GetReadOnlyLock(this._Locks);
        }

        public override void Dispose()
        {
            Locks.ReleaseReadOnlyLock(this._Locks);
        }
    }

    public class WriteLock : BaseLock
    {
        public WriteLock(ReaderWriterLockSlim locks)
            : base(locks)
        {
            Locks.GetWriteLock(this._Locks);
        }

        public override void Dispose()
        {
            Locks.ReleaseWriteLock(this._Locks);
        }
    }
```