I have a WCF Service written in Visual Studio 2015 (c#) that uses web sockets, this means the endpoint is configured with netHTTPBindings. I have a Delphi application that I have currently written in XE and would like to consume the WCF Service, what is the best way for a Delphi application to consume a web socket WCF Service?
// Edit ---------------------------------------------------------------------------
I am now using wsDualHttpBindings, here is some prototype code:
WCF Service interface
[ServiceContract(CallbackContract = typeof(IStatusCallback))]
public interface IStatusService
{
[OperationContract(IsOneWay = true)]
Task StartSendingStatus();
}
[ServiceContract]
public interface IStatusCallback
{
[OperationContract(IsOneWay = true)]
Task SendStatus(string aValue);
}
Implementation
public class StatusService : IStatusService
{
public async Task StartSendingStatus()
{
var callback = OperationContext.Current.GetCallbackChannel<IStatusCallback>();
while (((IChannel)callback).State == CommunicationState.Opened)
{
await callback.SendStatus(GetStatus());
await Task.Delay(1000);
}
}
private string GetStatus()
{
// For now return datetime
string dt = DateTime.UtcNow.ToString();
return Convert.ToString(dt);
}
}
Here is how I consume it in a C# application, I put the result into list box.
public partial class ClientForm : Form
{
private StatusServiceReference.StatusServiceClient StatusService;
private class CallbackHandler : StatusServiceReference.IStatusServiceCallback
{
private ListBox _listbox;
public CallbackHandler(ListBox aListBox)
{
_listbox = aListBox;
}
public void SendStatus(string aValue)
{
_listbox.Items.Add(aValue);
_listbox.SelectedIndex = _listbox.Items.Count - 1;
}
}
public ClientForm()
{
InitializeComponent();
var context = new InstanceContext(new CallbackHandler(StatusListBox));
StatusService = new StatusServiceReference.StatusServiceClient(context);
}
private void StatusBtn_Click(object sender, EventArgs e)
{
StatusService.StartSendingStatus();
}
}
This works fine, I would like to know the best way to do the above client code in Delphi. When I import the WSDL file it does not have the IStatusServiceCallback interface.
Related
I have a ASP NET Core application that will serve as a RabbitMQ producer.I have read the tutorial and guides regarding the RabbitMQ .NET client and i still do not know how to deal with the channel lifetime and concurrent access.
From what i have read i understood the following:
IConnection is threadsafe ,but is costly to create
IModel is not threadsafe but is lightweight
For the IConnection i would initialize it in the Startup and inject it as a singleton (service).
However i I do not know how to deal with IModel management.Lets say i have a couple of services that use it, is it scalable to just :
Solution 1
public void Publish(IConnection connection)
{
using(IModel model=connection.CreateChannel())
{
model.BasicPublish(...);
}
}
Solution 2
From what i have read , i understood that its not really scalable.
So another solution would be to create a separate service which would contain a loop , a ConcurrentQueue, and all services would dispatch messages here.
This service would be the sole publisher to RabbitMQ
Publisher
public class Publisher
{
private CancellationTokenSource tcs=new CancellationTokenSource();
public BlockingCollection<byte[]> messages=new BlockingCollection<byte[]>();
private IModel channel;
private readonly string ExchangeName;
private Task loopTask;
public void Run()
{
this.loopTask=Task.Run(()=>Loop(tcs.Token),tcs.Token);
}
private void Loop(Cancellation token)
{
while(true)
{
token.ThrowIfCancellationRequested();
queue.Take(out byte[]data);
channel.BasicPublish(...,body:data);
}
}
public void Publish(byte[] message)
{
this.queue.Add(message);
}
}
Usage
public class SomeDIService
{
private IConnection connection;
SomeDIService(Publisher publisher)
{
this.publisher=publisher;
}
public void DoSomething(byte[] data)
{
//do something...
this.publisher.Publish(data);
}
}
I would prefer solution 1 but i do not know the performance penalty ,while i do not like solution 2 since i wanted to just publish messages directly to RabbitMQ.Now i have to deal with this long running Task too.
Is there any other solution , am i missing something ? Is there a simpler way?
Update
I mentioned concurrent access.I meant i need a way to publish messages from multiple endpoints (services) to RabbitMQ.
Real scenario
public class Controller1:Controller
{
private SomeDIService service; //uses Publisher
[HttpGet]
public void Endpoint1()
{
this.service.DoSomething();
}
[HttpPost]
public void Endpoint2()
{
this.service.DoSomething();
}
}
public class Controller2:Controller
{
private SomeDIService service;
[HttpGet]
public void Endpoint3()
{
this.service.DoSomething();
}
[HttpPost]
public void Endpoint4()
{
this.service.DoSomething();
}
}
after searching for long time i found this solution and it works very good for me
using Microsoft.Extensions.Options;
using RabbitMQ.Client;
using System;
using System.Collections.Generic;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
namespace BSG.MessageBroker.RabbitMQ
{
public class Rabbit : IRabbit
{
private readonly EnvConfigModel EnvConfig;
private readonly string _hostname;
private readonly string _password;
private readonly string _exchangeName;
private readonly string _username;
private IConnection _connection;
private IModel _Model;
public Rabbit(IOptions<EnvConfigModel> appSettings)
{
EnvConfig = appSettings.Value;
_Logger = services;
_exchangeName = EnvConfig.Rabbit_ExchangeName;
_hostname = EnvConfig.Rabbit_Host;
_username = EnvConfig.Rabbit_Username;
_password = EnvConfig.Rabbit_Password;
CreateConnection();
_Model = _connection.CreateModel();
}
private void CreateConnection()
{
try
{
var factory = new ConnectionFactory
{
HostName = _hostname,
UserName = _username,
Password = _password,
AutomaticRecoveryEnabled = true,
TopologyRecoveryEnabled = true,
NetworkRecoveryInterval = TimeSpan.FromSeconds(3)
};
_connection = factory.CreateConnection();
}
catch (Exception ex)
{
Console.WriteLine($"Could not create connection: {ex.Message}");
}
}
private bool ConnectionExists()
{
if (_connection != null)
{
return true;
}
CreateShredderConnection();
return _connection != null;
}
public bool PushToQueue(string Message)
{
try
{
if (ConnectionExists())
{
byte[] body = Encoding.UTF8.GetBytes(JsonSerializer.Serialize(Message));
_Model.BasicPublish(exchange: _exchangeName,
routingKey: 1001,
basicProperties: null,
body: body);
}
return true;
}
catch (Exception ex)
{
return false;
}
}
}
}
My Scenerio is that i need a SignalR self Hosted WCF Service that response and sends message to all connected users that came from Winform or WPF.
I have tried alot as follows:
I have Created WCF service with SignalR Self Hosting code as below which contains 3 Classes and 1 Interface.
namespace SignalRServiceClass
{
[ServiceContract]
public interface ISignalRServiceClass
{
[OperationContract]
string GetsMessage(string name);
[OperationContract]
void Configuration(IAppBuilder app);
[OperationContract]
void Send(string name, string message);
}
}
namespace SignalRServiceClass
{
public class SignalRServiceClass : ISignalRServiceClass
{
public string GetsMessage(string name)
{
return "Message From Service " + name + "!";
}
}
}
namespace SignalRServiceClass
{
class ClassHub : Hub
{
public void Send(string name, string message)
{
Clients.All.addMessage(name, message);
}
}
}
namespace SignalRServiceClass
{
class Startup
{
public void Configuration(IAppBuilder app)
{
// app.UseCors(CorsOptions.AllowAll);
// app.MapSignalR();
app.Map("/signalr", map =>
{
map.UseCors(CorsOptions.AllowAll);
var hubConfiguration= new HubConfiguration
{
EnableDetailedErrors=true,
EnableJSONP= true
};
map.RunSignalR(hubConfiguration);
});
}
}
}
And Secondly Winform Client. I am confused here that how to manage the client code here but i put some code for testing as below.
private void button1_Click(object sender, EventArgs e)
{
//MessageBox.Show(test.GetsMessage("This is the Test Message"));
var hubConnection = new HubConnection("http://localhost:50172/");
var serverHub = hubConnection.CreateHubProxy("MessageRecievingHub");
serverHub.On("broadCastToClients", message => MessageBox.Show(message));
hubConnection.Start().Wait();
}
Please guide me in this manner.
Your Help will be appreciated. I have tried and googled alot but in vain.
Thanks alot in Advance.
You do not want SignalR, you need XSockets WCF sample
SignalR and WCF don't interoperate in this way, and don't really need to. If you're using SignalR, there's no reason to use WCF- you can publish your hub on IIS or self-hosted (see the Getting Started tutorial and the Self-Host tutorial at asp.net/signalr), and connect to it with desktop or JavaScript/HTML clients.
You can easily create a .NET client application to communicate with your SignalR server - below is a simple WinForm .NET C# client that sends and receives a SignalR message:
namespace SignalrTestClient
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
async void Form1_Load(object sender, EventArgs e)
{
var hubConnection = new HubConnection("http://localhost:8080/");
IHubProxy hubProxy = hubConnection.CreateHubProxy("MyHub");
await hubConnection.Start();
hubProxy.On("addMessage", message => onData(message));
await hubProxy.Invoke("Send", "Hello SignalR Server!");
}
private void onData(string msg)
{
Console.WriteLine(msg);
}
}
}
In your SignalR server you just need the following hub class:
public class MyHub : Hub
{
public void Send(string message)
{
Console.WriteLine("Received a message from a client");
if (message.Contains("Hello")) {
Clients.All.addMessage("Well hello there client!");
}
}
}
It is also possible to create a C++ client for SignalR
I am experimenting with a WCF service in a Visual Studio unit test. Both the client and the service are configured programmatically.
Currently my code looks like this:
using System;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace Tests
{
public abstract class EntityBase
{
}
public class TestEntity : EntityBase
{
public string Name { get; set; }
}
[ServiceContract]
[ServiceKnownType("GetKnownTypes", typeof(ServiceKnownTypesDiscoveryHelper))]
public interface ITestService
{
[OperationContract]
EntityBase GetEntity(string entityName);
}
public class TestService : ITestService
{
public EntityBase GetEntity(string entityName)
{
Type t = Type.GetType(entityName);
return (EntityBase)Activator.CreateInstance(t);
}
}
[TestClass]
public class ServiceTests
{
private static ServiceHost ServiceHost { get; set; }
[ClassInitialize]
public static void ClassInitialize(TestContext testContext)
{
ServiceHost = new ServiceHost(typeof(TestService));
NetTcpBinding wsBinding = new NetTcpBinding();
ServiceHost.AddServiceEndpoint(typeof(ITestService), wsBinding,
"net.tcp://localhost:8011/TestService");
// trying to turn on debugging here
var behavior = ServiceHost.Description.Behaviors.Find<ServiceDebugBehavior>();
behavior.IncludeExceptionDetailInFaults = true;
ServiceHost.Open();
}
[ClassCleanup]
public static void ClassCleanup()
{
ServiceHost.Close();
}
[TestMethod]
public void TestSomething()
{
var binding = new NetTcpBinding();
var endpoint = new EndpointAddress("net.tcp://localhost:8011/TestService");
using (ChannelFactory<ITestService> testServiceFactory =
new ChannelFactory<ITestService>(binding, endpoint))
{
var proxy = testServiceFactory.CreateChannel();
using (proxy as IDisposable)
{
try
{
var entity = proxy.GetEntity(typeof(TestEntity).FullName);
Assert.IsInstanceOfType(entity, typeof(TestEntity));
}
catch (FaultException ex)
{
// copied this from MSDN example
string msg = "FaultException: " + ex.Message;
MessageFault fault = ex.CreateMessageFault();
if (fault.HasDetail == true)
{
var reader = fault.GetReaderAtDetailContents();
if (reader.Name == "ExceptionDetail")
{
ExceptionDetail detail = fault.GetDetail<ExceptionDetail>();
msg += "\n\nStack Trace: " + detail.StackTrace;
}
}
System.Diagnostics.Trace.WriteLine(msg);
}
}
}
}
}
}
If my ServiceKnownTypesDiscoveryHelper does not return known types, I know that my service and client should throw something serialisation related somewhere deep in .NET servicemodel code (if I modify it to return my TestEntity then of course everything works without any issues).
But currently if the service fails, I get only some vague exception messages like:
The socket connection was aborted. This could be caused by an error processing your message or a receive timeout being exceeded by the remote host, or an underlying network resource issue.
and at the end of using() I get
The communication object, System.ServiceModel.Channels.ServiceChannel, cannot be used for communication because it is in the Faulted state.
(which also is weird - why can't I even dispose the ServiceChannel if it's in a faulted state...)
How do I catch the actual fault which caused the service or the client to fail instead of those vague exception messages?
I have a sample service to test WCF net.tcp communication. It is very simple service and all it does is subscribing a client to the service and then calls callbackchannel to notify all connected clients about broadcasted message. The service is hosted inside IIS 7.5.
Here is service code and test client to test it.
[ServiceContract(CallbackContract = typeof(ISampleServiceCallBack), SessionMode = SessionMode.Required)]
public interface ISampleCuratioService
{
[OperationContract(IsOneWay = true)]
void SubcribeToService(string sub);
[OperationContract]
string GetData(int value);
[OperationContract(IsOneWay = true)]
void Broadcast(string message);
}
public interface ISampleServiceCallBack
{
[OperationContract(IsOneWay = true)]
void NotifyClient(string message);
}
Here is the service implementation:
[ServiceBehavior(Name = "CuratioCSMService", InstanceContextMode = InstanceContextMode.PerSession)]
public class Service1 : ISampleCuratioService
{
private static List<ISampleServiceCallBack> JoinedClien = new List<ISampleServiceCallBack>();
public void SubcribeToService(string sub)
{
var subscriber = OperationContext.Current.GetCallbackChannel<ISampleServiceCallBack>();
if (!JoinedClien.Contains(subscriber))
{
JoinedClien.Add(subscriber);
}
}
public string GetData(int value)
{
return string.Format("You entered: {0}", value);
}
public void Broadcast(string message)
{
JoinedClien.ForEach(c => c.NotifyClient("message was received " + message));
}
}
I can not understand the behavior I get when running it. After the first client runs everything works fine but as I close and open test client app, it throws exception notifying that channel can not be used for communication as it is in fault state.
This is sample test client:
static void Main(string[] args)
{
var callneckclient = new ServiceClientProxy();
var client = new SampleCuratioServiceClient(new InstanceContext(callneckclient));
client.SubcribeToService("me");
Console.ReadLine();
for (int i = 0; i < 15; i++)
{
Console.WriteLine(client.GetData(5));
client.Broadcast("this is from client me");
}
client.Close();
Console.Read();
}
public class ServiceClientProxy : ISampleCuratioServiceCallback, IDisposable
{
public void NotifyClient(string message)
{
Console.WriteLine(message);
}
public void Dispose()
{
GC.SuppressFinalize(this);
}
}
The situation gets even buggy when I run 5 clients. Non of those send or receive messages.
When a client calls SubcribeToService you add its operation context to a List called JoinedClien.
When you call Broadcast in your server, you call the method NotifyClient on all collected operation contexts for every client that has ever connected.
The problem is, that a disconnected client won't get removed from your JoinedClien list.
When you try to call an operation method on a disconnected operation context, you get the channel is in faulted state error.
To work around, you should subscribe to the Channel_Closed and Channel_Faulted events and also catch the CommunicationException when calling back into your clients and remove the operation context of the faulted clients:
public void Broadcast(string message)
{
// copy list of clients
List<OperationContext> clientsCopy = new List<OperationContext>();
lock(JoinedClien) {
clientsCopy.AddRange(JoinedClien);
}
// send message and collect faulted clients in separate list
List<OperationContext> clientsToRemove = new List<OperationContext>();
foreach (var c in JoinedClien)
{
try {
c.NotifyClient("message was received " + message));
}
catch (CommunicationException ex) {
clientsToRemove.Add(c);
}
}
foreach (var c in clientsToRemove)
{
lock(JoinedClien) {
if(JoinedClien.Contains(c))
JoinedClien.Remove(c);
}
}
}
When adding new clients you have to lock that operation, too:
var subscriber = OperationContext.Current.GetCallbackChannel<ISampleServiceCallBack>();
lock(JoinedClien)
{
if (!JoinedClien.Contains(subscriber))
{
JoinedClien.Add(subscriber);
}
}
I want to send a data from WCF host (not service proxy) to the connected client with the service.
How can I achieve this?
You'll need to create a Duplex service. See this article for more information: http://msdn.microsoft.com/en-us/library/ms731064.aspx
Here's an example:
[ServiceContract(
SessionMode=SessionMode.Required,
CallbackContract=typeof(INotificationServiceCallback))]
public interface INotificationService
{
[OperationContract(IsOneWay = true)]
void Connect();
}
public interface INotificationServiceCallback
{
[OperationContract(IsOneWay = true)]
void SendNotification(string notification);
}
public class NotificationService : INotificationService
{
public static List<INotificationServiceCallback> Clients =
new List<INotificationServiceCallback>();
public void Connect()
{
Clients.Add(
OperationContext.Current.GetCallbackChannel<ICalculatorDuplexCallback>());
}
}
public class Notifier
{
void HandleReceivedNotification(string notification)
{
foreach (var client in NotificationService.Clients)
{
client.SendNotification(notification);
}
}
}