Duplex WCF service - Direct call and callback do not use same channel - wcf

My goal is to reach a WCF service behind a firewall with no incoming ports opened.
The solution I chose is to host a duplex WCF service on the public side, that has as callback the same contract that was used if no firewall was involved.
It worked if I used netTcpBinding but since I need streamed communication I had to use the custom binding.
Everything works fine until I raise up the firewall. At that point, the direct call (from behind the firewall out) works fine, but the callback does not (firewall stops it).
The question is WHY? Shoudn't they use the same channel as for the predefined netTcpBinding?
Here is my channels stack in app.config:
<customBinding>
<binding name="ReversedServiceBinding">
<compositeDuplex />
<oneWay />
<binaryMessageEncoding />
<tcpTransport transferMode="Streamed" />
</binding>
</customBinding>

Actual behavior is correct. You have used compositeDuplex binding element - the meaning of composite word is exactly what you get - two separate channels each working for one direction. Composite duplex is needed only for transport channel which doesn't support duplex communication by default. That is not the case for TCP transport channel - check remarks. If you don't use compositeDuplex it should work like you have expected. Also there is no need to define whole new custom binding to allow streamed transport. NetTcpBinding also has TransportMode property which can be specified in configuration.
EDIT:
I have prepared working example with Net.TCP duplex communication. It doesn't use streaming. You are right that streaming is not possible when duplex TCP communication is used. You can try to combine duplex communication with chunking channel or check WCF Xtensions (commercial product).
Shared contracts
namespace NetTcpDuplexContracts
{
[ServiceContract(SessionMode = SessionMode.Required,
CallbackContract = typeof(IDuplexServiceCallback))]
public interface IDuplexService
{
[OperationContract(IsOneWay = true)]
void DoAction(string message);
}
public interface IDuplexServiceCallback
{
[OperationContract(IsOneWay = true)]
void ConfirmAction(string message);
}
}
Service and host
namespace NetTcpDuplexService
{
public class DuplexService : IDuplexService
{
public void DoAction(string message)
{
Console.WriteLine("DoAction: " + message);
var callbackChannel =
OperationContext.Current.GetCallbackChannel<IDuplexServiceCallback>();
callbackChannel.ConfirmAction("Ping back " + message);
}
}
class Program
{
public static void Main(string[] args)
{
try
{
using (var host = new ServiceHost(typeof(DuplexService)))
{
host.Open();
Console.ReadLine();
}
}
catch (Exception e)
{
Console.WriteLine(e.Message);
Console.ReadLine();
}
}
}
}
Service configuration
<configuration>
<system.serviceModel>
<services>
<service name="NetTcpDuplexService.DuplexService">
<host>
<baseAddresses>
<add baseAddress="net.tcp://localhost:8800/NetTcpDuplexService"/>
</baseAddresses>
</host>
<endpoint address="" binding="netTcpBinding" contract="NetTcpDuplexContracts.IDuplexService" />
</service>
</services>
</system.serviceModel>
</configuration>
Callback and client
namespace NetTcpDuplexClient
{
public class DuplexServiceCallback : IDuplexServiceCallback
{
public void ConfirmAction(string message)
{
Console.WriteLine(message);
}
}
class Program
{
public static void Main(string[] args)
{
DuplexChannelFactory<IDuplexService> factory = null;
try
{
var callbackService = new DuplexServiceCallback();
var context = new InstanceContext(callbackService);
factory = new DuplexChannelFactory<IDuplexService>(context, "IDuplexService_NetTcp");
var channel = factory.CreateChannel();
channel.DoAction("Hello world");
factory.Close();
Console.ReadLine();
}
catch (Exception e)
{
if (factory != null && factory.State != CommunicationState.Closed)
{
factory.Abort();
}
Console.WriteLine(e.Message);
Console.ReadLine();
}
}
}
}
Client configration
<configuration>
<system.serviceModel>
<client>
<endpoint name="IDuplexService_NetTcp" address="net.tcp://localhost:8800/NetTcpDuplexService" binding="netTcpBinding"
contract="NetTcpDuplexContracts.IDuplexService" />
</client>
</system.serviceModel>
</configuration>

