.NET 4.0 y Arquitecturas orientadas al Dominio

El miércoles 24 de Marzo de 2010 se celebra en las oficinas de Microsoft España de Madrid un evento para arquitectos  y desarrolladores en el que como reza el título trataremos de dar una visión profunda de las posibilidades de .NET 4.0 para la creación de arquitecturas basadas en el dominio. La verdad es que llevamos mucho tiempo para la preparación de este evento, puesto que no es un evento tradicional en el que unos cuantos ponentes presentamos unas ppt y contamos nuestra visión del mundo. En este evento nos mojaremos de verdad , y lo haremos porque presentaremos un proyecto completo basado en la ola 4.0 y además acompañaremos el mismo con documentación abundante…, muchos se llevarán una agradable sorpresa.. sobre todo los que vengan :-)

 

Espero realmente que os animéis a llenar el evento, el registro aquí, y, para los que no vengáis que colaboréis con la solución, que estara disponible como código fuente abierto para la comunidad, con vuestros comentarios, sugerencias de código, mejoras, problemas etc etc…

 

Saludos

Unai

Publicado por Unai | 8 comment(s)

EF 4.0: ¿Que artefacto de generación seleccionamos?

Como seguramente todos sabréis una de las ventajas mas acogidas dentro de la comunidad con respecto a EF 4.0 está precisamente en la capacidad de adaptar como queremos que se genere tanto el código de nuestras entidades como el código del contexto de trabajo de EF. Aunque hasta la llegada de VS 2010 por defecto solamente teníamos la opción de generación  de clases prescriptivas, en cada una de las CTP de EF Feature Pack veíamos como disponíamos de  opciones para seleccionar nuevos artefactos como la generación ,POCO ( Plain Old CLR Objects ) o STE ( Selft Tracking Entities ) por medio de plantillas T4 que podríamos llegar a adaptar para satisfacer nuestras necesidades, por muy rebuscadas que fueran. Con el  reciente lanzamiento de VS 2010 RC la plantilla de generación STE la tenemos por defecto como opción, sin tener  que recurrir a la instalación de Feature Pack, por su parte para la generación de clases POCO Microsoft pone a nuestra disposición estas plantillas por medio de una extensión accesible desde el Extension Manager de VS 2010.

La pregunta realmente para muchos consiste en saber ahora que artefacto de generación seleccionar, cuando decantarnos por objetos POCO, STE o la generación por defecto de clases prescriptivas. Aunque la respuesta no siempre es sencilla quizás este gráfico, salido de Danny Simmons, os resulta atractivo para hacer una valoración respecto a esta selección y aspectos arquitectónicos dentro de la solución, aspectos como integración de clientes no .NET, facilidad en el desarrollo etc…

 

ee335715_simmons_figure1(en-us)

En poco tiempo espero escribir mas largo y tendido sobre este tema …

 

Saludos

Unai

Publicado por Unai | con no comments

EF 4 – Features CTP 2 y VS2010 RC

Para los que estéis probando EF 4 Features CTP tened en cuenta los cambios en VS2010 y la compatibilidad de estas features en la nueva versión de Visual Studio. Si estais usando STE ( Selft Tracking Entities ) ahora están ‘out-of-box’ en VS 2010 RC y por lo tanto no necesitais instalar Features CTP, que además os fastidiaría los nuevos ttinclude y por lo tanto no serían compatibles. Para los que os interesen las plantillas de POCO estas estarán disponibles como extension a VS y por lo tanto descargables automáticamente desde el Extensions Manager de Visual Studio ( tampoco estarán en EF Features ).

Por lo tanto, que queda en EF FEATURES?? pues solamente la posibilidad de hacer ‘Code Only’…

 

 

Espero que os sirva de interes y que no tengais que reparar la instalación de la RC como un servidor…

 

Saludos

Unai

Publicado por Unai | con no comments

Entity Framework y Specification Pattern

Introducción

Hace ya unos cuantos dias que vengo dándole vueltas a la cabeza para escribir un post sobre especificaciones y como construirlas sobre la infraestructura de Entity Framework, aunque en realidad sería igual sobre cualquier elemento IQueryable, aunque con alguna nota de implementación. Pensando en esta entrada no sabía si comenzar con una explicación sobre este patrón o ceñirme directamente a una posible implementación,  puesto que, me parece magnifica la documentación sobre el mismo que Fowler y Evans tienen en el documento de referencia. Al final, y despues de darle muchas vueltas me quedaré entre medias puesto que creo que es necesario realizar una pequeña introducción, aunque aconsejo encarecidamente desde aquí leerse el documento anterior si nunca ha visto información acerca de este patrón.

Sintetizándolo, aunque sea demasiada síntesis, podríamos decir que el patrón espeficación trata de lograr una separación entre la sentencia que unos objetos deberían de cumplir y el objeto que realiza la selección. Por ejemplo una especificación podría describir “los clientes que pertenecen a una determinada situación geográfica” pero no saber nada acerca de como realizar dicha selección. En definitiva no se trata nada más y nada menos que agregar una responsabilidad desacoplada de los objetos de dominio que la usan.

Este patrón es muy escuchado y usado en arquitecturas basadas en modelos de dominio y puesto en práctica en muchas arquitecturas para definir “criterios” de selección. Basándonos en la definición formal de este patrón, mostrada en la Figura 1, podríamos ver a primeras que una implementación de este patrón trabajando con IQueryable no tendría mucho sentido.

 

Specification_UML

La razón principal de la afirmación anterior viene de la propia definición del patron, la cual implica trabajar con objetos directamente en memoria puesto que el método IsSatisfiedBy tomaría una instancia del objeto en el cual queremos comprobar si cumple un determinado criterio y devolver true o false según se cumpla o no, algo que por supuesto no deseamos por la sobrecarga que esto implicaría. Por todo esto podríamos modificar un poco nuestra definición de Specification para que en vez de devolver un booleano negando o afirmando el cumplimiento de una especificación determinada podríamos devolver una “expression” con el criterio a cumplir. En el siguiente fragmento de código tendríamos un esqueleto de nuestro contrato base con esta ligera modificación.

    /// <summary>
    /// Base contract for Specification pattern, for more information
    /// about this pattern see http://martinfowler.com/apsupp/spec.pdf
    /// or http://en.wikipedia.org/wiki/Specification_pattern.
    /// Really this is variant implementation for add feature of linq and
    /// lambda expression into this pattern.
    /// </summary>
    /// <typeparam name="TEntity">Type of entity</typeparam>
    public interface ISpecification<TEntity>
        where TEntity : class,new()
    {
        /// <summary>
        /// Check if this specification is satisfied by a 
        /// specific expression lambda
        /// </summary>
        /// <returns></returns>
        Expression<Func<TEntity, bool>> SatisfiedBy();
    }

Profundizando

Llegados a este punto podríamos decir que ya tenemos la base y la idea de lo que queremos construir, ahora, solamente falta seguir las propias normas y guias de este patrón empezándonos a crear nuestras especificaciones directas o “hard coded specifications” y nuestras especificaciones compuestas, al estilo And, Or …

Según avanzas en esta aproximación uno se da cuenta de que tendrá que realizar un buen esfuerzo de trabajo con árboles de expresiones, algo en el que no todo el mundo está muy puesto, y a lo que se le tiene algo de miedo, del que uno se puede curar leyendo el excelente libro de mi amigo y colega Octavio :-)

Mi primera intentona, y fallida a mi pesar, era similar a lo siguiente:

    /// <summary>
    /// A logic AND Specification
    /// </summary>
    /// <typeparam name="T">Type of entity that check this specification</typeparam>
    public class AndSpecification<T>
       : CompositeSpecification<T>
       where T : class,new()
    {
        #region Members

        private ISpecification<T> _RightSideSpecification = null;
        private ISpecification<T> _LeftSideSpecification = null;

        #endregion

        #region Public Constructor

        /// <summary>
        /// Default constructor for AndSpecification
        /// </summary>
        /// <param name="leftSide">Left side specification</param>
        /// <param name="rightSide">Right side specification</param>
        public AndSpecification(ISpecification<T> leftSide, ISpecification<T> rightSide)
        {
            if (leftSide == (ISpecification<T>)null)
                throw new ArgumentNullException("leftSide");

            if (rightSide == (ISpecification<T>)null)
                throw new ArgumentNullException("rightSide");

            this._LeftSideSpecification = leftSide;
            this._RightSideSpecification = rightSide;
        }

        #endregion

        #region Composite Specification overrides

        /// <summary>
        /// Left side specification
        /// </summary>
        public override ISpecification<T> LeftSideSpecification
        {
            get { return _LeftSideSpecification; }
        }

        /// <summary>
        /// Right side specification
        /// </summary>
        public override ISpecification<T> RightSideSpecification
        {
            get { return _RightSideSpecification; }
        }

        /// <summary>
        /// <see cref="Microsoft.Samples.NLayerApp.Domain.Core.Specification.ISpecification"/>
        /// </summary>
        /// <returns><see cref="Microsoft.Samples.NLayerApp.Domain.Core.Specification.ISpecification"/></returns>
        public override Expression<Func<T, bool>> SatisfiedBy()
        {
            Expression<Func<T, bool>> left = _LeftSideSpecification.SatisfiedBy();
            Expression<Func<T, bool>> right = _RightSideSpecification.SatisfiedBy();

            InvocationExpression invokedExpr = Expression.Invoke(right, left.Parameters.Cast<Expression>());
            return Expression.Lambda<Func<T, bool>>(Expression.AndAlso(left.Body, invokedExpr), left.Parameters);
        }

        #endregion
    }

