WCF TIPS - Como generar una descarga http estándar y hostearla desde un servicio

por maxi  20. septiembre 2010

    Objectivo: Crear un ejemplo de servicio con WCF que permita a un navegador descargar un archivo por http en forma estándar.

    Características del servicio:

    • Self-Host: En este caso va a ser Self-host  en una aplicacion Winforms asi puede ser hosteado en cualquier maquina sin necesidad de configurar un IIS.

    • REST: No se utilizara SOA, ya que REST es más liviano dado que el mensaje no tiene toda la información del Envelope SOAP.

      Streamming: El servicio enviará el archivo en mediante Streamming lo que permite manejar archivos grandes.

    • Multithreading: el servicio no será Single es decir una instancia del servicio atenderá cada llamada, de esta forma el servicio no encolara las solicitudes si no que podrá atender varias descargas al mismo tiempo y a diferentes clientes.

      Lo primero que haremos será crear el contrato, al que llame IDownloadService que contiene el método DownloadFileHttp, el cual recibe el path y el nombre del archivo para descargar y retornara el Stream.

      Esta claro que no es ideal recibir el path del archivo en el servicio, pero lo hice para ejemplificar como recibir varios parametros ya que este servicio utiliza REST por lo cual el servicio recibe los parametros en forma de query string. El atributo WebGet precisamente nos permite definir que el servicio recibira la solicitud mediante una url de la siguiente forma:

                                       DirecciónBase           / download    / path / fileName

      P.e.: http://localhost:9000/DownloadService / download    / C:@mp3  /   tema.mp3

       
           [ServiceContract]
          public interface IDownloadService
          {
              [OperationContract]
              [WebGet(UriTemplate = "download/{path}/{fileName}")]
              Stream DownloadFileHttp(string path, string fileName);
          }
       

      Una vez definido el contrato vamos  a implementarlo de la siguiente manera:

       
        [ServiceBehavior(
              //Cada solicitud crea una nueva instancia de StreamerSvc y se recicla despeus de la llamada 
              //De esta forma las llamadas no se encolan se puede atender a varios clientes
              InstanceContextMode = InstanceContextMode.PerCall,
              //La instancia del servicio es multi threaded
              //Los threads pueden llamar a cualquier operacion del servicio en cualquier momento
              //Multiple implica que le programador tiene que manejar la sincronizacion con locks
              //Pero como el servicio no maneja estados, p.e. variables o atributo de clase no me afecta
              ConcurrencyMode = ConcurrencyMode.Multiple,
              //Esta propiedad hay que setearla en este ejemplo ya que el servicio esta siendo hosteado en una aplicacon Winforms
              //Implica que todas las llamadas al servicio correran en el mismo thread de la UI que creo el servicio (new ServiceHost(...)))
              //La pongo en false para evitar deadlocks ya que por defecto es true
              UseSynchronizationContext = false)]    public class DownloadSvc : IDownloadStream
          {
              public Stream DownloadFileHttp(string path, string fileName)
              {
      //Truco para poder poner el path en la url solo para este ejemplo
                      path = path.Replace("@", @"\");@", @"\");File does not exists!");application/download";content-disposition", string.Format("attachment;filename=\"{0}\"", fileName));
                      fileName = fileName.Replace("
                      path = Path.Combine(path, fileName);
       
                      if (!File.Exists(path))
                      {
                          throw new FaultException("
                      }
              
                      //Abrimos el archivo 
                      var fileStream = File.OpenRead(path);
       
                      //Configurar Headers para el cliente p.e. un browser
                      var headers = WebOperationContext.Current.OutgoingResponse.Headers;
                      WebOperationContext.Current.OutgoingResponse.ContentType = "
                      headers.Add("
                      headers.Add("content-length", fileStream.Length.ToString());
       
               //Retonranos FileStream
                      return fileStream;
              }}
        

      Muy bonito pero para que todo esto funcione correctamente debemos configurar nuestro servicio. Para esto vamos a setear las siguientes entradas en el config:

       
      <?xml version="1.0" encoding="utf-8"?>
      <configuration>
               <system.serviceModel>
                         <bindings>
                                  <mexHttpBinding>
                                            <binding name="MetadataBinding" />
                                  </mexHttpBinding>
                                  <webHttpBinding>
                                            <binding name="DownloadBinding" sendTimeout="00:10:00" maxBufferPoolSize="2147483647"
                                             maxReceivedMessageSize="2147483647" transferMode="Streamed">
                                            </binding>
                                  </webHttpBinding>
                         </bindings>
                         <services>
                                  <service behaviorConfiguration="ServiceBehavior" name="StreamerSvcHost.StreamerSvc">                             
                                            <endpoint address="mex" binding="mexHttpBinding" bindingConfiguration="MetadataBinding"
                                             name="MetadataEndpoint" contract="IMetadataExchange" />
                                            <endpoint address="FileSvc" behaviorConfiguration="RestBehavior"
                                             binding="webHttpBinding" bindingConfiguration="DownloadBinding"
                                             name="DownloadEndpoint" contract="StreamerSvcHost.IDownloadService" />
                                            <host>
                                                     <baseAddresses>
                                                              <add baseAddress="http://localhost:9000/" />
                                                     </baseAddresses>
                                            </host>
                                  </service>
                         </services>
                         <behaviors>
                                  <endpointBehaviors>
                                            <behavior name="RestBehavior">
                                                     <webHttp />
                                            </behavior>
                                  </endpointBehaviors>
                                  <serviceBehaviors>
                                            <behavior name="ServiceBehavior">
                                                     <serviceMetadata httpGetEnabled="true" />
                                                     <serviceDebug includeExceptionDetailInFaults="true" />
                                                     <dataContractSerializer maxItemsInObjectGraph="2147483647" />
                                            </behavior>
                                  </serviceBehaviors>
                         </behaviors>
               </system.serviceModel>

      </configuration>

       

        Lo destacable es lo siguiente:

        • El endpoint deberá tener un webHttpBinding, esto permite exponer el servicio para ser accedido mediante HTTP requests  en lugar de mensajes SOAP.

        • El binding se deberá configurar con TransferMode = Streamed

        • El endpoint deberá tener configura un EndpointBehavior agregándole le elemento webHttp, para que un cliente pueda comunicarse por http con el servicio este debe tener asociado este comportamiento.

        Tags: , , , , , ,

        WCF

        WCF Tips - Clases definidas en el servicio que no aparecen en el proxy del cliente?

        por maxi  14. septiembre 2010

        Cuando generamos una referencia a un servicio en un proyecto de Visual Studio se genera un proxy con todos los métodos definidos en el ServiceContract y las clases definidas como DataContract.
        Los DataContract aparecen en el proxy cuando son recibidos o retornados por algún método de la interfaz. En caso contrario no se generará ya que no aparece en la metadata del servicio.

        Si por algun caso en particular, nosotros utilizamos alguna clase o un enum en un proceso del servicio y este no es recibido ni retornado por ningun metodo y quisieramos utilizarlo del lado, es decir en el cliente,
        estaríamos obligados a duplicarlos manualmente. Por lo tanto vamos a querer que se generen en el proxy del cliente y para lograr esto debemos hacer lo siguiente:

        Primero, generamos una clase que va a contener un método que retornará la lista de clases que serán agregadas a la metadata del servicio. Luego implementamos el método correspoindiente el cual debe retornar una lista de Types, IEnumerable<Type>, y recibirá como parametro un ICustomAttributeProvider. Internamente creamos una lista donde agregamos los Type deseados como se muestra a continuación:

         

         

         

           1:      public static class EnumHelper
           2:      {
           3:          /// <summary>
           4:          /// Obtiene una lista de tipos conocidos que se requieren publicar al cliente
           5:          /// </summary>
           6:          /// <param name="provider"></param>
           7:          /// <returns></returns>
           8:          public static IEnumerable<Type> ObtenerTiposConocidos(ICustomAttributeProvider provider)
           9:          {
          10:              //Incluir los tipos que se requiere ver en el cliente
          11:              var tiposConocidos = new List<Type>
          12:                                              {
          13:                                                  typeof (CodigosMovEnum),
          14:                                                  typeof (Etc)
          15:                                              };
          16:   
          17:              return tiposConocidos;
          18:          }
          19:      }


         

        Segundo, debemos agregar el Atributo ServiceKnownType a nuestro contrato de servicio. Y vamos a pasar como parámetros, primero el Metodo que retorna los Types de las clases que serán agregadas

        en la metadata del servicio y luego, el Type de la clase que contiene dicho método que en este caso es EnumHelper como se muestra a continuación:

         

           1:      [ServiceKnownType("ObtenerTiposConocidos", typeof(EnumHelper))]
           2:      [ServiceContract]
           3:      public partial interface IMiServiceContract
           4:     {    
           5:     }

         

        Luego compilamos el servicio y al actualizar la referencia al proxy del cliente apareceran estas nuevas clases.

         

        Tags: ,

        WCF Tips - Enum definirlo en el servicio y utilizarlo en el cliente

        por maxi  14. septiembre 2010

        Para utilizar un enum simplemente lo definimos y agregamos el atributo DataContract. A cada valor del enum le definimos el atributo EnumMember.

        De esta forma si este enum se recibe como parámetro o es retornado por un metodo del servicio, se generará en la referencia al servicio del cliente.

         

            [DataContract]
        public enum CodigosMovEnum
        {
        [EnumMember]
        Ninguno = 0,

        [EnumMember]
        AEntregar = 1,

        [EnumMember]
        ACumplir = 2,

        }

        Tags: ,

        WCF

        Acerca de los Autores

        Este es el blog del equipo de VEMN SA 
        Presentaremos temas que nos parezcan de interés sobre tecnología .NET, Procesos y Metodologías y todo aquello relacionado con el proceso de desarrollo de Software

        Month List

        BlogRoll

        Download OPML file OPML