Related

How to fix AddressInUseException that occurs whenever service host is opened

I am working on WCF windows application... while restarting the service, I am getting the below exception.
outerType: System.ServiceModel.AddressAlreadyInUseException
outerMessage: "Cannot listen on pipe name 'net.pipe://0.0.0.1/MyService' because another pipe endpoint is already listening on that name."
type: System.IO.PipeException
App.Config file:
<services>
<service name="ServiceName">
<endpoint address="" binding="netNamedPipeBinding" contract="ServiceClient">
</endpoint>
<host>
<baseAddresses>
<add baseAddress="net.pipe://0.0.0.1/MyService"/>
</baseAddresses>
</host>
</service>
</services>
Start Method:
public void Start(string serviceName)
{
_serviceName = serviceName;
if (_serviceController == null)
{
_serviceController = new ServiceController(_serviceName);
}
TimeSpan timeout;
switch (_serviceController.Status)
{
case ServiceControllerStatus.Stopped:
_serviceController.Start();
timeout = TimeSpan.FromMilliseconds(60000);
_serviceController.WaitForStatus(ServiceControllerStatus.Running, timeout);
break;
case ServiceControllerStatus.Paused:
_serviceController.Continue();
timeout = TimeSpan.FromMilliseconds(60000);
_serviceController.WaitForStatus(ServiceControllerStatus.Running, timeout);
break;
}
}
Stop Method:
public void Stop()
{
if (_serviceController == null)
{
_serviceController = new ServiceController(_serviceName);
}
if (_serviceController.Status == ServiceControllerStatus.Running)
{
_serviceController.Stop();
TimeSpan timeout = TimeSpan.FromMilliseconds(20000);
_serviceController.WaitForStatus(ServiceControllerStatus.Stopped, timeout);
}
}
When ever I restarts my service, I will start my Service controller and also open a service host for hosting my service.
_serverHost = new ServiceHost(this); // this points my service name mentioned in App.Config
_serverHost.Open(); // Exception occurs here.
I tried to increase my time out in WaitForStatus() on both Stop() method but it didn't worked out.
Any suggestion will be a great help.
Thanks.
Since you merely post a management application of the window NT service, I don’t think any changes on this side would help.
What is the Windows NT service code design? In my opinion, we should open and close the service host properly in the windows service OnStart event and windows service OnStop event. Windows NT service is the root of the issue.
Besides, I suggest add the IF statement detection during opening the service host and stop the service host.
Please consider the below code.
public partial class Service1 : ServiceBase
{
public Service1()
{
InitializeComponent();
}
Uri uri = new Uri("net.pipe://localhost/mypipe");
ServiceHost sh = null;
protected override void OnStart(string[] args)
{
NetNamedPipeBinding binding = new NetNamedPipeBinding();
try
{
if (sh == null)
{
sh = new ServiceHost(typeof(MyService), uri);
sh.Open();
} }
catch (Exception e)
{
}
}
protected override void OnStop()
{
if (sh != null && sh.State == CommunicationState.Opened)
{
sh.Close();
}
}
Feel free to let me know if the problem still exists.

how to find the service address in WCF client

I created a WCF service and client in same machine, the services address is wrote into Client's code, so I can easily find the service and create connection to service.
Then I try to deploy them into Intranet. The first problem is: how could Client find the address of server. In actual environment, customers can install service at any computer in Intranet, is there any way to let client find the server address?
WCF service could expose a specific endpoint as a discovery endpoint to all clients so that client could find where the service lies. You could even use UDP multicast to enable the service to be discovered by the client.
You could check the official document.
https://learn.microsoft.com/en-us/dotnet/framework/wcf/feature-details/wcf-discovery
I have made a demo, wish it is useful to you.
Server.
class Program
{
static void Main(string[] args)
{
using (ServiceHost sh=new ServiceHost(typeof(MyService)))
{
sh.Open();
Console.WriteLine("serivce is ready...");
Console.ReadLine();
sh.Close();
}
}
}
[ServiceContract]
public interface IService
{
[OperationContract]
string SayHello();
}
public class MyService : IService
{
public string SayHello()
{
return "Hello, I am a Clown";
}
}
Server app.config
<system.serviceModel>
<services>
<service name="DiscoveryEndpoint20181024.MyService" behaviorConfiguration="mybehavior">
<endpoint address="http://10.157.18.188:4800" binding="wsHttpBinding" contract="DiscoveryEndpoint20181024.IService"></endpoint>
<endpoint kind="discoveryEndpoint" address="http://localhost:9999" binding="wsHttpBinding"></endpoint>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="mybehavior">
<serviceMetadata />
<serviceDiscovery />
</behavior>
</serviceBehaviors>
</behaviors>
Client.
class Program
{
static void Main(string[] args)
{
DiscoveryClient client = new DiscoveryClient("my_client");
client.ClientCredentials.Windows.ClientCredential.UserName = "administrator";
client.ClientCredentials.Windows.ClientCredential.Password = "abcd1234!";
FindCriteria crit = new FindCriteria(typeof(IService));
FindResponse resp = client.Find(crit);
if (resp != null && resp.Endpoints.Count > 0)
{
EndpointDiscoveryMetadata epaddrMtd = resp.Endpoints[0];
ChannelFactory<IService> factory = new ChannelFactory<IService>(new WSHttpBinding(), epaddrMtd.Address);
factory.Credentials.Windows.ClientCredential.UserName = "administrator";
factory.Credentials.Windows.ClientCredential.Password = "abcd1234!";
IService service = factory.CreateChannel();
var result=service.SayHello();
Console.WriteLine(result);
Console.ReadLine();
}
}
}
[ServiceContract]
public interface IService
{
[OperationContract]
string SayHello();
}
class DemoService : IService
{
public string SayHello()
{
OperationContext context = OperationContext.Current;
return $"the address:{OperationContext.Current.Channel.LocalAddress.Uri}";
}
}
Client.config
<system.serviceModel>
<client>
<endpoint name="my_client" kind="discoveryEndpoint" address="http://10.157.18.188:9999" binding="wsHttpBinding"></endpoint>
</client>
</system.serviceModel>

Having two different topics within same windows server service bus namespace in WCF endpoint

I have two endpoints defined pointing to two different service bus topics. With same transportClientEndpointBehavior and on same service.
<endpointBehaviors>
<behavior name="securityBehavior">
<transportClientEndpointBehavior>
<tokenProvider>
<windowsAuthentication>
<stsUris>
<stsUri value="https://on-permises:9355/Namespace" />
</stsUris>
</windowsAuthentication>
</tokenProvider>
</transportClientEndpointBehavior>
</endpointBehaviors>
<customBinding>
<binding name="messagingBinding" >
<textMessageEncoding messageVersion="None" writeEncoding="utf-8" >
<readerQuotas maxStringContentLength="2147483647"/>
</textMessageEncoding>
<netMessagingTransport/>
</binding>
</customBinding>
<endpoint name="endpoint1"
address="sb://on-permises/Namespace/topic1"
listenUri="sb://on-permises/Namespace/topic1/subscriptions/sub"
binding="customBinding"
bindingConfiguration="messagingBinding"
contract="WCFService.IService1"
behaviorConfiguration="securityBehavior" />
<endpoint name="endpoint2"
address="sb://on-permises/Namespace/topic2"
listenUri="sb://on-permises/Namespace/topic2/subscriptions/sub"
binding="customBinding"
bindingConfiguration="messagingBinding"
contract="WCFService.IService2"
behaviorConfiguration="securityBehavior" />
Once i run the application, I get the error :
System.ArgumentException: The value could not be added to the collection, as the collection already contains an item of the same type: 'Microsoft.ServiceBus.TransportClientEndpointBehavior'. This collection only supports one instance of each type.
Parameter name: item
I tried by defining two different endpoint behaviors, but get the same error.
any help here will be helpful.
Found the work around solution, the issue was service host was trying to add these two service endpoints in WSDL/Metadata. which was not necessary. So with the help of ServiceMetadataContractBehavior (IContractBehavior ) stopped exposing the WSDL/Metadata .
Any better approach or correction please let me know.
public class DisableContractMetadata : Attribute, IContractBehavior
{
public void AddBindingParameters(ContractDescription contractDescription, ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
{
}
public void ApplyClientBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, ClientRuntime clientRuntime)
{
}
public void ApplyDispatchBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, DispatchRuntime dispatchRuntime)
{
/// Here ServiceMetadataContractBehavior type is derived from IContractBehavior
/// MetadataGenerationDisabled property of ServiceMetadataContractBehavior type = flase disables disables exposing WSDL
contractDescription.ContractBehaviors.Add(new ServiceMetadataContractBehavior() { MetadataGenerationDisabled = true });
}
public void Validate(ContractDescription contractDescription, ServiceEndpoint endpoint)
{
}
}
Note that the above answer is incomplete as it doesn't actually implement IContractBehavior.
As this is caused by hosting multiple services, you can also do it in the host:
ContractDescription contractDescription = ContractDescription.GetContract(typeof(MyService));
contractDescription.ContractBehaviors.Add(new ServiceMetadataContractBehavior() { MetadataGenerationDisabled = true });
Or, if you have multiple contracts on a type, use this:
private static void DisableMetadataGeneration(Type serviceType)
{
// prevent this error due to hosting multiple services:
// System.ArgumentException: The value could not be added to the collection, as the collection already contains an item of the same type: 'Microsoft.ServiceBus.TransportClientEndpointBehavior'. This collection only supports one instance of each type.
foreach (Type contractType in GetContracts(serviceType))
{
ContractDescription contractDescription = ContractDescription.GetContract(contractType, serviceType);
contractDescription.ContractBehaviors.Add(new ServiceMetadataContractBehavior() { MetadataGenerationDisabled = true });
}
}
private static Type[] GetContracts(Type serviceType)
{
System.Diagnostics.Debug.Assert(serviceType.IsClass);
Type[] interfaces = serviceType.GetInterfaces();
List<Type> contracts = new List<Type>();
foreach (Type type in interfaces)
{
if (type.GetCustomAttributes(typeof(ServiceContractAttribute), false).Any())
{
contracts.Add(type);
}
}
return contracts.ToArray();
}

