ExcelDNA RTD with SignalR - c#

I'm trying to hook up ExcelDNA RTD with a ASP.NET SignalR server.
Whenever there is a change on the server a message get pushed to the connected clients, and my ExcelDna add-in is getting the new messages but the registered function is not updated.
My RTD server:
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.SignalR.Client;
using ExcelDna.Integration;
using ExcelDna.Integration.Rtd;
namespace DMT.Excel.AddIn
{
[ComVisible(true)]
public class SignalRServer : ExcelRtdServer
{
private HubConnection _connection;
private List<Topic> _topics = new List<Topic>();
public TradesRtdServer()
{
_connection = new HubConnectionBuilder()
.WithUrl("http://localhost:5000/api/test/hub")
.WithAutomaticReconnect()
.Build();
_connection.On<object>("Test", m =>
{
foreach (Topic topic in _topics)
{
topic.UpdateValue(m);
}
});
Task.Run(() => _connection.StartAsync());
}
protected override bool ServerStart()
{
DmtAddIn.Logger.Information("ServerStart");
return true;
}
protected override void ServerTerminate()
{
DmtAddIn.Logger.Information("ServerTerminate");
}
protected override object ConnectData(Topic topic, IList<string> topicInfo, ref bool newValues)
{
DmtAddIn.Logger.Information("ConnectData: {0} - {{{1}}}", topic.TopicId, string.Join(", ", topicInfo));
_topics.Add(topic);
return ExcelErrorUtil.ToComError(ExcelError.ExcelErrorNA);
}
protected override void DisconnectData(Topic topic)
{
_topics.Remove(topic);
DmtAddIn.Logger.Information("DisconnectData: {0}", topic.TopicId);
}
}
}
My function
[ExcelFunction(Name = "SignalR.Test.RTD")]
public static object GetSignalRMessages()
{
return XlCall.RTD("Excel.AddIn.Trading.SignalRServer", null, "Test");
}
When I debug I can see topic.UpdateValue(m); is being hit whenever a message is pushed from the server but not GetSignalRMessages
Am I missing anything to propagate the topic change to the function?
Thank you!
Joseph

I managed to solve this by sending a string from the SignalR server, then deserialize it on the client side

The ExcelRtdServer checks whether the value passed into UpdateValue is different to the previous value. You might be passing either the same value every time, or some value that is interpreted to an Excel data type as the same (e.g. some object type that is converted to the same string every time).
You might be better off building this through the IObservable or Rx abstractions, that are a bit higher-level than the ExcelRtdServer. See the samples here https://github.com/Excel-DNA/Samples/tree/master/RtdClocks
Maybe something like this project which combines Rx and SignalR: https://github.com/jwooley/SignalrRxSamples

Related

How to make Redis notify my service about events

I have a remote computer with Redis. From time to time new entries are added to it (key-value pair). I want Redis to send notifications to my C# Service about events like this (i'm interested in value part). I've searched online and found simple code example to subscribe my Service to Redis. How to make Redis send notifications?
Service:
public partial class ResultsService : ServiceBase
{
private ConnectionMultiplexer connection = ConnectionMultiplexer.Connect(ConfigurationManager.AppSettings["RedisConnection"]);
private const string ChatChannel = "__keyspace#0__:*";
public VerificationResultsService()
{
InitializeComponent();
}
protected override void OnStart(string[] args)
{
Start();
}
public void Start()
{
var pubsub = connection.GetSubscriber();
pubsub.Subscribe(ChatChannel, (channel, message) => MessageAction(message));
while (true)
{
}
}
private static void MessageAction(RedisValue message)
{
// some handler...
}
}
Making redis send automatic keyspace notifications is a redis server configuration piece, which can be enabled via the .conf file (notify-keyspace-events), or via CONFIG SET at runtime; the documentation for this is here.
You can see how this works with example code:
using StackExchange.Redis;
using System;
using System.Linq;
static class P
{
private const string ChatChannel = "__keyspace#0__:*";
static void Main()
{
// connect (allowAdmin just lets me use ConfigSet)
using var muxer = ConnectionMultiplexer.Connect("127.0.0.1,allowAdmin=true");
// turn on all notifications; note that this is server-wide
// and is NOT just specific to our connection/code
muxer.GetServer(muxer.GetEndPoints().Single())
.ConfigSet("notify-keyspace-events", "KEA"); // KEA=everything
// subscribe to the event
muxer.GetSubscriber().Subscribe(ChatChannel,
(channel, message) => Console.WriteLine($"received {message} on {channel}"));
// stop the client from exiting
Console.WriteLine("Press any key to exit");
Console.ReadKey();
}
}
which works like:
However, in many scenarios you may find that this is too "noisy", and you may prefer to use either a custom named event that you publish manually when you do things that need notification, or (again manually) you could make use of the streams features to consume a flow of data (streams can be treated as a flow of events in the "things that happened" sense, but they are not delivered via pub/sub).