Os podreís imaginar que la especificacion OR era similar a esta, simplemente modificando Expression.AndAlso por Expression.Or. Todo funcionaba de maravilla en mis test contra mi mock de los ObjectSet de Entity Framework hasta que pasando las pruebas de integración me di cuenta que el QueryProvider de Entity Framework no soportaba el método Invoke, al contrario que un QueryProvider sobre objetos en memoria, y que por lo tanto esta forma no me servía :-(.

Aunque ahora mismo pondré la solución fijaros como, de una forma elegante, manteniendo el principio de separación de responsabilidades y dejando un concepto de negocio como es un tipo especial de búsqueda perfectamente explícito, se podrían declarar especificaciones como la siguiente

NOTA: Seguramente que alguna vez ha pensado como hacer consultas distintas en función de parámetros usando conjunciones o disjunciones de expressiones, pues esta es una posible solución.

 

    /// <summary>
    /// AdHoc specification for finding orders
    /// by shipping values
    /// </summary>
    public class OrderShippingSpecification
        :Specification<Order>
    {
        #region Members

        string _ShippingName = default(String);
        string _ShippingAddress = default(String);
        string _ShippingCity = default(String);
        string _ShippingZip = default(String);

        #endregion

        #region Constructor

        /// <summary>
        /// Default constructor for this specification
        /// </summary>
        /// <param name="shippingName">Shipping Name or null for not include this value in search</param>
        /// <param name="shippingAddress">Shipping Address or null for not include this vlaue in search</param>
        /// <param name="shippingCity">Shipping City or null for not include this value in search</param>
        /// <param name="shippingZip">Shipping Zip or null for not include this value in search</param>
        public OrderShippingSpecification(string shippingName,string shippingAddress,string shippingCity,string shippingZip)
        {
            _ShippingName = shippingName;
            _ShippingAddress = shippingAddress;
            _ShippingCity = shippingCity;
            _ShippingZip = shippingZip;
        }

        #endregion

        #region Specification Overrides

        /// <summary>
        /// <see cref=" Microsoft.Samples.NLayerApp.Domain.Core.Specification.Specification{TEntity}"/>
        /// </summary>
        /// <returns><see cref=" Microsoft.Samples.NLayerApp.Domain.Core.Specification.Specification{TEntity}"/></returns>
        public override System.Linq.Expressions.Expression<Func<Order, bool>> SatisfiedBy()
        {
            Specification<Order> beginSpec = new TrueSpecification<Order>();

            if (_ShippingName != null)
                beginSpec &= new DirectSpecification<Order>(o =>o.ShippingName!=null &&  o.ShippingName.Contains(_ShippingName));

            if (_ShippingAddress != null)
                beginSpec &= new DirectSpecification<Order>(o => o.ShippingAddress !=null && o.ShippingAddress.Contains(_ShippingAddress));

            if (_ShippingCity != null)
                beginSpec &= new DirectSpecification<Order>(o => o.ShippingCity != null && o.ShippingCity.Contains(_ShippingCity));

            if (_ShippingZip != null)
                beginSpec &= new DirectSpecification<Order>(o => o.ShippingZip != null && o.ShippingZip.Contains(_ShippingZip));

            return beginSpec.SatisfiedBy();

        }

        #endregion
    }

Una posible solución para especificaciones And y OR

Os podreís imaginar que seguramente existe más de una aproximación para este tema y que yo probablemente me haya decantado por una aproximación algo dura, pero la verdad es que me parecía la más adecuada. Releyendo algún post sobre el tema me acorde de la serie que Matt Warren tenía sobre el tema y como hacía uso del patrón Visitor para evaluar las expresiones, ExpressionVisitor. Además, navegando, me encontré con un ejemplo aceptable que resolvía este tema en el blog de Colling Meek el cual fué la solución que adopte.

Dada la explicación, lo que necesitamos es la siguiente clase que nos haga una recomposición de las expressiones en vez de un InvocationExpression, esta clase de apoyo es la siguiente:

    /// <summary>
    /// Extension methods for add And and Or with parameters rebinder
    /// </summary>
    public static class ExpressionBuilder
    {
        public static Expression<T> Compose<T>(this Expression<T> first, Expression<T> second, Func<Expression, Expression, Expression> merge)
        {
            // build parameter map (from parameters of second to parameters of first)
            var map = first.Parameters.Select((f, i) => new { f, s = second.Parameters[i] }).ToDictionary(p => p.s, p => p.f);

            // replace parameters in the second lambda expression with parameters from the first
            var secondBody = ParameterRebinder.ReplaceParameters(map, second.Body);
            // apply composition of lambda expression bodies to parameters from the first expression 
            return Expression.Lambda<T>(merge(first.Body, secondBody), first.Parameters);
        }
        public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second)
        {
            return first.Compose(second, Expression.And);
        }
        public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second)
        {
            return first.Compose(second, Expression.Or);
        }

    }
 

La definición completa por lo tanto de una especificación And queda como sigue:

    /// <summary>
    /// A logic AND Specification
    /// </summary>
    /// <typeparam name="T">Type of entity that check this specification</typeparam>
    public class AndSpecification<T>
       : CompositeSpecification<T>
       where T : class,new()
    {
        #region Members

        private ISpecification<T> _RightSideSpecification = null;
        private ISpecification<T> _LeftSideSpecification = null;

        #endregion

        #region Public Constructor

        /// <summary>
        /// Default constructor for AndSpecification
        /// </summary>
        /// <param name="leftSide">Left side specification</param>
        /// <param name="rightSide">Right side specification</param>
        public AndSpecification(ISpecification<T> leftSide, ISpecification<T> rightSide)
        {
            if (leftSide == (ISpecification<T>)null)
                throw new ArgumentNullException("leftSide");

            if (rightSide == (ISpecification<T>)null)
                throw new ArgumentNullException("rightSide");

            this._LeftSideSpecification = leftSide;
            this._RightSideSpecification = rightSide;
        }

        #endregion

        #region Composite Specification overrides

        /// <summary>
        /// Left side specification
        /// </summary>
        public override ISpecification<T> LeftSideSpecification
        {
            get { return _LeftSideSpecification; }
        }

        /// <summary>
        /// Right side specification
        /// </summary>
        public override ISpecification<T> RightSideSpecification
        {
            get { return _RightSideSpecification; }
        }

        /// <summary>
        /// <see cref="Microsoft.Samples.NLayerApp.Domain.Core.Specification.ISpecification"/>
        /// </summary>
        /// <returns><see cref="Microsoft.Samples.NLayerApp.Domain.Core.Specification.ISpecification"/></returns>
        public override Expression<Func<T, bool>> SatisfiedBy()
        {
            Expression<Func<T, bool>> left = _LeftSideSpecification.SatisfiedBy();
            Expression<Func<T, bool>> right = _RightSideSpecification.SatisfiedBy();

            return (left.And(right));
         }

        #endregion
    }
 

¿Por dónde continuamos?

Dentro de la jerarquía de especificaciones que se propone en el documento de Eric y Fowler podemos encontrar desde la especificación Not hasta una base para LeafSpecifications que tendríamos que construir… es decir, un poquito más de curro para poder disfrutar…

 

Un resumen

La verdad espero que le encontréis utilidad a esta forma de implementar el patrón Specification, y por supuesto, si no lo conociais espero que os levante un poco la curiosidad y veais las bondades que este puede tener  dentro de vuestros desarrollos, por supuesto me encantaría ver vuestras opiniones al respecto y si realmente esta forma de encajar criterios es adecuado para vuestras soluciones…

 

Saludos

Unai Zorrilla

 

 

Publicado por Unai | 2 comment(s)

EF 4.0: POCO y proxies dinámicos

Para ser sinceros, entre el comienzo del nuevo libro de EF 4.0 y el papel en blanco que esto representa, no sabía por dónde empezar a escribir sobre EF. Son tantas y en mi opinión tan acertadas las nuevas novedades que no tenía ni idea del sitio en el que poner mi granito de arena.

Desde la primera versión, y por ahora única, de Entity Framework, muchas han sido las reclamaciones y recomendaciones de mejora que este framework ha recibido por parte de la comunidad, seguro muchos conocéis el famoso “Vote of No Confidence”  que algunos MVP firmaron y que tanto revuelo levanto en su tiempo. De todas las recomendaciones y falta de confianza en esta primera versión del producto una muy comentada fue el hecho de no poder disponer de objetos POCO dentro de nuestro modelos de dominio, algo imprescindible para incluir el concepto de ignorancia de la persistencia, PI ( Persistent Ignorant ) dentro de nuestros desarrollos.

Por suerte, en esta segunda versión esta feature tan solicitada está disponible dentro de la nueva versión del producto, así como plantillas T4 para la generación automática de las clases del modelo. Para una introducción rápida podéis leer los infinitos blogs que tratan este tema, y como no, en esta misma comunidad mis vecinos de blog, Juan Carlos y Alberto Diaz tienen entradas relativas que recomiendo leer.

Objectos POCO

Para no repetirme en el tema, en mi entrada me gustaría comentar algunas interioridades de estos objetos POCO y como trabajan internamente. Me imagino, que al igual que yo, una pregunta que os haréis es como se gestionan los cambios que sobre estos objetos se producen, dentro del ámbito de un contenedor, para que posteriormente puedan ser llevados hasta el almacen relacional configurado y como se manejan las relaciones entre los mismos.

Pongámonos en contexto y empecemos definiendo, tal y como muestran las entradas anteriormente mencionadas, un par de entidades, que denominaremos Blog y Post, mostradas a continuación.

 

   1:  public class Blog
   2:  {
   3:          #region Properties
   4:   
   5:          public int Id { get; set; }
   6:   
   7:          public string Title { get; set; }
   8:   
   9:          public string Description { get; set; }
  10:   
  11:          List<Post> Posts { get; set; }
  12:   
  13:          #endregion
  14:  }

 

   1:  public class Post
   2:  {
   3:          public int Id { get; set; }
   4:   
   5:          public string Title { get; set; }
   6:   
   7:          public string Body { get; set; }
   8:   
   9:          public int BlogId { get; set; }
  10:   
  11:          public Blog Blog { get; set; }
  12:  }

 

Ahora realicemos una sencilla operación de cambio en un conjunto de entidades, por ejemplo los blogs almacenados en nuestro almacen de datos:

   1:  using (BlogModelContainer container = new BlogModelContainer())
   2:  {
   3:         List<Blog> blogs = container.Blogs.ToList();
   4:         blogs.ForEach(blog => blog.Title = "Changed in code!");
   5:   
   6:         container.SaveChanges();
   7:  }

 

Si comprobamos nuestra base de datos veremos como estos cambios se han realizado correctamente, ¿como es posible esto? Fijaos que en nuestras clases Blog y Post ya no disponemos de los eventos RaisePropertyChanged que notificaba a la infraestructura un cambio en el valor de esta propiedad, sin embargo todo sigue funcionando correctamente. Como os podéis haber imaginado alguien se ha encargado de sincronizar nuestros objetos POCO con  los elementos que manejan el estado de todas las entidades dentro de la infraestructura de Entity Framework, nuestras ya famosas e hiper-conocidas ObjectStateEntry.

