Desarrollando un Servicio REST sobre WCF

Publicada en Publicada en C#, Código, REST, SQL Server, Visual Studio, WCF

Compartelo con tus amigos!

Hola a todos!

En la siguiente entrada estaremos viendo paso a paso como desarrollar un servicio REST sobre WCF.

Donde tendremos como objetivo:

  • Realizar el CRUD de la entidad Actividad.cs
  • Implementar pruebas unitarias del servicio Actividades.svc

Habiendo definido nuestros objetivos, comencemos.

Para este ejemplo, usaremos: SQL Server 2017 y Visual Studio 2017 Community (bajo C#).

En primer lugar, crearemos un proyecto del tipo Aplicación  de servicios WCF, el cual encontraremos en WCF. Lo llamaremos PeruNETDevelopment.Rest. (Figura 1)

(Figura 1)

Luego de crear nuestro proyecto, observaremos la siguiente estructura en la ventana del explorador de soluciones. (Figura 2)

(Figura 2)

Procedemos a eliminar IService1.cs y Service1.svc (interfaz y servicio respectivamente) generados por default en el proyecto.

A continuación, adicionaremos un nuevo elemento del tipo Servicio WCF, al cual llamaremos Actividades.svc. (Figura 3)

(Figura 3)

Seguidamente verificaremos que se haya creado la interfaz IActividades.cs y servicio Actividades.svc. (Figura 4)

(Figura 4)

Por consiguiente, añadiremos 3 folders a la solución:

  • Dominio: Contendrá la clase Actividad.cs
  • Errores: Contendrá la clase ActividadException.cs
  • Persistencia: Contendrá la clase ActividadDAO.cs

Ahora, nuestra estructura del proyecto es la siguiente. (Figura 5)

(Figura 5)

Editaremos el archivo Actividad.cs, donde usaremos las siguientes anotaciones:

  • [DataContract]: Es un acuerdo formal entre un servicio y un cliente que describe de manera abstracta los datos que se intercambiarán.
  • [DataMember]:  Cuando se aplica al miembro de un tipo, especifica que el miembro forma parte de un DataContract y es serializable por el DataContractSerializer.
using System.Runtime.Serialization;

namespace PeruNETDevelopment.Rest.Dominio
{
    [DataContract]
    public class Actividad
    {
        [DataMember]
        public string nActividad { get; set; }
        [DataMember]
        public string cCliente { get; set; }
        [DataMember]
        public string cUsuario { get; set; }
        [DataMember]
        public string cTipoActividad { get; set; }
        [DataMember]
        public string dDescripcion { get; set; }
        [DataMember]
        public string dAsunto { get; set; }
        [DataMember]
        public string sEstado { get; set; }
    }
}

Editaremos el archivo ActividadException.cs, donde usaremos la anotación DataContract y para cada atributo DataMember:

using System.Runtime.Serialization;

namespace PeruNETDevelopment.Rest.Errores
{
    [DataContract]
    public class ActividadException
    {
        [DataMember]
        public int Codigo { get; set; }
        [DataMember]
        public string Descripcion { get; set; }
    }
}

Editaremos el archivo ActividadDAO.cs, agregando lo siguiente:

private string CadenaConexion = "Data Source=(local);Initial Catalog=DB_DSD;User ID=usrqas;Password=***;";

En esta clase (ActividadDAO), realizaremos la CRUD. Se tendrán los siguientes métodos:

  • Crear
public Actividad Crear(Actividad actividadACrear)
{
    Actividad actividadCreada = null;
    string sql = "Insert into actividad (nActividad, cCliente, cUsuario, cTipoActividad, dDescripcion, dAsunto) " +
        "values(@nactividad, @ccliente, @cusuario, @ctipoactividad, @ddescripcion, @dasunto)";
    using (SqlConnection conexion = new SqlConnection(CadenaConexion))
    {
        conexion.Open();
        using (SqlCommand comando = new SqlCommand(sql, conexion))
        {
            comando.Parameters.Add(new SqlParameter("@nactividad", actividadACrear.nActividad));
            comando.Parameters.Add(new SqlParameter("@ccliente", actividadACrear.cCliente));
            comando.Parameters.Add(new SqlParameter("@cusuario", actividadACrear.cUsuario));
            comando.Parameters.Add(new SqlParameter("@ctipoactividad", actividadACrear.cTipoActividad));
            comando.Parameters.Add(new SqlParameter("@ddescripcion", actividadACrear.dDescripcion));
            comando.Parameters.Add(new SqlParameter("@dasunto", actividadACrear.dAsunto));
            comando.ExecuteNonQuery();
        }
    }
    actividadCreada = Obtener(actividadACrear.nActividad);
    return actividadCreada;
}
  • Obtener
public Actividad Obtener(string nActividad)
{
    Actividad actividadEncontrada = null;
    string sql = "select * from actividad where nActividad = @nactividad";
    using (SqlConnection conexion = new SqlConnection(CadenaConexion))
    {
        conexion.Open();
        using (SqlCommand comando = new SqlCommand(sql, conexion))
        {
            comando.Parameters.Add(new SqlParameter("@nactividad", nActividad));
            using (SqlDataReader resultado = comando.ExecuteReader())
            {
                if (resultado.Read())
                {
                    actividadEncontrada = new Actividad()
                    {
                        nActividad = (string)resultado["nactividad"],
                        cCliente = (string)resultado["cCliente"],
                        cUsuario = (string)resultado["cUsuario"],
                        cTipoActividad = (string)resultado["cTipoActividad"],
                        dDescripcion = (string)resultado["dDescripcion"],
                        dAsunto = (string)resultado["dAsunto"],
                        sEstado = (string)resultado["sEstado"],
                    };
                }
            }
        }

    }
    return actividadEncontrada;
}
  • Obtener
public Actividad Modificar(Actividad actividadAModificar)
{
    Actividad actividadModificada = null;
    string sql = "update actividad set ctipoactividad = @ctipoactividad, ddescripcion = @ddescripcion, " +
        "dasunto = @dasunto, sestado = @sestado where nactividad = @nactividad";
    using (SqlConnection conexion = new SqlConnection(CadenaConexion))
    {
        conexion.Open();
        using (SqlCommand comando = new SqlCommand(sql, conexion))
        {
            comando.Parameters.Add(new SqlParameter("@ctipoactividad", actividadAModificar.cTipoActividad));
            comando.Parameters.Add(new SqlParameter("@ddescripcion", actividadAModificar.dDescripcion));
            comando.Parameters.Add(new SqlParameter("@dasunto", actividadAModificar.dAsunto));
            comando.Parameters.Add(new SqlParameter("@sestado", actividadAModificar.sEstado));
            comando.Parameters.Add(new SqlParameter("@nactividad", actividadAModificar.nActividad));
            comando.ExecuteNonQuery();
        }
    }
    actividadModificada = Obtener(actividadAModificar.nActividad);
    return actividadModificada;
}
  • Eliminar
public void Eliminar(string nActividad)
{
    string sql = "delete from actividad where nActividad = @nactividad";
    using (SqlConnection conexion = new SqlConnection(CadenaConexion))
    {
        conexion.Open();
        using (SqlCommand comando = new SqlCommand(sql, conexion))
        {
            comando.Parameters.Add(new SqlParameter("@nactividad", nActividad));
            comando.ExecuteNonQuery();
        }
    }
}
  • Listar
public List<Actividad> Listar()
{
    List<Actividad> actividadesEncontradas = new List<Actividad>();
    Actividad actividadEncontrada = null;
    string sql = "select * from actividad";
    using (SqlConnection conexion = new SqlConnection(CadenaConexion))
    {
        conexion.Open();
        using (SqlCommand comando = new SqlCommand(sql, conexion))
        {
            using (SqlDataReader resultado = comando.ExecuteReader())
            {
                while (resultado.Read())
                {
                    actividadEncontrada = new Actividad()
                    {
                        nActividad = (string)resultado["nactividad"],
                        cCliente = (string)resultado["cCliente"],
                        cUsuario = (string)resultado["cUsuario"],
                        cTipoActividad = (string)resultado["cTipoActividad"],
                        dDescripcion = (string)resultado["dDescripcion"],
                        dAsunto = (string)resultado["dAsunto"],
                        sEstado = (string)resultado["sEstado"]
                    };
                    actividadesEncontradas.Add(actividadEncontrada);
                }
            }
        }

    }

    return actividadesEncontradas;
}
  • Listar Actividades Por usuario y estado * Método adicional de búsqueda
public List<Actividad> ListarActividadesPorUsuarioEstado(string cUsuario, string sEstado)
{
    List<Actividad> actividadesEncontradas = new List<Actividad>();
    Actividad actividadEncontrada = null;
    string sql = "select * from actividad where cusuario = @cusuario and sestado = @sestado";
    using (SqlConnection conexion = new SqlConnection(CadenaConexion))
    {
        conexion.Open();
        using (SqlCommand comando = new SqlCommand(sql, conexion))
        {
            comando.Parameters.Add(new SqlParameter("@cusuario", cUsuario));
            comando.Parameters.Add(new SqlParameter("@sestado", sEstado));
            using (SqlDataReader resultado = comando.ExecuteReader())
            {
                while (resultado.Read())
                {
                    actividadEncontrada = new Actividad()
                    {
                        nActividad = (string)resultado["nactividad"],
                        cCliente = (string)resultado["cCliente"],
                        cUsuario = (string)resultado["cUsuario"],
                        cTipoActividad = (string)resultado["cTipoActividad"],
                        dDescripcion = (string)resultado["dDescripcion"],
                        dAsunto = (string)resultado["dAsunto"],
                        sEstado = (string)resultado["sEstado"]
                    };
                    actividadesEncontradas.Add(actividadEncontrada);
                }
            }
        }

    }
    return actividadesEncontradas;
}

Editaremos el archivo IActividades.cs, donde usaremos lo siguiente:

  • [ServiceContract]: Indica que una interfaz o una clase define un contrato de servicio en una aplicación Windows Communication Foundation (WCF).
  • [OperationContract]: Indica que un método define una operación que forma parte de un contrato de servicio en una aplicación Windows Communication Foundation (WCF).
  • [WebInvoke]: Se aplica a una operación de servicio además de OperationContract y asocia la operación con un UriTemplate así como un verbo de transporte subyacente que representa una invocación (por ejemplo, HTTP POST, PUT o DELETE).
[OperationContract]
[WebInvoke(Method = "POST", UriTemplate = "Actividades", ResponseFormat = WebMessageFormat.Json)]
Actividad CrearActividad(Actividad actividadACrear);

[OperationContract]
[WebInvoke(Method = "GET", UriTemplate = "Actividades/{nActividad}", ResponseFormat = WebMessageFormat.Json)]
Actividad ObtenerActividad(string nActividad);

[OperationContract]
[WebInvoke(Method = "PUT", UriTemplate = "Actividades", ResponseFormat = WebMessageFormat.Json)]
Actividad ModificarActividad(Actividad actividadAModificar);

[OperationContract]
[WebInvoke(Method = "DELETE", UriTemplate = "Actividades/{nActividad}", ResponseFormat = WebMessageFormat.Json)]
void EliminarActividad(string nActividad);

[OperationContract]
[WebInvoke(Method = "GET", UriTemplate = "Actividades", ResponseFormat = WebMessageFormat.Json)]
List<Actividad> ListarActividades();

[OperationContract]
[WebInvoke(Method = "GET", UriTemplate = "Actividades/{cusuario}/{sestado}", ResponseFormat = WebMessageFormat.Json)]
List<Actividad> ListarActividadesPorUsuarioEstado(string cUsuario, string sEstado);

Editaremos Actividades.svc, donde se han mapeado los siguientes código de errores:

  • Código 101: No se puede crear una actividad duplicada.
  • Código 102: No se puede modificar una actividad que no existe.
  • Código 103: No se puede eliminar una actividad que haya sufrido movimientos (en contexto del ejemplo, tenga más de un cambio de estado, ejm: Creada (C), Asignada (A), etc).
  • Código 104: (método de búsqueda adicional.) No se puede buscar las actividades por código de usuario o estado de actividad sí el usuario se encuentra inactivo.
using PeruNETDevelopment.Rest.Dominio;
using PeruNETDevelopment.Rest.Errores;
using PeruNETDevelopment.Rest.Persistencia;
using System.Collections.Generic;
using System.Net;
using System.ServiceModel.Web;

namespace PeruNETDevelopment.Rest
{
    public class Actividades : IActividades
    {
        private ActividadDAO actividadDAO = new ActividadDAO();

        public Actividad CrearActividad(Actividad actividadACrear)
        {
            Actividad actividadExistente = actividadDAO.Obtener(actividadACrear.nActividad);
            if (actividadExistente != null)
            {
                throw new WebFaultException<ActividadExceptio>(new ActividadException()
                {
                    Codigo = 101,
                    Descripcion = "Actividad duplicada"
                }, HttpStatusCode.Conflict);
            }

            return actividadDAO.Crear(actividadACrear);
        }

        public Actividad ObtenerActividad(string nActividad)
        {
            return actividadDAO.Obtener(nActividad);
        }

        public Actividad ModificarActividad(Actividad actividadAModificar)
        {
            Actividad actividadExistente = actividadDAO.Obtener(actividadAModificar.nActividad);

            if (actividadExistente != null)
            {
                throw new WebFaultException<ActividadException>(new ActividadException()
                {
                    Codigo = 102,
                    Descripcion = "Actividad no existe"
                }, HttpStatusCode.Conflict);
            }

            return actividadDAO.Modificar(actividadAModificar);
        }

        public void EliminarActividad(string nActividad)
        {
            Actividad actividadExistente = actividadDAO.Obtener(nActividad);

            if (actividadExistente.sEstado != "C")
            {
                throw new WebFaultException<ActividadException>(new ActividadException()
                {
                    Codigo = 103,
                    Descripcion = "Actividad con movimientos"
                }, HttpStatusCode.Conflict);
            }

            actividadDAO.Eliminar(nActividad);
        }

        public List>Actividad> ListarActividades()
        {
            return actividadDAO.Listar();
        }

        public List<Actividad> ListarActividadesPorUsuarioEstado(string cUsuario, string sEstado)
        {
            Actividad validaUsuario = actividadDAO.ValidarUsuario(cUsuario);
            if (validaUsuario.sEstado == "I")
            {
                throw new WebFaultException<ActividadException>(new ActividadException()
                {
                    Codigo = 104,
                    Descripcion = "Usuario Inactivo"
                }, HttpStatusCode.Conflict);
            }
            return actividadDAO.ListarActividadesPorUsuarioEstado(cUsuario, sEstado);
        }
    }
}

Muy bien, hasta este punto tenemos:

  • CRUD de la entidad Actividad(ejemplo básico)
  • Implementación del servicio Actividad

Ahora pasaremos a probar nuestro servicio, para ello haremos lo siguiente:
(Figura 6)

  1. Clic derecho sobre Actividades.svc
  2. Clic en Ver en Explorador (Google Chrome)

Figura 6

Luego de ello, tendremos la siguiente pantalla: (Figura 7)

  • No se asusten, el servicio levanto sin errores 😀


(Figura 7)

Ahora, para probar todos los métodos del servicio usaremos la extensión Advanced REST client (en google chrome): (Figura 8)

(Figura 8)

Bien! Ahora daremos clic en Iniciar aplicación y veremos la interfaz para probar el servicio: (Figura 9)

(Figura 9)

Finalmente, probaremos el método ListarActividades:

  • Method: GET
  • Request URL; http://localhost:8186/Actividades.svc/Actividades
  • Header Name: Content-Type
  • Header Value: application/json

¡Esto es todo por ahora!

Continuaremos con la implementación de las pruebas unitarias en una siguiente entrada.

Para encontrar el proyecto pueden dar clic aquí.

Compartelo con tus amigos!

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *