26 de diciembre de 2014

WCF - Entity Framework - AutoMapper(1)

Automapper sirve para hacer mapeos entre objetos .NET, ahorrandonos gran cantidad de código y que provee algunas utilidades para personalizar cómo se realiza dicho mapeo entre objetos. Realizar mapeo entre objetos en una funcionalidad común en muchos de nuestros desarrollos, ya que permiten personalizar la salida de los datos evitando revelar información confidencial y dando al cliente solo la información que él realmente necesita.

En esta entrada se muestra el uso de la funcion mas basica de AutoMapper: Flattening, para eso instalamos el automapper desde la consola de Nuget escribiendo: Install-Package AutoMapper -Version 3.3.0 una vez instalado creamos un servicio de WCF, le agregamos un modelo de base de datos utilizando el ORM Entity Framework y separamos las entidades creadas por el EF en una biblioteca de clases, tal como se desarrollo en este enlace

El modelo de la base de datos tiene las siguientes entidades

Pero no queremos mandar todos las propiedades de esta entidad, ya que el campo CreadoPor y FechaCreacion sirven para control interno ademas que los datos ingresados deben ser validados para mantener la integridad de la base de datos, por lo que la entidad que nos interesa mandar al cliente solo deberia contener las propiedades CodigoEmp, NombreEmp y CargoEmp, es para esto que se utilizara el AutoMapper, para crear un objeto con las propiedades que me interesan(al que llamare EmployeeDTO) a partir de otro objeto similar(Employee)

Para esto se agregaran dos clases a la bilbioteca de clases: EmployeeDTO y ProjectDTO, el solution Explorer tendra dos proyectos: lunes(que tendra el modelo de base de datos y el servicio WCF a hospedar en IIS) y la bilbioteca de clases: Clases(que tendra las entidades creadas por el EF en Modelo.tt y las clases DTO que nosotros crearemos), queda de la siguiente manera:


la clase EmployeeDTO  de nuestra autoria y la clase Employee generada por el ORM ENtity Framework quedan asi:

EmployeeDTO.cs

Employee.cs

Con esto ya tenemos dos objetos que no comparten todas las propiedades pero si algunas, es importante aclarar que la configuración de las transformaciones solo hay que realizarla una vez por AppDomain por eso ahora debemos agregar dos clases mas al proyecto: AutomapBootstarp.cs y AutomapServiceBehaviour.cs que se muestran a continuacion, fijarse en las referencias y espacios de nombre utilizados

AutomapBootstarp.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using Clases;
using AutoMapper;

namespace lunes{
    public class AutomapBootstrap{
        public static void InitializeMap(){
            Mapper.CreateMap<Employee, EmployeeDTO>().ReverseMap();
           
        }}}


AutomapServiceBehaviour.cs
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.Web;

namespace lunes{
    public sealed class AutomapServiceBehavior : Attribute, IServiceBehavior{
        public AutomapServiceBehavior(){}

        public void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase,
            Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters)
        {
            AutomapBootstrap.InitializeMap();
        }

        public void ApplyDispatchBehavior(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase)
        { }

        public void Validate(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase)
        { }

    }}
 

Ahora el desarrollo de la interfaz del servicio, lo unico que hara es enviar un listado de EmployeeDTO para que sea mostrado en el DataGridView de un cliente, la interfaz ILunesService.cs y de la clase que lo implementa LunesService.svc.cs se muestra

ILunesService.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
using Clases;
using System.Collections;

namespace lunes{
 
    [ServiceContract]
    public interface ILunesService{
        [OperationContract]
        List<EmployeeDTO> Listado();       
    }}
 

En la clase LunesService.svc.cs se debe colocar la etiqueta [AutomapServiceBehavior]
LunesServicio.svc.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
using AutoMapper;
using System.Data.Entity;
using Clases;
using System.Web;

namespace lunes{
      [AutomapServiceBehavior]
   // [ServiceErrorBehavior(typeof(ElmahErrorHandler))]
   // [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
    public class LunesService : ILunesService
    {
    public LunesService(){}
       
        public List<EmployeeDTO> Listado(){
            conexion cnn = new conexion();
            var datos = (from emp in cnn.Employees
                         select emp).ToList();
            List<EmployeeDTO> lista = Mapper.Map<List<Employee>, List<EmployeeDTO>>(datos);
            return lista;
       
    }}}

Ahora se hospedara el servicio en IIS tal como se muestra en este enlace

El siguiente paso es crear un cliente que consumira el servicio, para este ejemplo se debe abrir Visual Studio y crear una aplicacion Windows Forms con un formulario y un DataGridView, se hace la referencia al servicio al cual llamare a su referencia zerbixio(para referencia este enlace) y este sera el codigo para cargar el DataGridView con los datos que tenga EmployeeDTO

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace datosEJ{
    public partial class Form1 : Form{
        public Form1(){  InitializeComponent();  }

        private void Form1_Load(object sender, EventArgs e){
            try{
                zerbixio.LunesServiceClient clase = new zerbixio.LunesServiceClient();
                dataGridView1.DataSource = clase.Listado();
            }
            catch (Exception ex){ MessageBox.Show(ex.Message); }
          
        }}}

con esto se obtiene el resultado deseado


Datos complementarios: uso del ForMember cuando los objetos tienen el mismo tipo de datos y hacen alusion al mismo dato pero se denominan diferente(primeras cuatro lineas del ejemplo) o bien se quiere la combinacion de valores desde el objeto fuente(ultima linea del ejemplo, en este ultimo caso se puede agregar una propiedad nueva al objeto destino, sin necesidad que haga referencia a alguna propiedad en el objeto origen, por ejemplo Double CostaAlCliente no existe en la clase origen sino mas bien se agrego de tipo Double para mostrar una combinacion de valores de la clase origen.

Mapper.CreateMap<Empleado, EmpleadoOferta>()
                .ForMember(dest => dest.Nombre, fte => fte.MapFrom(src => src.Nombre))
                .ForMember(dest => dest.Id, fte => fte.MapFrom(src => src.Id))
                .ForMember(dest => dest.AnnosExperiencia, fte => fte.MapFrom(src => src.Experiencia))
                .ForMember(dest => dest.Especialidad, fte => fte.MapFrom(src => src.Especialidad))
                .ForMember(dest => dest.CostoAlCliente, fte => fte.MapFrom(src => (src.SalarioXHora * 0.40) + src.SalarioXHora));