WCF server not sending data to excel on another computer

I have a C# application which takes data in from a sensor.
It has the code below: (unfortunately not written by me and not documented, and the old developer has long left the country).
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;
namespace Client
{
[ServiceContract]
public interface IDataRequest
{
[OperationContract]
string query(string instr, string attr);
}
public class DataRequest : IDataRequest
{
public string query(string symbol, string attr)
{
//data_set is a simple dictionary in another file
Data data = MAIN.data_set[symbol].Value;
return data.last;//numeric value of sensor reading
}
}
class WCFServer
{
ServiceHost host = null;
public void open()
{
host = new ServiceHost(
typeof(DataRequest),
new Uri[]{new Uri("net.pipe://localhost")});
host.AddServiceEndpoint(typeof(IDataRequest), new NetNamedPipeBinding(), "DataRequest");
try
{
host.Open();
}
catch (AddressAlreadyInUseException) { }
}
public void close()
{
try
{
host.Close();
}
catch (CommunicationObjectFaultedException) { }
}
}
}
Then in Excel, I can place in a cell:
=RTD("data",,"sensor1","last"
Everything works fine on the one computer.
However, when I move the application and excel file to another computer, nothing works, instead of seeing the data value streaming into excel I just see #N/A. The client application itself works on another computer, so i know the C# code is fine, just something to do with the WCF link from C# to Excel.
Is there something else involved? Registration of the WCF server or anything?
Add the servername in RTD: RTD("data,server_name,"sensor1","last"). Documentation: https://support.microsoft.com/en-us/kb/289150

System.InvalidOperationException when calling WCF Service

I'm trying to create a WCF callback service with netTcpBinding. When I try to call a method of the service I get following exception:
An unhandled exception of type 'System.InvalidOperationException' occurred in System.ServiceModel.dll
Additional information: The InstanceContext provided to the ChannelFactory contains a UserObject that does not implement the CallbackContractType 'Client.WCFService.IHostFunctionsCallback'.
I've added a service reference instead of using SvcUtil.exe
I've searched the internet for fixing this problem, but I haven't found a solution yet.
Here's my implementation:
IHostFunctions.cs (Part of HostLibrary)
using System.ServiceModel;
namespace HostLibrary
{
[ServiceContract(CallbackContract = typeof(ICallback))]
public interface IHostFunctions
{
[OperationContract]
void OpenSession();
}
}
ICallback.cs (Part of HostLibrary)
using System.ServiceModel;
namespace HostLibrary
{
public interface ICallback
{
[OperationContract]
void OnCallback();
}
}
HostFunctions.cs (Part of HostLibrary)
using System;
using System.ServiceModel;
using System.Timers;
namespace HostLibrary
{
[ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Multiple)]
public class HostFunctions : IHostFunctions
{
#region Implementation of IHostFunctions
public static ICallback Callback;
public static Timer Timer;
public void OpenSession()
{
Console.WriteLine("> Session opened at {0}", DateTime.Now);
Callback = OperationContext.Current.GetCallbackChannel<ICallback>();
Timer = new Timer(1000);
Timer.Elapsed += OnTimerElapsed;
Timer.Enabled = true;
}
void OnTimerElapsed(object sender, ElapsedEventArgs e)
{
Callback.OnCallback();
}
#endregion
}
}
Callback.cs (Part of Client)
using System;
using HostLibrary;
namespace Client
{
public class Callback : ICallback
{
#region Implementation of ICallback
public void OnCallback()
{
Console.WriteLine("> Received callback at {0}", DateTime.Now);
}
#endregion
}
}
Program.cs of the service
using System;
using System.ServiceModel;
using HostLibrary;
namespace WCF_TCP_Callbacks
{
internal static class Program
{
private static void Main(string[] args)
{
using (var sh = new ServiceHost(typeof (HostFunctions)))
{
sh.Open();
Console.WriteLine("Service started.");
Console.ReadLine();
Console.WriteLine("Stopping service...");
sh.Close();
}
}
}
}
Program.cs of the client
using System;
using System.Globalization;
using System.ServiceModel;
using System.Threading;
using Client.WCFService;
namespace Client
{
internal class Program
{
private static void Main(string[] args)
{
var callback = new Callback();
using (var proxy = new HostFunctionsClient(new InstanceContext(callback)))
{
proxy.OpenSession();
}
Console.ReadLine();
}
}
}
The code is from http://adamprescott.net/2012/08/15/a-simple-wcf-service-callback-example/ but with netTcpBinding.
by default WCF will attempt to dispatch using an available SynchronizationContext. The problem with this callback is the UI thread is already blocked in an outbound call. SO for the call to dispatch we need to tell WCF not to use the SynchronizationContext – again using the CallbackBehavior attribute:
[CallbackBehavior(ConcurrencyMode=ConcurrencyMode.Reentrant, UseSynchronizationContext=false)]
public class Callback : ICallback
{
....
}
for further detail look this link http://www.dotnetconsult.co.uk/weblog2/PermaLink,guid,b891610a-6b78-4b54-b9a6-4ec81c82b7c0.aspx
and one more post describe it further
http://stefanoricciardi.com/2009/08/28/file-transfer-with-wcp/
I fixed that problem by simply renaming the class ICallback to IHostFunctionsCallback.
I still don't know why this works now as I didn't use IHostFunctionsCallback before.
I know post is old :
What is really the name of your callback Class?
the code you posted says this:
Callback : ICallback
The Error Message says this:
CallbackContractType 'Client.WCFService.IHostFunctionsCallback'
So is the Callback as per your code above, or is it really defined as:
Client.WCFService.IHostFunctionsCallback
I would say you have decorated an attribute reference to the callback channel incorrectly, or inherited from the wrong callback. Search your project to make sure you named everything correctly.
EDIT
As to why the fix worked and what happened:
I answered for the case of others.It may be if you were in a team environment that someone changed the name of the Callback class interface from so generic to something more understandable - in WCF that is what your userobject is - it is the contract. You may have used Visual Studio to generate your Client or Service at one point. which can also foul things up, which is what it looks like to me as the naming convention follows IService[Callback].

Progress update while WCF is executed

I have an ASP.NET 3.5 app consuming a C# WCF in an intranet.
The service executes three commands sequentially taking 2-3 mins each. I'd like to keep the user updated with the command that is running, for example, refreshing a label.
I'm not an expert on this matter so I'd like to know what is the best way to do this.
Thanks,
Ps. The service and the client are hosted in the same server using IIS 7.5.
EDIT
Well, I've been working on this for the past two days .. I'm not an expert :)
I'm following Eric's suggestion, use of WSHttpDualBinding and a callback function.
So, I was able to build a service using duplex binding and define a callback function, however I can't define the callback function on the client side, can you please shed some light on this.
namespace WCF_DuplexContracts
{
[DataContract]
public class Command
{
[DataMember]
public int Id;
[DataMember]
public string Comments;
}
[ServiceContract(SessionMode = SessionMode.Required, CallbackContract = typeof(ICallbacks))]
public interface ICommandService
{
[OperationContract]
string ExecuteAllCommands(Command command);
}
public interface ICallbacks
{
[OperationContract(IsOneWay = true)]
void MyCallbackFunction(string callbackValue);
}
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
public class CommandService : ICommandService
{
public string ExecuteAllCommands(Command command)
{
CmdOne();
//How call my callback function here to update the client??
CmdTwo();
//How call my callback function here to update the client??
CmdThree();
//How call my callback function here to update the client??
return "all commands have finished!";
}
private void CmdOne()
{
Thread.Sleep(1);
}
private void CmdTwo()
{
Thread.Sleep(2);
}
private void CmdThree()
{
Thread.Sleep(3);
}
}
}
EDIT 2
This the client implementation,
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Client.DuplexServiceReference;
using System.ServiceModel;
namespace Client
{
class Program
{
public class Callback : ICommandServiceCallback
{
public void MyCallbackFunction(string callbackValue)
{
Console.WriteLine(callbackValue);
}
}
static void Main(string[] args)
{
InstanceContext ins = new InstanceContext(new Callback());
CommandServiceClient client = new CommandServiceClient(ins);
Command command = new Command();
command.Comments = "this a test";
command.Id = 5;
string Result = client.ExecuteAllCommands(command);
Console.WriteLine(Result);
}
}
}
And the result:
C:\>client
cmdOne is running
cmdTwo is running
cmdThree is running
all commands have finished!
Use a duplex binding and update status in a callback.
EDIT*
You need to get a reference to the callback channel
public string ExecuteAllCommands(Command command)
{
var callback = OperationContext.Current.GetCallbackChannel<ICallbacks>();
CmdOne();
//How call my callback function here to update the client??
callback.MyCallbackFunctio("cmdOne done");
CmdTwo();
callback.MyCallbackFunctio("cmdTwo done");
//How call my callback function here to update the client??
CmdThree();
callback.MyCallbackFunctio("cmdThree done");
//How call my callback function here to update the client??
return "all commands have finished!";
}
You'll prolly want the service method to be void as to not time out
I would create a couple of operations. One to start the lengthy command, and another to get the status. If you have one operation that waits for the command to complete, you run into the problem of timeouts and have no way to figure out progress.