How to use WCF service running on Windows Service

I am new at WCF. I have created a new WCF service which has one method called SendFax(). It is running on windows service. I want other users to use this method but how? I installed the windows service with installutil.exe and it is running now. I guess my WCF service is listening. How can I reach this service? My windows service code is here:
public partial class WCFWinService : ServiceBase
{
ServiceHost serviceHost;
public WCFWinService()
{
InitializeComponent();
ServiceName = "Digiturk FaxPro WCF";
}
protected override void OnStart(string[] args)
{
if (serviceHost != null)
{
serviceHost.Close();
}
serviceHost = new ServiceHost(typeof(FaxPro.WCFWindowsService.WCFWinService));
serviceHost.Open();
}
protected override void OnStop()
{
if (serviceHost != null)
{
serviceHost.Close();
serviceHost = null;
}
}
}
WCF Interface code:
[ServiceContract]
public interface IWCFService
{
[OperationContract]
void SendFax(....);
}
WCF Service Code:
public class WCFService : IWCFService
{
public void SendFax(...)
{ ... }
Any suggestions?
App.Config is here
<?xml version="1.0"?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/>
</startup>
<system.serviceModel>
<bindings>
<basicHttpBinding>
<binding name="BasicHttpBinding_IWCFService" />
</basicHttpBinding>
</bindings>
<client>
<endpoint address="http://localhost:8733/Design_Time_Addresses/Digiturk.FaxPro.WcfServiceLibrary/Service1/"
binding="basicHttpBinding" bindingConfiguration="BasicHttpBinding_IWCFService"
contract="localhost.IWCFService" name="BasicHttpBinding_IWCFService" />
</client>
</system.serviceModel>
</configuration>
You can connect to this service at the defined address:
http://example.com:8733/Design_Time_Addresses/Digiturk.FaxPro.WcfServiceLibrary/‌Service1/
It's a SOAP service - so you'll need a SOAP-capable testing tool, like SoapUI or the WCF Test Client (just a browser will not work). You can get the WSDL for the service at the above address by appending ?wsdl to the URL.

How to Host SVC file in win form apps WCF

i am new in WCF. i know how to host wcf service in windows form. now i develop a small wcf service which has .svc file. i want to host this svc file in win form. so just want to know process will be same or different?
here is my svc file markup
<%# ServiceHost Language="C#" Debug="true"
Service="Services.ChatService" CodeBehind="ChatService.svc.cs" %>
here is small code inside svc file code behind file
namespace Services
{
/// <summary>
/// Implements the chat service interface.
/// </summary>
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single,
ConcurrencyMode = ConcurrencyMode.Multiple)]
public class ChatService : IChatService
{
private readonly Dictionary<Guid, IChatServiceCallback> clients =
new Dictionary<Guid, IChatServiceCallback>();
#region IChatService
Guid IChatService.Subscribe()
{
IChatServiceCallback callback =
OperationContext.Current.GetCallbackChannel<IChatServiceCallback>();
Guid clientId = Guid.NewGuid();
if (callback != null)
{
lock (clients)
{
clients.Add(clientId, callback);
}
}
return clientId;
}
void IChatService.Unsubscribe(Guid clientId)
{
lock (clients)
{
if (clients.ContainsKey(clientId))
{
clients.Remove(clientId);
}
}
}
void IChatService.KeepConnection()
{
// Do nothing.
}
void IChatService.SendMessage(Guid clientId, string message)
{
BroadcastMessage(clientId, message);
}
#endregion
/// <summary>
/// Notifies the clients of messages.
/// </summary>
/// <param name="clientId">Identifies the client that sent the message.</param>
/// <param name="message">The message to be sent to all connected clients.</param>
private void BroadcastMessage(Guid clientId, string message)
{
// Call each client's callback method
ThreadPool.QueueUserWorkItem
(
delegate
{
lock (clients)
{
List<Guid> disconnectedClientGuids = new List<Guid>();
foreach (KeyValuePair<Guid, IChatServiceCallback> client in clients)
{
try
{
client.Value.HandleMessage(message);
}
catch (Exception)
{
// TODO: Better to catch specific exception types.
// If a timeout exception occurred, it means that the server
// can't connect to the client. It might be because of a network
// error, or the client was closed prematurely due to an exception or
// and was unable to unregister from the server. In any case, we
// must remove the client from the list of clients.
// Another type of exception that might occur is that the communication
// object is aborted, or is closed.
// Mark the key for deletion. We will delete the client after the
// for-loop because using foreach construct makes the clients collection
// non-modifiable while in the loop.
disconnectedClientGuids.Add(client.Key);
}
}
foreach (Guid clientGuid in disconnectedClientGuids)
{
clients.Remove(clientGuid);
}
}
}
);
}
}
}
here is binding info
<service behaviorConfiguration="Services.ChatServiceBehavior" name="Services.ChatService">
<endpoint address="" binding="wsDualHttpBinding" bindingConfiguration="WSDualHttpBinding_IChatService" contract="Services.IChatService">
<identity>
<dns value="localhost"/>
</identity>
</endpoint>
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
</service>
here is two endpoint one for wsDualHttpBinding and another one for mex
so now my mex endpoint is
http://localhost:49722/ChatService.svc?wsdl
now i want to add another tcp endpoint and expose this service with two endpoint. so just tell me what i need to write for tcp endpoint and when i add tcp endpoint then what will mex endpoint for tcp because i want that user can create proxy with any of two url
one would be http url and another would be tcp url. so do i need to add mex for tcp here?
please guide me. thanks
you must start host manually
follow to msdn link http://msdn.microsoft.com/en-us/library/system.servicemodel.servicehost.aspx
edited
ServiceHost _serviceHost;
public void Start(Type type)
{
_serviceHost = new ServiceHost(type);
_serviceHost.Open();
}