Este proceso de sincronización de cambios puede realizarse de dos formas diferentes, la primera de forma imperativa mediante una llamada a un nuevo método de los contextos de trabajo llamado DetectChanges, y el segundo, como hemos visto en nuestro objeto, de forma implícita en la llamada a SaveChanges, el cual en su implementación ya realiza este proceso por defecto.

Por supuesto, por poner pegas que no sea, este sistema no es del todo eficiente, pensemos que en realidad el proceso es básicamente la creación de un snapshot de las entidades materializadas y posteriormente comparar estos snapshot con los elementos a guardar para conocer o sincronizar los cambios. ¿Cómo podemos mejorar este proceso? La respuesta es mediante los “Change Tracking Proxies” de los cuales hablaremos a continuación.

 

Algunas notas importantes sobre los Objetos POCO

Una característica que los objetos POCO también deben soportar es el manteniemiento de las relaciones entre los objetos,asegurando la consistencia de las relaciones. Aunque este trabajo lo podemos hacer nosotros a mano tal y como se muestra en el siguiente enlace en la sección Fix-up relationships la plantilla por defecto que el equipo de producto de EF nos ofrecerá por defecto ya contendrá esta característica funcamental.

 

Change Tracking Proxies

 

Un Change Tracking Proxy no es mas que una subclase de nuestros objetos POCO que implementa la infraestructura de gestión de cambios que se define en Entity Framework, es decir, implementa la interfaz IEntityWithChangeTracker Por supuesto, con solo decir que es una subclase ya estamos imponiendo una  serie de restricciones dentro de nuestros objetos, por ejemplo el hecho de que no pueda ser una clase sellada. A continuación se muestra una lista de las restricciones que se deben de cumplir para disponer de esta capacidad dentro de nuestros objetos:

 

  1. Las clases POCO no pueden estar selladas
  2. Las clases POCO tienen que tener un constructor por defecto público.
  3. Las propiedades deben declararse como publicas y virtuales para que puedan ser sobreescritas por la subclase.
  4. El tipo de las propiedades de navegación debe de ser ICollection<TEntity>

Veamos un ejemplo en la práctica modificando nuestras entidades anteriores para que cumplan estas restricciones:

 

   1:  public class Blog
   2:  {
   3:          #region Properties
   4:   
   5:          public virtual int Id { get; set; }
   6:   
   7:          public virtual string Title { get; set; }
   8:   
   9:          public virtual string Description { get; set; }
  10:   
  11:          public ICollection<Post> Posts { get; set; }
  12:   
  13:          #endregion
  14:  }

 

   1:  public class Post
   2:  {
   3:   
   4:          #region Properties
   5:   
   6:          public virtual int Id { get; set; }
   7:   
   8:          public virtual string Title { get; set; }
   9:   
  10:          public virtual string Body { get; set; }
  11:   
  12:          public virtual int BlogId { get; set; }
  13:   
  14:          public virtual Blog Blog { get; set; }
  15:   
  16:          #endregion
  17:  }

 

Si realizamos un pequeño ejemplo de cambio en una entidad y observamos los valores que el objeto ObjectStateEntry asociado a la entidad tiene en sus valores originales y actuales podremos ver ‘la magia’ de nuestros “change tracking proxies”.

 

   1:  using (BlogModelContainer container = new BlogModelContainer())
   2:  {
   3:                  Blog firstQueriedBlog = container.Blogs.First();
   4:                  firstQueriedBlog.Title = "Changed in code!";
   5:   
   6:                  ObjectStateEntry entry = container.ObjectStateManager.GetObjectStateEntry(firstQueriedBlog);
   7:   
   8:   
   9:                  string original = entry.OriginalValues["Title"].ToString();
  10:                  string current = entry.CurrentValues["Title"].ToString();
  11:   
  12:                  Console.ReadLine();
  13:  }

 

 

¿De dónde viene esta “magia”? Si observamos con detalle el tipo de Blog obtenido veremos como en realidad el resultado es una subclase emitida en tiempo de ejecución que hará de proxy de nuestros objetos POCO. De esta forma tenemos PI en nuestros desarrollos y rendimiento en la gestión de los cambios de los mismos.

img1

Algunas notas importantes sobre Change Tracking Proxies

 

Aunque el uso de proxies dinámicos es una buena solución, en mi opinión mejor que la inyección de IL como proponen otros MVP , también tienen algunas consideraciones que debemos tratar. La primera y más imporatante es como afectan estos proxies en aplicaciones N-Tier en las que podríamos serializar estas entidades como contratos de datos, puesto que no sabemos a priory el nombre del tipo creado y por lo tanto no lo podremos establecer como KnownType o ServiceKnowType para facilitar el proceso de serialización. Con el fin de soportar esta problemática WCF 4.0 dispone de un nuevo mecanismo para resolver dinámicamente los tipos conocidos por medio de un nuevo elemento llamado DataContractResolver, sobre el cual podréis leer aquí y aquí.

Publicado por Unai | 5 comment(s)

WCF 4.0:WS-Discovery y DynamicEndpoint

En otras entradas, la verdad que de hace un pequeño tiempo, hicimos alguna introducción a las novedades que la siguiente versión de WCF 4.0 incorporará en Visual Studio 2010, de entre la lista de novedades seguramente una que llamará mucho la atención y que será de uso frecuente en nuestros desarrollos es la implementación de WS-Discovery, vista y tratada en las entradas siguientes, WS-Discovery Parte I y WS-Discovery Parte II

A lo largo de la siguiente entrada tocaremos también algo relacionado con esta implementación, pero en este caso mediante DynamicEndpoint. Como su nombre indica, DynamicEndpoint representa un extremo capaz de direccionarse automáticamente, y utiliza para ello la implementación de WS-Discovery realizada en WCF 4.0. Al igual que las clases vistas en las entradas anteriores lo primero que necesitamos es un criterio de búsqueda, algo que por regla general se hace en base al contrato que implementa el servicio que queremos buscar.

DynamicEndpoint dynamicEndpoint = new DynamicEndpoint(
    ContractDescription.GetContract(typeof(ICalculatorService)),
    new NetTcpBinding());
CalculatorServiceClient client = new CalculatorServiceClient(dynamicEndpoint);

Con este sencillo paso y de forma automática para todos los servicios que usen las capacidades de descubrimiento ya tendremos disponible nuestro proxy de cliente sin necesidad de saber de antemano la dirección física en la que están expuestos los servicios a los que nos queremos comunicar.

Otra de las features que nos permite DynamicEndpoint es la de hacer búsquedas de nuestros servicios en función de ámbitos de los mismos. Imagínese que usted ha creado un servicio de WCF para impresión y que este servicio está repetido dentro de la red de una empresa por muchas plantas, por supuesto, cada uno para trabajar con una impresora de la planta concreta. Si hicieramos uso de DynamicEndpoint este nos devolvería el extremo del primer servicio descubierto, pero que puede que no fuera del que quisiéramos hacer uso. Para resolver este problema, dentro de los comportamientos de nuestros extremos podremos agregar uno nuevo conocido como endPointDiscoveryBehavior el cual nos permitirá establecer en la configuración de despliegue de un servicio el ámbito del mismo. Este ámbito puede ser una dirección URI, una dirección LDAP o bien un identificador único  tal y como podemos ver en la documentación de MSDN al respecto. El siguiente fragmento XML podemos ver un comportamiento de extremo con soporte para ámbitos de descubrimiento.

 

 <endpointBehaviors>
                <behavior name="discoveryScopeBehavior">
                    <endpointDiscovery>
                        <scopes>
                            <add scope="http://www.microsoft.com" />
                        </scopes>
                    </endpointDiscovery>
                </behavior>
</endpointBehaviors>

Ahora, si queremos que nuestro DynamicEndpoint se base en un ámbito concreto para la búsqueda solamente tendremos que incluir en la propiedad Scopes de su elemento FindCriteria, los ámbitos concretos que hay que buscar en la red.

Para todos aquellos que deseeis ver algun ejemplo concreto sobre este tema y alguno más relacionado con las novedades de WCF os recomiendo descargaros los Samples de WCF 4.0 para Visual Studio 2010 Beta 2

 

Saludos

Unai

Publicado por Unai | con no comments

Artalde .NET – EF v 4.0 entre otras cosas

El próximo Jueves 16 de Septiembre tendré el placer de compartir con mis “amigos” de Artalde .NET una charla sobre Entity Framework V 4.0 entre otras cosas. Para los que podáis asistir en Bilbao a esta charla os dejo el enlace de registro

Artalde.NET: ADO.NET  Entity Framework Presente y Futuro

Día: 16 de Septiembre Miércoles.
Hora: 19:00 a 21:00.
Ponente: Unai Zorrilla
Agenda:
19:00 - 19:15 Registro.
19:15 - 21:00 ADO. NET Entity Framework Presente y Futuro

Introducción a Entity Framework

  • Modelos conceptuales
  • Desajuste de impedancias
  • Domain Driven Design

Entity Framework v.1

  • Creación de modelos conceptuales con EDM
  • Modelos conceptuales avanzados
  • Los servicios de Objetos y Linq To Entities

Entity Framework v.4

  • Nuevos modelos de generación de código

· POCO

· Selft Tracking Entities

  • Novedades en el modelador de entidades
  • Model First
  • Object First

    Lugar:
    Universidad de Deusto
    Edificio ESIDE, Aula de videoconferencia (2º piso)
    Avda Universidades, 24
    48007, BILBAO

Publicado por Unai | 6 comment(s)

EF 4.0: Testeando nuestros repositorios

De todos es sabido mi cariño por esta tecnología, reflejado sin duda en los numerosos artículos, entradas de blog y por su puesto el Libro ADO.NET EF publicado sobre la misma junto a mis colegas Octavio Hernandez y Eduardo Quintás. Por supuesto, ya desde hace bastante tiempo han salido betas y CTP sobre features que tendremos disponibles en Visual Studio 10 para lo que se ha llamado “Entity Framework 4.0”. A lo largo de futuros post y por supuesto de la segunda versión del libro intentaremos desgranar lo bueno de esta nueva versión y todos los ‘inconvenientes’ que hace más sencillos para la gente que se ha enfrentado a esta tecnología, sobre todo, para aquellos que la han implementado ( o intentado implementar ) en aplicaciones N-CAPAS.

