I'm trying to migrate Azure Web Roles running WCF, to stateless services in Azure Service Fabric, using the WCF Communication Listener. Everything works in my local service cluster. After publishing to Azure, other services within the cluster are able to access the stateless WCF service, but external (internet) clients (including my dev machine) are unable to connect with a transient network error.
I verified that the load balancer in the resource group has rules/probes for ports 80 and 8080, and have tested with TCP and HTTP. I also tried to setup the partition resolver on the WCF client to point the "client connection endpoint" on the service cluster (from default, which works within the service cluster).
At this point, I'm not sure if I have a configuration issue, or if it's even possible for external (internet) clients to connect to stateless services running the WCF Communication Listener.
Here is my config:
WCF Communication Listener
private Func<StatelessServiceContext, ICommunicationListener> CreateListener()
{
return delegate (StatelessServiceContext context)
{
var host = new WcfCommunicationListener<IHello>(
wcfServiceObject: this,
serviceContext: context,
endpointResourceName: "ServiceEndpoint",
listenerBinding: CreateDefaultHttpBinding()
);
return host;
};
}
WCF Binding
public static Binding CreateDefaultHttpBinding()
{
var binding = new WSHttpBinding(SecurityMode.None)
{
CloseTimeout = new TimeSpan(00, 05, 00),
OpenTimeout = new TimeSpan(00, 05, 00),
ReceiveTimeout = new TimeSpan(00, 05, 00),
SendTimeout = new TimeSpan(00, 05, 00),
MaxReceivedMessageSize = int.MaxValue,
};
var quota = new XmlDictionaryReaderQuotas
{
MaxArrayLength = int.MaxValue,
MaxDepth = int.MaxValue
};
binding.ReaderQuotas = quota;
return binding;
}
ServiceManifest.xml (I've also used the default TCP binding with various ports)
<Endpoints>
<Endpoint Name="ServiceEndpoint" Protocol="http" Port="8080" />
</Endpoints>
WCF Console App
var address = new Uri("fabric:/ServiceFabricWcf.Azure/ServiceFabricWcf");
var client = GetClient(address, CreateDefaultHttpBinding());
try
{
var results = client.InvokeWithRetry(x => x.Channel.Hello());
System.WriteLine($"Results from WCF Service: '{results}'");
Console.ReadKey();
}
catch (Exception e)
{
System.Console.WriteLine("Exception calling WCF Service: '{e}'");
}
WCF Client
public static WcfServiceFabricCommunicationClient<IHello> GetClient(Uri address, Binding binding)
{
//ServicePartitionResolver.GetDefault(); Works with other services in cluster
var partitionResolver = new ServicePartitionResolver("<clientConnectionEndpointOfServiceCluster>:8080");
var wcfClientFactory = new WcfCommunicationClientFactory<IHello>(binding, null, partitionResolver);
var sfclient = new WcfServiceFabricCommunicationClient<IHello>(wcfClientFactory, address, ServicePartitionKey.Singleton);
return sfclient;
}
WCF Client Factory
public class WcfServiceFabricCommunicationClient<T> : ServicePartitionClient<WcfCommunicationClient<T>> where T : class
{
public WcfServiceFabricCommunicationClient(ICommunicationClientFactory<WcfCommunicationClient<T>> communicationClientFactory,
Uri serviceUri,
ServicePartitionKey partitionKey = null,
TargetReplicaSelector targetReplicaSelector = TargetReplicaSelector.Default,
string listenerName = null,
OperationRetrySettings retrySettings = null
)
: base(communicationClientFactory, serviceUri, partitionKey, targetReplicaSelector, listenerName, retrySettings)
{
}
}
Here's a way that works for a WCF Service with a WebHttpBinding
: https://github.com/loekd/ServiceFabric.WcfCalc
Try changing your code so it doesn't use endpointResourceName
but address
containing an explicit URL instead. The URL should be the public name of your cluster, like mycluster.region.cloudapp.azure.com.
Edit: url should use node name, which is easier.
string host = context.NodeContext.IPAddressOrFQDN;
var endpointConfig = context.CodePackageActivationContext.GetEndpoint
("CalculatorEndpoint");
int port = endpointConfig.Port;
string scheme = endpointConfig.Protocol.ToString();
string uri = string.Format(CultureInfo.InvariantCulture,
"{0}://{1}:{2}/", scheme, host, port);