How share data in WCF webservice

In order to call webservices dynamicly, I use WCF Dynamic Proxy from Microsoft
If I understood properly how it works, the code load the wsdl and compile on system class in order to consume distant webservice. I put this code in a "generic webservice". Its goal is to call any webservice with a request in parameter, and respond the answer of the webservice called.
But a problem appears : each request to this "generic webservice" pulls a new compilation of the proxy, and use time and ressources of the server.
My objective is to save instance of each proxies during a laps of time, and renew the instance when this laps is reached.
After few hours of googling, I found two ways :
Use my WCF webservice "by session", but I don't find any tutorial which explains how create easily the session layer
Use a singleton in order to save my datas and mutualize them with all instances of webservice
I exclude the first solution because I don't know how to do this. So I decided to use the second way.
There is my implementation :
FactoryTest is the singleton, contening the hashtable with instances
ProxyTest is the class which contains information about each instances of distant webservices
There is the code of FactoryTest :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using WcfSamples.DynamicProxy;
using System.Threading;
using System.Collections;
namespace WS_Generic
{
public sealed class FactoryTest
{
private static object syncRoot = new Object();
private static Hashtable hashFactory = new Hashtable();
public static DynamicProxy getProxy(String sServiceWsdl, String sContract)
{
if (hashFactory[sServiceWsdl] == null || ((ProxyTest)hashFactory[sServiceWsdl]).getTimeFromCreation().TotalSeconds > 60 * 60 * 6)
{
lock (syncRoot)
{
if (hashFactory[sServiceWsdl] == null || ((ProxyTest)hashFactory[sServiceWsdl]).getTimeFromCreation().TotalSeconds > 60 * 60 * 6)
{
hashFactory.Add(sServiceWsdl, new ProxyTest(sServiceWsdl, sContract));
}
}
}
return ((ProxyTest)hashFactory[sServiceWsdl]).getProxy();
}
public static bool isProxyExists(String sServiceWsdl, String sContract)
{
lock (syncRoot)
{
return hashFactory[sServiceWsdl] == null ? false : true;
}
}
}
}
There is the code of ProxyTest :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using WcfSamples.DynamicProxy;
namespace WS_Generic
{
public class ProxyTest
{
private DateTime instanceCreation;
private String sServiceWsdl;
private String sContract;
private DynamicProxyFactory factory;
private volatile DynamicProxy proxy;
public ProxyTest(String sServiceWsdl, String sContract)
{
instanceCreation = DateTime.Now;
this.sServiceWsdl = sServiceWsdl;
this.sContract = sContract;
this.factory = new DynamicProxyFactory(this.sServiceWsdl);
this.proxy = factory.CreateProxy(this.sContract);
}
public DynamicProxy getProxy()
{
return proxy;
}
public TimeSpan getTimeFromCreation()
{
return DateTime.Now.Subtract(instanceCreation);
}
}
}
The problem is the webservice seems to reset the static status of FactoryTest after each call. So each time I called the webservice, my hashtable is empty and the factory create a new instance.
If anybody had already the problem of share datas between differents threads in WCF webservice (and found the solution), thanks in advance to give me some tips :)
PS : Sorry for my english, that's not my native language
If you store data in static variable WCF itself will not affect their purging. The problem must be somewhere else (application restart, app domain recreation, etc.).
Btw. this solution has only very limited usage because long living shared proxies should not be used and in many cases it can result in unexpected behavior. It can perhaps work only for services using basicHttpBinding.

Categories