Para esta primera entrada, de verdad :-), sobre EF 4.0, me gustaría centrarme en aspectos de test y test-doubles, es decir, ver como podemos con las nuevas API’s tratar  de testear nuestros repositorios de datos, y como hacer test-doubles de los mismos sin necesidad de acudir realmente a la base de datos.

El primer cambio importante que uno ve cuando de entrada crea un proyecto con EDM en Visual Studio 2010 y hecha un vistazo al código, independientemente de la plantilla de T4 seleccionada ( Clases prescriptivas, POCO o Selft Tracking Entities ) es que nuestros objetos de consulta que el contenedor pone a nuestra disposición ya no son directametne ObjectQuery<T> sinó ObjectSet<T>. Esta nueva clase, no es más que una herencia de la anterior, la cual, además de los tradicionales métodos de construcción de consultas agrega una serie de elementos que nos simplificarán un poco las cosas, como son por ejemplo los métodos AddObject, Attach y DeleteObject. Aunque a priori pueda parecer una nimiedad fíjese como en la versión actual de EF cuando queremos ‘Atachar’ una entidad dentro del contexto debemos realizar una especificación del EntitySet por medio de una cadena de caracteres, algo no muy elegante a la par de una fuente propensa de errores cuando el proyecto evoluciona, es decir, cuando los modelos tienen una frecuencia de cambio relativamente alta. Le recomiendo la lectura del post de Alex James dónde expone estos simples cambios y de paso como podríamos simularlos en .NET 3.5 SP1.

Además de los métodos comentados, la parte más interesante de esta nueva clase es que la misma se basa en la implementación de una interfaz, IObjectSet<T>, y por lo tanto es susceptible de ser simulada con cierta rapidez. Precisamente, es este punto, el que nos interesa dentro del trabajo a realizar para la creación de test-doubles con los contenedores de EF.

Supongamos que creamos un nuevo modelo de entidades que disponga de una entidad Person y que sobre el mismo decidimos crear nuestra implementación de contedor y entidades POCO.

Un ejemplo de nuestro contenedor podría ser algo similar a lo siguiente:

 

public class ModelContainer
        :ObjectContext
{
        #region Constructor

        public ModelContainer()
            :base("name=ConnectionStringName","ContainerName")
        {
        }

        #endregion

        #region ObjectSets

        private ObjectSet<Person> mPerson = null;
        public ObjectSet<Person> Person
        {
            get
            {
                return mPerson ?? this.CreateObjectSet<Person>();
            }
        }

        #endregion
}

Como podrá observar sería muy sencillo readaptar nuestro contenedor para que el mismo se basara en una interfaz, que por ejemplo llamaremos IContainer y que tendría la siguiente firma.

public interface IContainer
{    
    IObjectSet<Person> Person { get; }
}

 

public class ModelContainer
        :ObjectContext,IContainer
{
        #region Constructor

        public ModelContainer()
            :base("name=ConnectionStringName","ContainerName")
        {
        }

        #endregion

        #region ObjectSets

        private ObjectSet<Person> mPerson = null;
        public IObjectSet<Person> Person
        {
            get
            {
                return mPerson ?? this.CreateObjectSet<Person>();
            }
        }

        #endregion
}

 

Llegados a este punto, podríamos decir que ya tenemos las bases para poder hacer simulaciones de un contendor de trabajo. La pieza que tendríamos que simular sería una implementación ‘dummy’ de la interfaz IContainer, la cual puede ser fácilmente construída con Stub’s del que ya hemos visto cosillas en un video de Channel9@Spain o bien con cualquier otro framework de Mockering-Stubing como NMock, RhinoMock etc.. El problema principal que nos queda por resolver es la creación de nuestras simulaciones de IObjectSet, puesto que además de los elementos vistos esta interfaz es IQueryable<T> y por lo tanto debermos de dar una implementación a los métodos de la misma. Para resolver esta problemática utilizaremos el método extensor, AsQueryable(), que todos los elementos IEnumerable<T> ,como las listas genéricas, poseen y mediante el cual, podemos transformar directamente una colección de este tipo a Queryable<T>.

Con el fin de hacer esto de una forma más genérica y reutilizable para todos nuestros ObjectSet  partiremos de la siguiente clase.

 

class MockObjectSet<T>
        :IObjectSet<T>
        where T:class

    {
        #region Members

        List<T> mInnerList = null;

        #endregion

        #region Constructor

        public MockObjectSet(List<T> innerList)
        {
            //Set InnerList
            mInnerList = innerList;
        }

        #endregion

        #region IObjectSet<T> Members

        public void AddObject(T entity)
        {
            if (mInnerList != null)
                mInnerList.Add(entity);
        }

        public void Attach(T entity)
        {
            //TODO: For future post :-)
        }

        public void DeleteObject(T entity)
        {
            if ( mInnerList != null )
                mInnerList.Remove(entity);
        }

        #endregion

        #region IEnumerable<T> Members

        public IEnumerator<T> GetEnumerator()
        {
            return mInnerList.GetEnumerator();
        }

        #endregion

        #region IEnumerable Members

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return this.GetEnumerator();
        }

        #endregion

        #region IQueryable Members

        public Type ElementType
        {
            get { return typeof(T); }
        }

        public System.Linq.Expressions.Expression Expression
        {
            get { return mInnerList.AsQueryable().Expression; }

        }
        public IQueryProvider Provider
        {
            get { return mInnerList.AsQueryable().Provider; }

        }
                

        #endregion
    }

 

Cómo se puede observar, esta nueva clase nos es más que la implementación de la interfaz IObjectSet a partir de una lista de elementos cualquiera y puede ser construída fácilmente por medio, por ejemplo, de un método extensor aplicado a las listas genéricas.

static class Extensions
{
    public static MockObjectSet<T> AsObjectSet<T>(this List<T> list)
            where T:class
    {
         return new MockObjectSet<T>(list);
    }
}

 

Llegados aquí, ya hemos construídas todas las bases de nuestro trabajo, vamos entonces, a realizar la tarea que nos ocupaba que consistía en realizar la implementación de Test de nuestros contenedores sin tener que acudir a la base de datos, para ello iremos utilizando todas las partes que hemos visto y creado anteriormente.

 

[TestMethod()]
public void TestPersonObjectSet()
{
            //Triple AAA ( Arrange, Act, Assert.. )

           /*
            * Arrange
            */

           //Create a dummy implementation of IContainer
           SIContainer containerDouble = new SIContainer();

           //Create a list of dummy values for Person ObjectSet
           List<Person> persons = new List<Person>()
           {
               new Person(){IdPerson=1,FistName="Unai",LastName="Zorrilla"},
               new Person(){ IdPerson=2,FistName="Pablo",LastName="Alvarez Doval"}
           };

           //Set Stub ObjectSet
           containerDouble.PersonGet = () => persons.AsObjectSet();

           /*
            * Act
            */

           Person personId1 = ((IContainer)containerDouble).Person.Single(p => p.IdPerson == 1);

           List<Person> collection = ( from c in ((IContainer)containerDouble).Person
                                       orderby c.IdPerson select c).ToList();

           /*
            * Assert
            */

           Assert.AreEqual(personId1.FistName, "Unai");
           Assert.AreEqual(personId1.LastName, "Zorrilla");
           Assert.IsTrue(persons.Count > 0);
}

 

 

Como podemos observar en el código anterior, nuestro ejemplo de test crea una instancia de un contenedor creado por Stub’s, al cual le asignamos como ObjectSet de Person el creado a partir de una lista con nuestro nuevo método extensor, posteriormente, haremos las consultas, de igual forma que si estuvieramos trabajando contra ObjectSet de nuestros modelos de EF, es decir, como si estuvieramos trabajando con bases de datos.

 

Por supuesto, esto no es más que un ejemplo de trabajo sencillo, en futuras entradas iremos viendo como poner en práctica todo esto contra repositorios de nuestros contenedores de trabajo y como modificar nuestras plantillas de T4 para que podamos tener un alto grado de productividad, tanto para la realización de aplicaciones con EF 4.0 como para que las mismas sean fácilmente testeables.

 

Espero que os guste…

Un saludo

Unai Zorrilla Castro

Publicado por Unai | 4 comment(s)

.NET 4.0 y System.Data.OracleClient

Himanshu Vasishth acaba de anunciar en el blog de ADO.NET que para .NET 4.0 el driver de Oracle para ADO.NET estará marcado como deprecated, por lo tanto su utilización no estará recomendada. Por supuesto, seguirán dando soporte a clientes que lo usen y resolviendo los issues que se produzcan…

Publicado por Unai | 2 comment(s)

WCF 4: Pequeñas novedades en REST

A lo largo de unos post anteriores he venido presentando algunas de las novedades de WCF 4.0 que ya podemos probar en Visual Studio 2010 Beta 1. Para continuar con esta serie de novedades veremos algunas pequeñas mejoras introducidas en los servicios REST-style que podremos construir  en esta nueva versión. La primera de ellas aunque simple consiste en la posibilidad de disponer automáticamente de una página de ayuda que muestra las distintas operaciones REST que nuestro servicio puede ofrecer, así como ejemplos de llamadas tanto en XML como Json para los estilos de los mensajes. Para incluir esta información simplemente tendremos que establecer el atributo enableHelpPage=true a nuestro comportamiento de extremo webHttp, tal y como se muestra a continuación.

<endpointBehaviors>
    <behavior name="webHttpEndPointBehavior">
        <webHttp enableHelp="true" />
    </behavior>
</endpointBehaviors>

 

Con esta simple tarea agregando el path /help a nuestra dirección base de servicio se nos presentará en formato RSS una página similar a la siguiente, en la que podremos ver una completa información de nuestro servicio REST.

 

help

 

La segunda de las novedades tiene que ver con la posibilidad de incluir un sistema de ‘Caching’ automático a nuestras operaciones GET, para ello disponemos de un nuevo atributo denominado [AspNetCacheProfile("Nombre-Cache")]. Este atributo hará uso de una sección de configuración donde podremos especificar la duración de la cache y los parámetros que puedan invalidarla, como ejemplo de esta configuración tenemos el siguiente fragmento.

