WCF con netTcpBinding en Microsoft Azure

En el último artículo te mostré cómo desplegar un servicio web con WCF en Azure App Service. Este es el escenario más sencillo ya que Azure App Service expone los puertos 80 y 443 por defecto pero ¿Qué ocurre si necesitamos migrar un servicio que utiliza un endpoint del tipo netTcpBinding? En este artículo te cuento cómo.

Dockerizar un servicio WCF

Para este artículo he creado un nuevo servicio, pero esta vez sin utilizar IIS como hospedaje. Si trabajas con WCF habitualmente sabrás que el mismo se puede consumir también como un proceso.

En él sigo manteniendo la clase Product.cs que creé para el artículo anterior:

using System.Runtime.Serialization;

namespace SelfHostedWCF.DataContract
{
    [DataContract]
    public class Product
    {
        [DataMember]
        public int Id { get; set; }
        [DataMember]
        public string Name { get; set; }
        [DataMember]
        public string Category { get; set; }
        [DataMember]
        public double Price { get; set; }
    }
}

Así como la interfaz IService.cs con el contrato que se debe implementar (para este ejemplo he eliminado los atributos para la configuración del endpoint webHttpBinding, que no utilizaré aquí):

using SelfHostedWCF.DataContract;
using System.Collections.Generic;
using System.ServiceModel;

namespace SelfHostedWCF
{
    [ServiceContract]
    public interface IService
    {
        [OperationContract]       
        List<Product> GetProductList();
    }
}

y la clase Service.cs que se encargará de implementar dicho contrato:

using SelfHostedWCF.DataContract;
using System.Collections.Generic;

namespace SelfHostedWCF
{
    public class Service : IService
    {
        public List<Product> GetProductList()
        {
            return new List<Product>
            {
                new Product() {Id = 1, Name = "Table", Category = "Dinning Room", Price = 100.5},
                new Product() {Id = 2, Name = "Bed", Category = "Bedroom", Price = 500.99},
                new Product() {Id = 3, Name = "Table", Category = "Kitchen Room", Price = 15},
                new Product() {Id = 4, Name = "Sink", Category = "Bathroom", Price = 90.5}
            };
        }
    }
}

En este caso, al ser una aplicación de consola, la configuración del servicio irá en el archivo o App.config:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.8" />
  </startup>
  <system.serviceModel>
    <behaviors>
      <serviceBehaviors>
        <behavior name="">
          <serviceMetadata httpGetEnabled="true" httpsGetEnabled="true"/>
          <serviceDebug includeExceptionDetailInFaults="false"/>
        </behavior>
      </serviceBehaviors>
    </behaviors>
    <services>
      <service name="SelfHostedWCF.Service">
        <endpoint address="" binding="basicHttpBinding" contract="SelfHostedWCF.IService"/>
        <endpoint binding="netTcpBinding" contract="SelfHostedWCF.IService" bindingConfiguration="noSecurityBind"/>
        <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
        <host>
          <baseAddresses>
            <add baseAddress="http://YOUR_DNS_LABEL.northeurope.azurecontainer.io:80/Service.svc"/>
            <add baseAddress="net.tcp://YOUR_DNS_LABEL.northeurope.azurecontainer.io:808/Service.svc"/>
          </baseAddresses>
        </host>
      </service>
    </services>
    <bindings>
      <netTcpBinding>
        <binding name="noSecurityBind" portSharingEnabled="false">
          <security mode="None"/>
        </binding>
      </netTcpBinding>
    </bindings>
  </system.serviceModel>
</configuration>

Como puedes ver, en esta ocasión tengo dos endpoints del tipo basicHttpBinding y netTcpBinding (a este último le he deshabilitado la parte de seguridad, para simplificar el escenario). En el apartado host he añadido las direcciones base. Para este ejemplo voy a utilizar Azure Container Instances para probar mi futuro contenedor y poder comprobar de manera rápida que el mismo funcionará con ambos endpoints.

Por último, en el archivo Program.cs inicio mi servicio con WCF:

using System;
using System.ServiceModel;
using System.Threading;

namespace SelfHostedWCF
{
    class Program
    {
        static void Main(string[] args)
        {
            // Create the ServiceHost
            ServiceHost host = new ServiceHost(typeof(Service));

            try
            {
                // Open the ServiceHost to start listening for messages
                host.Open();

                foreach (var uri in host.BaseAddresses)
                {
                    Console.WriteLine("The service is ready at {0}", uri);
                }

                // Leave the service running
                Console.WriteLine("The service is running...");
                Thread.Sleep(-1);
            }
            catch
            {
                host?.Close();
                Console.WriteLine("The service is closed");
            }

        }
    }
}

Archivo Dockerfile

Ahora que ya tienes un servicio que migrar, el único paso que queda es crear la receta para dockerizar el mismo. Fuera del proyecto he creado un archivo llamado Dockerfile.selfhost donde incluyo los pasos que necesito para la creación de mi imagen:

FROM mcr.microsoft.com/dotnet/framework/sdk:4.8 AS build
WORKDIR /app
COPY SelfHostedWCF/. .
RUN msbuild /p:Configuration=Release

FROM mcr.microsoft.com/dotnet/framework/runtime:4.8-20191008-windowsservercore-ltsc2019 AS runtime
EXPOSE 80
EXPOSE 808
WORKDIR /app
COPY --from=build /app/bin/Release .
ENTRYPOINT SelfHostedWCF.exe

Como puedes ver, utilizo una primera fase para compilar el proyecto y una segunda donde utilizo el runtime 4.8 de .NET para servir mi sitio, exponiendo los puertos 80, para basicHttpBinding, y 808 para netTcpBinding. Es importante que sepas que no todas las versiones de Windows Server Core están soportadas en ACI.

Genera la nueva imagen a través del siguiente comando:

docker build -t 0gis0/selfhosted -f Dockerfile.selfhosted .

Una vez que la imagen finalice puedes subirla a repositorio que consideres oportuno. En este ejemplo he utilizado Docker Hub.

docker push 0gis0/selfhosted

Probando tu servicio WCF dockerizado

Para comprobar que todo funciona despliega el servicio en Azure Container Instances. Puedes crear un nuevo contedor a través del portal de Azure, utilizando la opción Create a resource a través del menú izquierdo. Busca Container Instances y haz clic sobre el botón Create:

En la primera pantalla debes elegir la suscripción, el grupo de recursos, el nombre de tu contenedor, la región, la imagen, el SO Windows, y el tamaño de la máquina que hospedará tu servicio.

En el apartado Networking, además del puerto 80, debes dar de alta el puerto 808 para el endpoint con el enlace netTcpBinding. Además, puedes elegir un nombre para la URL de acceso, en mi caso wcf-test.

Haz clic en Review + create y espera a que el proceso termine. Para probar ambos enpoints puedes utilizar WCF Test Client. A partir de aquí puedes desplegar el mismo en Azure Kubernetes Service o incluso en Azure Service Fabric.

¡Saludos!