I have a Stateless Service that I am migrating some CloudService WCF Endpoints to. These enpoints are available publicly and require that I am able to discover their properties using WS-MetadataExchange via both References > Add Service Reference from a Visual Studio project as well as from WCFTestClient.
I've followed a few tutorials and have a test endpoint set up:
<Endpoint Protocol="tcp" Name="WcfServiceEndpoint" Type="Input" Port="8081" />
As well as a service contract
[ServiceContract]
public interface ITestContract
{
[OperationContract]
int TestMethod(int value1, int value2);
}
The method:
public class TestService : ITestContract
{
public int TestMethod(int value1, int value2)
{
var result = value1 + value2;
return result;
}
}
And a service listener override:
protected override IEnumerable<ServiceInstanceListener> CreateServiceInstanceListeners()
{
return new[] { new ServiceInstanceListener((context) =>
new WcfCommunicationListener<ITestContract>(
this.Context,
new TestService(),
WcfUtility.CreateTcpClientBinding(),
"WcfServiceEndpoint"
)
)};
In my previous project (that I am migrating from) I set up custom ServiceHost objects and was able to customize their bindings/Urls. I was able to have clients discover the services just fine. I need to be able to expose this service in the same manner so that WcfTestClient as well as References > Add Service Reference can discover their properties using WS-MetadataExchange. As it stands now I don't even have any control over what the service paths are!
Ideally I would like to still use ServiceHost:
var host = new ServiceHost(ITestContract);
host.AddServiceEndpoint(TestService, new NetTcpBinding(SecurityMode.None), "net.tcp://...", new Uri("net.tcp://..."));
host.Description.Behaviors.Remove(typeof(ServiceDebugBehavior));
host.Description.Behaviors.Add(new ServiceDebugBehavior { IncludeExceptionDetailInFaults = true });
host.Open();
UPDATE
Based on the suggestion from VipulM-MSFT (below) I am now creating the communication listener first:
var testCommunicationListener = new WcfCommunicationListener<ITestContract>(
this.Context,
new TestService(),
WcfUtility.CreateTcpClientBinding(),
"WcfServiceEndpoint"
);
Then modifying the ServiceHost object to allow for MetadataExchange Behavior:
ServiceMetadataBehavior metaDataBehavior = new ServiceMetadataBehavior();
testCommunicationListener.ServiceHost.Description.Behaviors.Add(metaDataBehavior);
As well as allowing for exception details in faults:
testCommunicationListener.ServiceHost.Description.Behaviors.Remove(typeof(ServiceDebugBehavior));
testCommunicationListener.ServiceHost.Description.Behaviors.Add(new ServiceDebugBehavior { IncludeExceptionDetailInFaults = true });
I then create a service endpoint (with my desired path to the service):
testCommunicationListener.ServiceHost.AddServiceEndpoint(typeof(ITestContract), new NetTcpBinding(SecurityMode.None), "net.tcp://localhost:8081/Services/Tests", new Uri("net.tcp://localhost:8081/Services/Tests"));
As well as the MexEndpoint (to allow for MetaExchange binding over TCP):
Binding mexBinding = MetadataExchangeBindings.CreateMexTcpBinding();
testCommunicationListener.ServiceHost.AddServiceEndpoint(typeof(IMetadataExchange), mexBinding, "net.tcp://localhost:8081/Services/Tests/mex", new Uri("net.tcp://localhost:8081/Services/Tests/mex"));
And finally I assign the listener to the Stateless Service:
return new[] { new ServiceInstanceListener((context) => testCommunicationListener)};
When I push this to my local cluster I get the following error:
The Service contains multiple ServiceEndpoints with different ContractDescriptions which each have Name='ITestContract' and Namespace='http://tempuri.org/'. Either provide ContractDescriptions with unique Name and Namespaces, or ensure the ServiceEndpoints have the same ContractDescription instance.
I thought maybe I needed to remove the default endpoint to avoid this collision so I tried:
testCommunicationListener.ServiceHost.Description.Endpoints.RemoveAt(0);
prior to calling:
testCommunicationListener.ServiceHost.AddServiceEndpoint(typeof(ITestContract), new NetTcpBinding(SecurityMode.None), "net.tcp://localhost:8081/Services/Tests", new Uri("net.tcp://localhost:8081/Services/Tests"));
Binding mexBinding = MetadataExchangeBindings.CreateMexTcpBinding();
testCommunicationListener.ServiceHost.AddServiceEndpoint(typeof(IMetadataExchange), mexBinding, "net.tcp://localhost:8081/Services/Tests/mex", new Uri("net.tcp://localhost:8081/Services/Tests/mex"));
Which gives me the following error:
Replica had multiple failures in API call: IStatelessServiceInstance.Open(); Error = System.NullReferenceException (-2147467261) Object reference not set to an instance of an object. at Microsoft.ServiceFabric.Services.Communication.Wcf.Runtime.WcfCommunicationListener
1.b__0(IAsyncResult ar) at System.Threading.Tasks.TaskFactory
1.FromAsyncCoreLogic(IAsyncResult iar, Func2 endFunction, Action
1 endAction, Task`1 promise, Boolean requiresSynchronization)
I've tried a few other variations with similar results...
I also tried allowing the MetadataExchange behavior on the default bindings (without creating any additional endpoints). But in this scenario how do I know what my endpoints url is? I tried using net.tcp://localhost:8081/TestService (which should be the default) but I cannot connect to this from a console app or WcfTestClient.
UPDATE 2
I was able to host my WCF endpoint as desired by only adding the MetadataExchangeBinding as an additional endpoint and assigning my desired service path within:
Binding mexBinding = MetadataExchangeBindings.CreateMexTcpBinding();
testCommunicationListener.ServiceHost.AddServiceEndpoint(typeof(IMetadataExchange), mexBinding, "net.tcp://localhost:8081/Services/Tests/mex", new Uri("net.tcp://localhost:8081/Services/Tests/mex"));