<system.web>
    <caching>
      <outputCacheSettings>
        <outputCacheProfiles>
          <add name="Nombre-Cache" duration="60" varyByParam="format"/>
        </outputCacheProfiles>
      </outputCacheSettings>
    </caching>
  </system.web>

 

Como nota importante decir que este sistema de caché solamente esta habilitado cuando el servicio está configurado con compatibilidad con ASP.NET y el mismo corre bajo IIS, es decir, no podremos hostearlo en un Servicio de Windows por poner un ejemplo.

 

Saludos

Unai

Publicado por Unai | 1 comment(s)

Channel9@Spain: Pex + Stub’s

Get Microsoft Silverlight

 

Saludos

Unai

Publicado por Unai | con no comments

Routing Service: Novedades en WCF 4.0

Código del ejemplo en VS2010 Beta 1

En una entrada anterior, hicimos una introducción acerca de la creación de un sistema de Router o Forwarding en WCF 3.X, fíjese que comenzamos diciendo que podría ser algo habitual en arquitecturas orientadas a servicios, note el patrón “Intermediate Routing”.

La siguiente versión de WCF, conocida como WCF 4.0 de entre las novedades que trae consigo, algunas de las cuales ya las hemos visto aquí y aquí, puede encontrarse la implementación de un servicio de router ‘out of box’ capaz de resolvernos este patrón sin necesidad de implementar ni una sola línea de código y soportando muchos de los esquemas de mensajes habituales, Request Replay, Fire And Forget, Duplex.

A lo largo de esta entrada trataremos de implementar el mismo ejemplo que teníamos en la entrada anterior  con el nuevo sistema de routing. Empezemos por el principio, que consiste en definir el servicio que será alojado en distintos procesos HOST (presumiblemente en distintas máquinas) y que desamos balancear.

 

[ServiceContract(Namespace="http://www.plainconcepts.com")]
 public interface IGeekService{
        [OperationContract()]
        string GetGeekBlog(string username);
  }

 

Una vez alojados estos servicios, cosa que no vamos a comentar y que damos por sabida, por ejemplo en las direcciones http://localhost:2020/GeekService/ y http://localhost:2030/GeekService/IGeekService llega la hora de hacer la implementación de nuestro router. El nuevo espacio de nombres System.ServiceModel.Routing pone a nuestra disposición un servicio por defecto denominado RoutingService el cual realizará por nosotros las tareas de enrutado sin necesidad de que tengamos tanto conocimiento de las cabeceras de los sobres SOAP To y Action como necesitábamos para la versión anterior.

Las siguientes líneas de código reflejan lo necesario para alojar nuestro servicio de Router, en nuestro ejemplo en una aplicación de consola.

 

using (ServiceHost host = new ServiceHost(typeof(System.ServiceModel.Routing.RoutingService))){
    host.Open();
    Console.WriteLine("Router a la escucha!");
    Console.ReadLine();
}

 

A la hora de configurar este servicio de router nos tendremos que fijar en distintos aspectos. Por un lado el enlace que vamos a seleccionar, que por supuesto tendrá que ser perfectamente compatible con los de los servicios y cliente, es decir, si el cliente y los servicios se comunican con el enlace ws2007HttpBinding y con un determinado mecanismo de seguridad, el router tendrá que tener este mismo enlace con la configuración adecuada.

Con respecto al contrato que implementa este Routing Service, tendremos distintas opciones a elegir, dependiendo del tipo de esquema de mensajería que se utilice. Otra vez el espacio de nombres System.ServiceModel.Routing pone a nuestra disposición cinco posibles contratos de servicio para nuestro router para que seleccionemos el adecuado para nuestras necesidades:

· System.ServiceModel.Routing.IDuplexRouterCallback

· System.ServiceModel.Routing.IDuplexSessionRouter

· System.ServiceModel.Routing.IRequestReplyRouter

· System.ServiceModel.Routing.ISimplexDatagramRouter

· System.ServiceModel.Routing.ISimplexSessionRouter

Como en nuestro ejemplo, teníamos un sistema de envío y respuesta tradicional, seleccionaremos IRequestReplyRouter. En este punto ya tendremos la configuración de nuestro router como sigue:

 

<services>
    <service behaviorConfiguration="RouterBehavior" name="System.ServiceModel.Routing.RoutingService">
        <endpoint address="" binding="ws2007HttpBinding" bindingConfiguration="ws2007NoSecurity"
                    contract="System.ServiceModel.Routing.IRequestReplyRouter" />
            <host>
                <baseAddresses>
                    <add baseAddress="http://localhost/RouterService" />
                </baseAddresses>
           </host>
    </service>
</services>

 

Llegados a este momento solamente falta ver como especificamos los filtros que permitan indicarle al router como redirigir los mensajes entrantes hacia los distintos procesos de alojamiento de los servicios. Para ello, lo primero que haremos será agregar un nuevo comportamiento  llamado routing, mediante el cual podremos indicar la tabla de filtros de enrutado.

 

<serviceBehaviors>
    <behavior name="RouterBehavior">
        <routing filterOnHeadersOnly="true" routingTableName="RoutingTableA" />
    </behavior>
</serviceBehaviors>

Nota: El comportamiento de routing permite indicar si solamente se realizaran filtros por las cabeceras ( filtersOnHeadersOnly ), algo muy rápido, o bien examinando el cuerpo de los mensajes.

Estamos ya en la parte más interesante del sistema, la parte con la que podemos especificar los filtros que nos permitan balancear las peticiones. En realidad, un filtro, para un router nos es nada más que una clase heradada de MessageFilter, por lo tanto la creación de filtros personalizados es relativamente sencillo. Por defecto, dentro de WCF 4.0 ya tendremos una serie de filtros OOB, los cuales nos permitirán balancear en función del Action, Address, Endpoint y/o un filtro XPath sobre el contenido del mensaje.

La sección de filtros para enrutado es un nueva subsección de <system.serviceModel> y que tendrá un aspecto similar a la siguiente, que pasaremos a desgranar seguidamente.

<routing>
        <namespaceTable>
          <add prefix="P_C" namespace="http://www.plainconcepts.com"/>
        </namespaceTable>
        <filters>
          <filter name="FilterHostA" filterType="XPath" filterData="s12:Envelope/s12:Header/P_C:Host='HostA'"/>
          <filter name="FilterHostB" filterType="XPath" filterData="s12:Envelope/s12:Header/P_C:Host='HostB'"/>
        </filters>
        <routingTables>
          <table name="RoutingTableA">
            <entries>
              <add filterName="FilterHostA" priority="1" endpointName="HostA"/>
              <add filterName="FilterHostB" priority="2" endpointName="HostB"/>
            </entries>
          </table>
        </routingTables>
</routing>

La primera parte de la nueva sección , namespaceTable, nos permite establecer prefijos para los distintos namespaces que pueda tener un sobre SOAP, como en nuestro caso, al igual que en la entrada para routers en WCF 3.X, establecíamos una cabecera con un determinado namespace, pasamos a establecer este nuevo prefijo. Por supuesto, algunos prefijos son por defecto ya conocidos como s12 para Soap 1.2 o s11 para Soap 1.1.

Una vez especificados los prefijos, llega la hora de establecer la lista de filtros que podrán usarse dentro de las tablas. En el caso de nuestra sección incluímos dos filtros basados en una consulta XPath sobre el mensaje. Cómo podrá observar el primer filtro se corresponderá con los mensajes que contengan una Header llamada Host y con valor HostA, el segundo filtro será equivalente pero en este caso para el valor HostB.

Nota: Por supuesto, dentro de esta colección se podrá hacer uso tanto de los filtros por defecto como de los filtros personalizados que deseemos crearnos.

Ya completadas las secciones de prefijos y filtros solamente falta la creación de las tablas, para nuestro ejemplo disponemos de una tabla con nombre ‘RoutingTableA’, previamente configurada en el comportamiento de servicio, la cual hace uso de los filtros con nombre FilterHostA y FilterHostB.

Ya está todo amigos, no ha hecho falta escribir una sola línea de código para completar el ejemplo, solamente configuración de un servicio que tenemos por defecto en WCF 4.

Ahora, solamente haría falta comunicarle al cliente la dirección del router para que la establezca como dirección de los servicios, sin preocuparnos de que el header Address no coincida, sin aplicar un ViaBehavior etc....

Espero que esta nueva entrada sobre las novedades de WCF os haya parecido interesante, si os interesa investigar o leer más sobre este tema a continuación os dejo algunos enlaces interesantes.

http://blogs.msdn.com/endpoint/archive/2009/05/07/the-road-to-4-wcf-changes-between-beta-1-and-ctp.aspx

http://blogs.thinktecture.com/cweyer/archive/2009/05/08/415335.aspx

http://www.aspnetpro.com/articles/2009/05/asp200905mb_f/asp200905mb_f.asp

 

Saludos

Unai Zorrilla

Publicado por Unai | 3 comment(s)

Routers en WCF 3.X

Codigo del ejemplo

Aunque suene en principio algo extraño, la posibilidad de crear un servicio de routers o forwarding es un elemento no tan raro dentro de nuestros desarrollos de aplicaciones distribuídas. Si bien, hoy tenemos sistemas de balanceo de carga por software, NLB, o por hardware como F5 por citar algunos, estos sistemas podrían no cubrir ciertas necesidades. Piense por ejemplo en enrutar o balancear determinados mensajes de servicio en función del valor de una cabecera o bien en función de la operación que se desea realizar. Como se imaginará, los sistemas citados anteriormente puede que no sean suficientes para estos propósitos.

El enrutamiento de mensajes ya era posible para los que tuvimos la suerte desgracia de trabajar con WSS 3.0, y por supuesto, es posible desde las primeras versiones de WCF, que a partir de ahora conoceremos o llamaremos como WCF 3.X.

A lo largo del siguiente post trataremos de ver un ejemplo de una posible implementación de un sistema de routers tal y como podemos hacer hoy en día con WCF 3.X, en otro post siguiente veremos cómo hacerlo en .NET 4.0 con su nuevo servicio dedicado explícitamente a esta funcionalidad, pero paso a paso..

NOTA: Dentro de la implementación de routers podrían distinguirse varios tipos además de implementaciones para resolver los distintos estilos de mensajes, simplex, dúplex, con sesión, seguridad etc.. para no complicar el tema trataremos de realizar un ejemplo simple sin pararnos a manejar temas como sesión o seguridad.

