WCF bajo Contrato (4/4)


Contrato de Fallo

Cualquier operación petición/respuesta devuelve dos posibles mensajes: su mensaje de respuesta normal, o bien un mensaje de error. Para enviar un mensaje de error (un «SOAP Fault» en términos de servicios web) se lanza una FaultException en la implementación del servicio, tal y como se ha ejemplificado hasta ahora.

Pero además se pueden especificar formalmente los mensajes de error utilizando tipos personalizados con un contrato de datos adecuado, dado que se serializarán mediante el DataContractSerializer. El tipo a utilizar en el mensaje de error se especifica contractualmente en la firma de la operación utilizando el atributo FaultContract, mientras que en la implementación de la operación se genera mediante una FaultException del tipo especificado en el FaultContract.

Como ejemplo suponga que deseamos especificar un mensaje de error, que nos informe sobre un error en el parámetro de la petición, utilizando el siguiente tipo con su contrato de datos:

Listado de HelloData.cs (fragmento)

[DataContract(Namespace="http://hello/data/")]
public class InvalidRequest
{
  private int id;
  private string message;
  private string notes;

  [DataMember()]
  public int ID
  {
    get { return id; }
    set { id = value; }
  }

  [DataMember()]
  public string Message
  {
    get { return message; }
    set { message = value; }
  }				

  [DataMember()]
  public string Notes
  {
    get { return notes; }
    set { notes = value; }
  }				
}

El fragmento de código se agrega al archivo HelloData.cs, tras lo cual lo compilamos.

Para especificar el uso del tipo InvalidRequest como tipo de fallo sobre la operación, aplicamos el atributo FaultContract a la firma de la operación:

Listado de Hello.cs (fragmento)

[ServiceContract(Namespace="http://hello/", Name="Hello")]
interface IHello
{
  [OperationContract()]
  [FaultContract(typeof(InvalidRequest))]
  SayHelloResponse SayHello(SayHelloRequest request);
}

Después en la clase de implementación del servicio se lanzan las excepciones correspondientes:

Listado de Hello.cs (fragmento)

  [ServiceBehavior(Namespace="http://hello/", Name="HelloService")]
  public class Hello : IHello
  {
    private StringBuilder message = new StringBuilder("¡Hola ");

    private void ValidateRequest(SayHelloRequest request)
    {
      if (request.PersonParameter == null)
      {
        InvalidRequest fault = new InvalidRequest();
        fault.ID = 1;
        fault.Message = "Parámetro Person inválido";
        fault.Notes = "El objeto Person no puede ser nulo...";
        throw new FaultException<InvalidRequest>(fault, "Parámetro inválido.");
      }
      else if (request.PersonParameter.FirstName == null ||
        request.PersonParameter.FirstName.Length == 0)
      {
        InvalidRequest fault = new InvalidRequest();
        fault.ID = 2;
        fault.Message = "Miembro FirstName inválido";
        fault.Notes = "El miembro FirstName del objeto Person no puede ser nulo o vacío...";
        throw new FaultException<InvalidRequest>(fault, "Parámetro inválido.");
      }
    }

    public SayHelloResponse SayHello(SayHelloRequest request)
    {
      ValidateRequest(request);

      message.Append(request.PersonParameter.FirstName + "!");
      string msg = message.ToString();
      SayHelloResponse response = new SayHelloResponse();
      HelloCard card = new HelloCard();
      card.Greeting = msg;
      response.HelloCardResult = card;
      System.Console.WriteLine(msg);
      return response;
    }
  }

Nótese que hemos extraído el código de validación del request en un método privado, en el cual se crea una instancia de InvalidRequest, se establece la información pertinente, y se lanza un FaultException con dicha instancia. Note además que se hace el uso de la versión «genérica» de FaultException para ello.

Compilamos Hello.cs, y para terminar del lado del servicio sólo ejecutamos la aplicación host.

Para el cliente una vez más se regeneran los artefactos cliente con svcutil.exe.

Después, aunque el código de la aplicación cliente podría funcionar sin cambios, para poder consumir los mensajes adicionales de error (de tipo InvalidRequest) ahora agregaremos un catch para «atrapar» el FaultException de tipo InvalidRequest. El listado de Client.cs queda de la siguiente manera:

Listado de Client.cs

using System;
using System.ServiceModel;

public class Client
{
  static void Main(string[] args)
  {
    String arg = null;
    String result = null;
    if (args.Length > 0) {
      arg = args[0];
    }
    else {
      arg = "Anónimo";
    }
    try {
      hello.Hello service = new hello.HelloClient();
      hello.data.Person person = new hello.data.Person();
      person.FirstName = arg;
      hello.SayHelloRequest request = new hello.SayHelloRequest();
      request.Person = person;
      hello.SayHelloResponse response = service.SayHello(request);
      result = response.HelloCard.Greeting;
    }
    catch (FaultException<hello.data.InvalidRequest> helloFault)
    {
      result = String.Format("{0}\nID: {1}\nMensaje: {2}\nNotas: {3}",
                helloFault.Message,
                helloFault.Detail.ID,
                helloFault.Detail.Message,
                helloFault.Detail.Notes);
    }
    catch (FaultException fault) {
      result = "Fault: " + fault.Message;
    }
    catch (Exception ex) {
      result = "Error: " + ex.ToString();
    }
    finally{
      System.Console.WriteLine(result);
    }
  }
}

Observe que para acceder a los miembros de la instancia de InvalidRequest enviada utilizamos la propiedad Detail de la instancia de FaultException helloFault.

Compilamos el proxy y el cliente, y ejecutamos Client.exe. Pruebe las excepciones modificando el código de la aplicación cliente, ya sea estableciendo a nulo el valor del miembro FirstName o el parámetro Person, y volviendo a compilar y ejecutar…

Epílogo

Con los ejemplos vistos en la presente serie se han explorado brevemente los diferentes tipos de contratos de WCF: contrato de servicio, contrato de datos, contrato de mensajes y contrato de fallo. Los mismos se definen aplicando ciertos atributos a clases y sus miembros. Los atributos permiten parámetros por nombre, tales como el Namespace y Name,  para una mejor definición del elemento al cual se aplica. Queda como tarea para el lector que indague sobre dichos parámetros, ya que existen muchos otros en cada uno de los distintos atributos.

Quedan pendientes entonces varias cosas, además del nuevo cliente Java, mismas que espero abordar en futuras ocasiones. ¡Hasta entonces!

Acerca de Willy Mejia

Developer, Techie, Human... http://about.me/willyxoft
Esta entrada fue publicada en .NET, NetFx3, WCF. Guarda el enlace permanente.

Deja un comentario