La primera pregunta que uno se hace, o por lo menos esta es la que yo me hice en su dia, cuando se pone a pensar en una posible implementación, es cómo manejar la cabecera Action, cabecera que todos los sobres SOAP contienen.

NOTA: La cabecera Action permite a la infraestructura o dispatcher de mensajes decidir que método del contrato de servicio se va a ejecutar. Si un contrato de servicio dispone de un método, llamemos A, cuando se invoca a esta operación el sobre SOAP contendrá una cabecera Action con un valor equivalente a la siguiente expresión: [Service Namespace]/[Contract Name]/OperationName, a lo menos que se especifique el atributo Action del decorador OperationContract. Por dar una nota explicativa más sobre este tema, para la implementación de REST en WCF precisamente uno de los cambios consistió en establecer la versión del sobre SOAP a None y cambiar el procesamiento de los mensajes para que la invocación del método del contrato de servicio no se basara en la cabecera Action sino en la Uri de invocación.

El porqué de esta pregunta es muy sencillo, si ponemos un intermediario entre el cliente y el servicio, esto es, básicamente un router o forwarder, un método del intermediario solamente sería llamado si su valor de Action se corresponde con la cabecera Action del sobre SOAP que se reciba. Por lo tanto, ¿Cómo hacemos para que un método de un intermediario pudiera procesar un sobre con un Action distinto al suyo?.

La respuesta a esta pregunta son ‘los métodos ociosos’, es decir, los métodos de contrato de servicio que tienen establecido su Action igual a *. Este valor de Action permite indicarle a la infraestructura que cuando llegue un mensaje al servicio cuyo valor de cabecera Action no se corresponda con ninguno de los elementos del contrato de servicio ejecute este método.

Llegados a este punto ya sabemos que podríamos tener por ejemplo un método ForwardMessage decorado con OperationContract y su valor de Action=* capaz de procesar cualquier mensaje que recibiera. Pero, los mensajes a procesar son distintos y variados, unos toman un parámetro de entrada otros varios, unos no devuelven resultados, otros un tipo simple y otros algún tipo complejo. En WCF 3.X siempre hemos tenido la posibilidad de trabajar de forma no tipada con nuestros servicios, asumiendo como un Message cualquier elemento de entrada y cualquier resultado de operación de servicio, aunque esta sea no devolver ningún resultado, por lo tanto, llegados ya ha este punto podríamos deducir que un contrato válido para un router de servicio podría ser algo similar a lo siguiente.

[ServiceContract(Namespace = "http://www.plainconcepts.com")]
public interface IRouter
{        [OperationContract(ReplyAction="*",Action="*")]
       Message ForwardMessage(Message message);
}

Con este sencillo contrato ya tenemos decidido cómo será nuestro intermediario o router, ahora, solamente queda ver cómo realizar una implementación. Para ello, empezaremos por definir el servicio que enrutaremos entre diferentes equipos, con el fin de no complicar el ejemplo con elementos innecesarios partiremos de un contrato de servicio tan simple como el siguiente:

 

[ServiceContract(Name="IGeekService",Namespace="http://www.plainconcepts.com")]
public interface  IGeekService
{        [OperationContract(Name="GetGeekBlog")]
       string GetGeekBlog(string username);
}

Una vez decidido el contrato de nuestro servicio pasaremos a realizar una implementación del mismo y  alojarlo en diferentes procesos Host, que podrían estar en diferentes máquinas, para nuestro ejemplo tendremos estos servicios alojados en las siguientes direcciones http://localhost:2020/GeekService y http://localhost:2030/GeekService.

Fíjese como llegados hasta aquí puede surgir otra pregunta similar a la que nos hacíamos con respecto a la cabecera Action, pero esta vez con respecto a la cabecera To. WCF tiene para cada uno de los extremos, endpoints, asociadas dos direcciones, una lógica y otra física, cuyo sentido es el mismo que en WSS con los valores de las cabeceras To y Via. To se corresponde con la dirección lógica a la que se envían los mensajes miestras que Via se corresponde con la dirección física de red dónde se está realizando la escucha de los mismos. En WCF 3.X la dirección lógica se establece con el parámetro Address y la dirección física con ListenUri, aunque por regla general solamente se suele establecer el valor de Address la dirección física se infiere automáticamente de esta.

Si en nuestro ejemplo anterior configuramos nuestro servicio GeekService con dos direcciones distintas, los valores que la cabecera To tendrá en los mensajes a uno y otro serán diferentes, y por lo tanto los mensajes se podrán rechazar si llegan a un servicio de escucha con un valor de dirección lógica distinto al que el mensaje contiene en sus cabeceras. ¿Cómo resolvemos ahora este problema?

 

NOTA: Puede obtener más información acerca del direccionamiento de WCF en este gran artículo de Aaron Skonnard.

En realidad, tenemos varias alternativas, aunque todas ellas pasan de una forma u otra por configurar los valores de ListenUri o establecer un behavior de tipo clientVia que como puede imaginarse se corresponde con el valor de Via o dirección física a las que nos comuniquemos.

Para nuestro ejemplo, creamos dos proyectos de host a los cuales agregamos la referencia a nuestro contrato de servicio y los configuramos tal y como se muestra a continuación:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <system.serviceModel>
        <bindings>
   <ws2007HttpBinding>
    <binding name="NoSecurity">
     <security mode="None" />
    </binding>
   </ws2007HttpBinding>
  </bindings>
  <behaviors>
   <serviceBehaviors>
    <behavior name="GeekService.GeekServiceBehavior">
     <serviceMetadata httpGetEnabled="true" httpGetUrl="http://localhost:2030/GeekService" />
    </behavior>
   </serviceBehaviors>
  </behaviors>
        <services>
   <service behaviorConfiguration="GeekService.GeekServiceBehavior"
    name="GeekService.GeekService">
    <endpoint address="urn:GeekService" binding="ws2007HttpBinding"
     bindingConfiguration="NoSecurity" name="wsBinding" contract="GeekService.IGeekService"
     listenUri="http://localhost:2030/GeekService" />
   </service>
  </services>
    </system.serviceModel>
</configuration>
<configuration>
    <system.serviceModel>
        <bindings>
   <ws2007HttpBinding>
    <binding name="NoSecurity">
     <security mode="None" />
    </binding>
   </ws2007HttpBinding>
  </bindings>
  <behaviors>
   <serviceBehaviors>
    <behavior name="GeekService.GeekServiceBehavior">
     <serviceMetadata httpGetEnabled="true" httpGetUrl="http://localhost:2030/GeekService" />
    </behavior>
   </serviceBehaviors>
  </behaviors>
        <services>
   <service behaviorConfiguration="GeekService.GeekServiceBehavior"
    name="GeekService.GeekService">
    <endpoint address="urn:GeekService" binding="ws2007HttpBinding"
     bindingConfiguration="NoSecurity" name="wsBinding" contract="GeekService.IGeekService"
     listenUri="http://localhost:2030/GeekService" />
   </service>
  </services>
    </system.serviceModel>
</configuration>

Fíjese como, para la configuración anterior hemos establecido su dirección lógica a urn:GeekService y hemos establecido su dirección física en http://localhost:2030/GeekService, para el otro host hacemos una configuración similar, aunque como indicamos antes, escuchando en la dirección física http://localhost/2020/GeekService.

Realizaremos ahora la implementación de nuestro router, tal y como se ve en el siguiente fragmento:

 

[ServiceBehavior(AddressFilterMode=AddressFilterMode.Any)]
public class RouterService
        :IRouter
{
       #region IRouter Members

      public Message ForwardMessage(Message message)
      {
            int headerIndex = message.Headers.FindHeader("Key", "http://www.plainconcepts.com");
            if (headerIndex != -1)
            {
                string host = message.Headers.GetHeader<string>(headerIndex);
                ChannelFactory<IRouter> client = new ChannelFactory<IRouter>(host);
                return (client.CreateChannel()).ForwardMessage(message);
            }

            throw new InvalidOperationException("No se puede encontrar la cabecera 'Key'");
        }

        #endregion
}

 

En esta implementación, lo único que se realiza es la verificación del mensaje para obtener una cabecer que nos permita enrutar las llamadas de servicio. Una vez obtenida esa cabecera creamos un cliente apropiado del host al que nos dirigimos, por supuesto, esto forma parte de la configuración de nuestro router y la podemos ver en la siguiente figura:

 

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <system.serviceModel>
        <client>
            <endpoint address="urn:GeekService" behaviorConfiguration="ViaHostA"
                binding="ws2007HttpBinding" bindingConfiguration="NoSecurity"
                contract="Router.IRouter" name="HostA" />
            <endpoint address="urn:GeekService" behaviorConfiguration="ViaHostB"
                binding="ws2007HttpBinding" bindingConfiguration="NoSecurity"
                contract="Router.IRouter" name="HostB" />
        </client>
        <behaviors>
            <endpointBehaviors>
                <behavior name="ViaHostA">
                    <clientVia viaUri="http://localhost:2020/GeekService" />
                </behavior>
                <behavior name="ViaHostB">
                    <clientVia viaUri="http://localhost:2030/GeekService" />
                </behavior>
            </endpointBehaviors>
        </behaviors>
        <bindings>
            <ws2007HttpBinding>
                <binding name="NoSecurity">
                    <security mode="None" />
                </binding>
            </ws2007HttpBinding>
        </bindings>
        <services>
            <service name="Router.RouterService">
                <endpoint address="urn:GeekService" binding="ws2007HttpBinding"
                    bindingConfiguration="NoSecurity" name="wsBinding" contract="Router.IRouter" listenUri="http://localhost/RouterService" />
            </service>
        </services>
    </system.serviceModel>
</configuration>

 

Preste atención a la configuración de los clientes del router y como a estos se les ha agregado el comportamiento clientVia, esto es necesario para que el valor de la cabecera To en respuesta a las peticiones del cliente no se viera modificada.

Ahora, ya solamente nos queda realizar la implementación del cliente, para ello simplemente agregamos el siguiente código de llamada, dónde además se agrega una cabecera personalizada, la que necesita el router para enrutar.

NOTA: El envío de esta cabecera se podría realizar automáticamente mediante la implementación de IClientMessageInspector, pero, esto formaría parte de otra entrada.

 

Proxy.GeekServiceClient client = new Client.Proxy.GeekServiceClient();
using (new OperationContextScope(client.InnerChannel))
{
MessageHeader header = MessageHeader.CreateHeader("Key",                 "http://www.plainconcepts.com", "HostA");
                OperationContext.Current.OutgoingMessageHeaders.Add(header);
Console.WriteLine("Unai blog:{0}", client.GetGeekBlog("unai"));
}

La configuración del cliente, es similar a la del router, y la podemos ver a continuación:

 

<system.serviceModel>
        <behaviors>
            <endpointBehaviors>
                <behavior name="ViaBehavior">
                    <clientVia viaUri="http://localhost/RouterService" />
                </behavior>
            </endpointBehaviors>
        </behaviors>
        <bindings>
            <ws2007HttpBinding>
                <binding name="NoSecurity">
                    <security mode="None" />
                </binding>
            </ws2007HttpBinding>
        </bindings>
        <client>
            <endpoint address="urn:GeekService" behaviorConfiguration="ViaBehavior"
                binding="ws2007HttpBinding" bindingConfiguration="NoSecurity"
                contract="Proxy.IGeekService" name="wsBinding" />
        </client>
    </system.serviceModel>

 

Por supuesto, este es un ejemplo muy sencillo, y no es la única forma de realizar esta tarea, pero espero que os anime a saber más sobre las posibilidades de enrutado en WCF y las posibilidades que se abren para hacer sistemas de balanceo en función de los parametros de los mensajes o cualquier otro elemento que podamos programar. En un futuro post, no demasiado lejano, veremos cómo esta tarea, que lleva una buena cantidad de código se simplifica con ciertas novedades introducidas en WCF 4.0.

Publicado por Unai | con no comments
Archivado en: ,

WS-Discovery (II):Novedades en WCF 4.0

En el post anterior empezamos a ver la especificación de WS-Discovery por medio de los mensajes PROBE y PROBE MATCH. A lo largo de esta entrada trataremos de ver los anunciós HELLO y BYE y como hacer que nuestros servicios los realicen de forma automática cada vez que los mismos se abren o cierran los canales de escucha.

Si para el descubrimiento de los servicios a los extremos de nuestro servicio agregábamos un elemento de tipo udpDiscoveryEndpoint para facilitar su descubrimiento junto al comportamiento discoveryBehavior. Para los anuncios solamente tendremos que incluir un nuevo extremo, udpAnnouncementEndpoint, esta vez, dentro de la sección de anuncios del comportamiento discoveryBehavior tal y como se muestra a continuación.

 

<behaviors>
            <serviceBehaviors>
                <behavior name="SampleServiceBehavior">
                    <serviceDiscovery>
                        <announcementEndpoints>
                            <endpoint name="udpAnnouncementEndpoint" kind="udpAnnouncementEndpoint"
                                endpointConfiguration="" />
                        </announcementEndpoints>
                    </serviceDiscovery>
                </behavior>
            </serviceBehaviors>
        </behaviors>

Con este sencillo paso, automáticamente nuestro servicio es capaz de realizar los envíos de los anuncios. La pregunta que os estaréis haciendo ahora es ¿Cómo se subscribe uno a estos anuncios? En definitiva, estos anuncios no son más que mensajes enviados a una dirección de multicast, ya comentada en el artículo anterior, que un determinado servicios puede estar escuchando. La implementación de este servicio de escucha es muy sencilla basándose en una clase ya incluída en WCF 4.0 que se llama AnnouncementService y cuyo ejemplo de uso podemos ver a continuación.

 

static void Main(string[] args)
        {
            AnnouncementService announcementService = new AnnouncementService();
            announcementService.OfflineAnnouncementReceived += new EventHandler<AnnouncementEventArgs>(announcementService_OfflineAnnouncementReceived);
            announcementService.OnlineAnnouncementReceived += new EventHandler<AnnouncementEventArgs>(announcementService_OnlineAnnouncementReceived);
            using (ServiceHost host = new ServiceHost(announcementService))
            {
                host.AddServiceEndpoint(new UdpAnnouncementEndpoint());
                host.Open();

                Console.ReadLine();
            }
        }

        static void announcementService_OnlineAnnouncementReceived(object sender, AnnouncementEventArgs e)
        {
            Console.WriteLine("El servicio {0} anuncia HELLO",e.AnnouncementMessage.EndpointDiscoveryMetadata.Address);
        }

        static void announcementService_OfflineAnnouncementReceived(object sender, AnnouncementEventArgs e)
        {
            Console.WriteLine("El servicio {0} anuncia BYE",e.AnnouncementMessage.EndpointDiscoveryMetadata.Address);
        }
Publicado por Unai | 1 comment(s)

WS-Discovery (I): Novedades en WCF 4.0

NOTA: En esta entrada solamente se tratará el modo AdHoc, es decir, basándose en descubrimiento de servicios bajo una misma subred. Se deja para otros futuros post la creación de un proxy de descubrimiento.

WS-Discovery es una de las ‘pocas’ novedades que trae WCF 4.0, es decir, la versión de Windows Communication Foundation en Visual Studio 2010. Aunque es ya un estandard de OASIS  que tiene su tiempo, hasta esta nueva versión no teníamos implementación directa.

Antes de empezar a mostrar ejemplos sobre su uso en Visual Studio 2010 haremos un breve repaso del porqué y cómo funciona WS-Discovery. La idea es muy simple, y se basa en el descubrimiento automático de extremos de servicio dentro de una red, algo muy demandado y necesario en Bus de Servicios Empresariales ( ESB ), dónde el direccionamiento puede modificarse con relativa frecuencia. La especificación de este estándar es relativamente pequeña y fácil de entender tal y como se puede ver resumida en los siguientes puntos.

· Cuando un servicio se une a una red, es decir, cuando se pone a escuchar en una determinada dirección este envía un anuncio denominado HELLO para notificar su entrada a la misma.

· Cuando un servicio se quita de una red, es decir, cuando se cierra y deja de escuchar en una dirección este envía un anuncio denominado BYE para notificar su salida.

· Un cliente puede descubrir servicios existentes en un red bajo un criterio determinado enviando un mensaje denominado PROBE, si existe algún servicio este responde con otro mensaje denominado PROBE MATCH el cual contiene la información de su ServiceEndPoint.

· Un cliente puede descubrir servicios existentes en la red mediante su nombre, para ello puede enviar un mensaje denominado RESOLVE, si el servicio está en la red responde con otro mensaje denominado RESOLVE MATCH con la información del mismo.

Sencillo verdad? Le suena esté mecanismo? En realidad es el mismo que Windows utiliza para el descubrimiento de dispositivos hardware puesto que también se basa en una implementación de WS Discovery.

Sobre este pequeño resumen de especificación caben unas cuantas preguntas, la primera es ¿dónde se envían estos mensajes y/o anuncios? La respuesta es mediante multicast a una dirección determinada, que por regla general y en el caso de WCF 4.0 es soap.udp://239.255.255.250:3702, valores que podremos modificar dentro de la nueva sección de configuración “Standard Endpoints” que WCF Configuration Editor pone a nuestra disposición. Al final, como podrá comprobar, WS-Discovery no es más que la implementación de un determinado contrato de servicio, que en caso de WCF 4.0 se corresponde con la versión de Abril 2005 y que tiene la siguiente firma.

[ServiceContract(Namespace="http://schemas.xmlsoap.org/ws/2005/04/discovery", CallbackContract=typeof(IDiscoveryResponseContractApril2005))]
internal interface IDiscoveryContractApril2005
{
    // Methods
    [OperationContract(Action="http://schemas.xmlsoap.org/ws/2005/04/discovery/Probe", IsOneWay=true, AsyncPattern=true)]
    IAsyncResult BeginProbeOperation(ProbeMessageApril2005 request, AsyncCallback callback, object state);
    [OperationContract(Action="http://schemas.xmlsoap.org/ws/2005/04/discovery/Resolve", IsOneWay=true, AsyncPattern=true)]
    IAsyncResult BeginResolveOperation(ResolveMessageApril2005 request, AsyncCallback callback, object state);
    void EndProbeOperation(IAsyncResult result);
    void EndResolveOperation(IAsyncResult result);
    [OperationContract(Action="http://schemas.xmlsoap.org/ws/2005/04/discovery/Probe", IsOneWay=true)]
    void ProbeOperation(ProbeMessageApril2005 request);
    [OperationContract(Action="http://schemas.xmlsoap.org/ws/2005/04/discovery/Resolve", IsOneWay=true)]
    void ResolveOperation(ResolveMessageApril2005 request);
}

Ahora que más o menos entendemos WS-Discovery, veámos como configurar un servicio para que acepte la petición Probe y por lo tanto pueda ser descubierto por los clientes del mismo ( entendiéndose como clientes también a otros servicios).

El primer paso es agregar a nuestros servicios un Endpoint especial, llamado updDiscoveryEndpoint, de forma similar a como agregamos extremos de servicio hasta la fecha.

<endpoint name="udpDiscoveryEndpoint" kind="udpDiscoveryEndpoint" endpointConfiguration="" />

 

NOTA: El atributo Kind es nuevo dentro de la configuración de Windows Communication Foundation y nos permite especificar que este extremo se corresponde con alguno de los extremos ‘estándar’ que tenemos configurados. Algunos ejemplos adicionales de estos extremos son timerServiceNotificationExpiredEndpoint,timerServiceEndpoint o announcementEndpoint.

Una vez que hemos agregado nuestro nuevo extremo al servicio solamente tendremos que agregar al mismo un nuevo comportamiento, denominado ServiceDiscovery.

<serviceBehaviors>
                <behavior name="SampleServiceBehavior">
                    <serviceDiscovery />
                </behavior>
</serviceBehaviors>

 

Con estos dos sencillos pasos nuestro servicio ya será capaz de aceptar mensajes de tipo PROBE y responder con la información de los extremos en los que está escuchando.

NOTA: El documento de especificación de WS-Discovery contiene una completa documentación acerca de los formatos de mensajes PROBE y PROBE MATCH que le recomiendo revisar.

 

Llegados a este momento en el que ya tenemos configurado nuestro servicio para aceptar peticiones PROBE veremos cómo obtener y/o descubrir la información de los mismos de forma AdHoc. Para ello nos serviremos de la clase DiscoveryClient situada en el espacio de nombres System.ServiceModel.Discovery, tal y como vemos en el siguiente ejemplo de código:

 

static void Discovery()
        {
            DiscoveryClient discoveryClient = new DiscoveryClient(new UdpDiscoveryEndpoint());
            discoveryClient.FindCompleted +=new EventHandler<FindCompletedEventArgs>(clientDiscovery_FindCompleted);
            discoveryClient.FindProgressChanged +=new EventHandler<FindProgressChangedEventArgs>(clientDiscovery_FindProgressChanged);

            discoveryClient.FindAsync(new FindCriteria(typeof(ISampleService)));
        }
static void clientDiscovery_FindCompleted(object sender, FindCompletedEventArgs e)
        {
            Console.WriteLine("Busqueda completada!");
            foreach (var item in e.Result.Endpoints)
            {
                Console.WriteLine("Endpoint listening At:{0}",item.Address);
            }
        }

        static void clientDiscovery_FindProgressChanged(object sender, FindProgressChangedEventArgs e)
        {
            Console.WriteLine("Buscando :{0}%",e.ProgressPercentage);
        }

 

 

Fíjese que en la especificación de la clase FindCriteria podemos establecer el contrato que los servicios que queremos buscar tienen que cumplir. Como elementos adicionales, FindCriteria también nos permite establecer un máximo de duración en la búsqueda o los Scopes de servicio que se tienen que cumplir.

Publicado por Unai | 1 comment(s)

WCF REST /Conditional GET: Save Bandwidth

Aunque para muchos es desconocido, el estilo REST es cada vez más usado dentro de muchas aplicaciones, numerosos son los ejemplos, Data Services, muchas de las API’s de AZURE y por su puesto en otras plataformas como Google, Amazon etc….

La llegada de .NET 3.5 nos permitió crear servicios REST de una forma simétrica a como hacíamos nuestros servicios SOAP, es lo que tiene tener una arquitectura extensible y plugable, gracias a unos pequeños cambios en los protocolos de mensajería y dispatcher ( básicamente establecer SoapVersion a None y asignación de métodos a ejecutar por medio de identificadores de recursos y no por la cabecera Action  de los sobres). Una de las cosas que no se suelen comentar dentro de los servicios REST es la posibilidad de tocar las cabeceras HTTP para obtener muchas de las posiblidades que los programadores Web ya conocen y utilizan habitualmente. A lo largo de este artículo hablaremos de un par de cabeceras interesantes que tiene que ver con el manejo de la cache como son Is-Modified-Since y If-None-Match. Estas dos cabeceras vienen a resolver ciertos problemas de cache cómo son la imposibilidad de los clientes de de tolerar ciertos estados (Cache-Control,Pragma ) o expiraciones de ‘caching hints’ como Expires.

Como bien dice la especificación de HTTP sobre temas de cache, la mejora de disponer de cache no solamente viene por la eliminación de peticiones al servidor sino también por eliminar las respuestas completas que estos nos den. Las cabeceras mencionadas anteriormente tiene este último propósito, eliminar respuestas completas, por medio del uso de un código de estado 304, también conocido como Not-Modified. Los que me conocéis sabéis que me resulta mucho más sencillo explicar las cosas con algún ejemplo de código por lo que vamos al trabajoJ.

Vamos a suponer que disponemos de una interfaz de servicio REST muy simple, como la mostrada a continuación:

[ServiceContract()]
public interface IUserService
{
    [OperationContract()]
    [WebGet(UriTemplate="Users/{userId}")]
    User GetUser(string userId);
}

Si alojamos este servicio convenientemente y hacemos repetidas peticiones a ‘http://dominio/servicio/Users/13’ podremos ver, mediante alguna herramienta de inspección del tráfico HTTP como Fiddler, como esta petición es procesada en el servidor y este devuelve siempre respuestas completas.

 

REST_Figure1

Nuestra idea de uso de las cabeceras anteriormente mencionadas es la de disponer de un mecanismo para averiguar si lo que el cliente de nuestros servicios REST está pidiendo es un elemento previamente consultado y no modificado en el servidor.

Empezaremos viendo como incorporar Is-Modified-Since dentro del código de nuestro método de servicio, ver código siguiente:

public User GetUser(string userId)
{
      //obtenemos los contextos de peticion y respuesta HTTP
      IncomingWebRequestContext incomingContext = WebOperationContext.Current.IncomingRequest;

      OutgoingWebResponseContext outgoingContext = WebOperationContext.Current.OutgoingResponse;

      //Obtenemos el usuario pedido
      User user = FindUser(userId);

blecemos los valores de las cabeceras Is-Modified-            //Since y If-None-Match

      outgoingContext.LastModified = user.LastModified;
      //outgoingContext.ETag = user.ETag.ToString();

      user = CheckIfModfiedSince(user, incomingContext, outgoingContext);
      //user = CheckIfNoneMatch(user, incomingContext, outgoingContext);

      return user;
}

 

Tal y como podrá observar en el fragmento de código anterior una vez obtenido el usuario solicitado establecemos dentro del contexto de respuesta al cliente el elemento LastModified, valor de la cabecera Is-Modified-Since. Con esta simple opción conseguimos que el cliente reciba el valor de esta header y pueda entregárnosla en futuras peticiones para verificar si le damos una respuesta completa o bien un código de estado 304 y obtenga el resultado de la petición de la caché del navegador, por ejemplo.

Volvamos a realizar un par de peticiones ahora con este pequeño cambio y veremos como la respuesta del servidor incluye nuestra nueva cabecera.

 

 

REST_Figure2

 

Una vez que ya sabemos cómo enviar nuestra cabecera solamente nos queda verificar el valor de la misma dentro del código de nuestro servicio con el fin de determinar si al cliente le enviamos la respuesta completa o el valor de ‘No hay cambios en la petición’. Esto lo conseguimos dentro del método de nuestro código anterior ChechIfModifiedSince que presentamos a continuación.

 

private User CheckIfModfiedSince(User user, IncomingWebRequestContext incomingContext, OutgoingWebResponseContext outgoingContext)
{
            string lastModified = incomingContext.Headers[HttpRequestHeader.IfModifiedSince];

            if (lastModified != null)
            {
                //Si no se ha modificado establecemos el estado NotModified en el protocolo HTTP
                //Comprobamos si el usuario ha cambiado
                if (IsNotModified(user.LastModified, DateTime.Parse(lastModified)))
                {
                    //Establecemos que no hay modificaciones
                    outgoingContext.SuppressEntityBody = true; // la peticion no devolverá nada
                    outgoingContext.StatusCode = HttpStatusCode.NotModified;
                    return null;
                }
            }

            return user;
}

 

Fíjese como este método lo único que realiza es la comparación de la header con un valor, en nuestro caso por sencillez dentro del elemento User, que nos permita evaluar si el elemento ha cambiado comparando para ello un par de elementos de tipo DateTime y estableciendo como código de salida el valor 304, NotModified, en caso afirmativo. Hecho este trabajo puede probar a realizar distintas peticiones al servidor y verá como este mecanismo reduce el ancho de banda necesario para nuestros servicios puesto que las respuestas no serán completas sino un simple código 304.

El funcionamiento de la cabecera If-None-Match es realmente similar a If-Modified-Since, la principal diferencia es que la comparación de cambio no se realiza sobre un campo fecha sino sobre un elemento conocido como etag o entity tag, el cual no es más que la expresión de una entidad en un punto en el tiempo, pudiendo establecerse como un hash un guid o lo que nosotros deseemos. Este valor de etag es el que utilizaremos para saber si una entidad ha cambiado y proceder a dar una respuesta completa al servidor o bien devolver un 304 como anteriormente realizamos.

 

private User CheckIfNoneMatch(User user, IncomingWebRequestContext incomingContext, OutgoingWebResponseContext outgoingContext)
{
    string etag = incomingContext.Headers[HttpRequestHeader.IfNoneMatch];
    if (etag != null)
    {
         if (IsNotModified(new Guid(etag), user.ETag)){
             //Establecemos que no hay modificaciones
             outgoingContext.SuppressEntityBody = true;
             // la peticion no devolverá nada
             outgoingContext.StatusCode = HttpStatusCode.NotModified;
             return null;
         }
    }
    return user;
}

 

P.D: Entity Tag también es utilizado en ADO.NET Data Services para manejar la concurrencia optimista, podéis ver una referencia aqui

Espero que os resulte de utilidad,

Unai Zorrilla

Publicado por Unai | 2 comment(s)
Archivado en:

MVP Summit 09, Don Box, Danny Simmons, Diego vega…

Tal y como comentaba Rodrigo, ya estamos de vuelta del MVP Summit 09, del que personalmente he disfrutado mucho, no solamente por la gran cantidad de charlas y reuniones que me meti en el cuerpo, y de las cuales disfruté enormemente sino también por ver como amigos como Miguel Llopis movían todos sus hilos para poder ofrecernos a todos los MVP de habla castellana una excelentísima sesión sobre OSLO del cual el es un enorme conocedor, como se puede reflejar el hecho de que el lenguaje de Oslo, M, toma por nombre la primera de sus letras.

Además de todo el trabajo, también hubo tiempo para el placer, y  este, vino de la mano de diversas excelentes entrevistas que pudimos hacer para DotNetMania, junto con mi buen amigo Octavio.

WithDannyAndDiego

Empezando las mismas con Danny Simmons y Diego Vega, de sobra conocidos para los amantes de EF, de hecho Diego fué uno de los revisores de nuestro libro sobre el tema, con los que tuvimos la oportunidad de charlar profundamente del presente y el futuro de la tecnología, amén de todas las ponencias impartidas :-), y cuyos consejos y noticias podréis ver en el número de Abril de Dotnetmania.

 

 

Las dos siguientes entrevistas realizadas,  tampoco tienen su desWithDonperdicio, por un lado un personaje poco conocido en este mundo como es un tal Don Box, entrevista para el més de Mayo y otro elemento desconocido como Paul Vick ( entrevista de Junio). A los que no estáis subscritos a DotnetMania no se que estáis esperando :-)

WithPaul

 

Saludos con Jet-Lag !!

Unai

Publicado por Unai | 6 comment(s)
Más artículos Página siguiente >