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.
Related
I am using C# gRPC with GrpcDotNetNamedPipes to do interprocess communication on the same machine.
Currently, I am having a problem in that if my service is not set up, and I invoke a service request from my client side -- the client side just locks up waiting until the service is available.
I am unsure how to check if client has been connected to the service.
Example code below:
///Autogenerated gRPC code that contains COM API
public static partial class PluginCOM
{
public partial class PluginCOMClient : grpc::ClientBase<PluginCOMClient>
{
//Autogenerated code from protofile
//...
}
}
/// Client class that
public class PluginClient : PluginCOM.PluginCOMClient
{
public PluginClient() : base(new GrpcDotNetNamedPipes.NamedPipeChannel(".", "Service"))
{
}
public bool Test() => Test(new Empty()).Loaded;
}
public static class Tester
{
static void Test()
{
client = new PluginClient();
client.Test(); /// Deadlocks here and waits until service is available
}
}
Calling the Tester.Test() function dead locks when attempting to call client.Test().
I would like something like:
public static class Tester
{
static void Test()
{
client = new PluginClient();
if (/* code here to check if client is connected */)
{
client.Test();
}
}
}
I've had the same problem, but after looking at issues on official GitHub page finally found the solution:
https://github.com/cyanfish/grpc-dotnet-namedpipes/issues/17
By default connection timeout is set by Infinite, but you can set up timeout by yourself using NamedPipeChannelOptions:
public PluginClient() : base(new GrpcDotNetNamedPipes.NamedPipeChannel(".","Service",
new GrpcDotNetNamedPipes.NamedPipeChannelOptions { ConnectionTimeout = 350 }))
{
}
im new to Akka.net, im running on linux and using .NET Core 3.1, i have written a very simple code but it does not work and i dont know why.
this my program.cs where i created the ActorSystem and simply called another actor
using Akka.Actor;
namespace PBFT
{
class Program
{
static void Main(string[] args)
{
var MyActorSystem = ActorSystem.Create("ActorSystem");
var Primary = MyActorSystem.ActorOf<PrimaryActor>();
Primary.Tell("Test");
}
}
}
and this is the first actor that is supposed to receive the message and simply outputs it to the console
using Akka.Actor;
using Akka;
using Akka.Event;
namespace PBFT
{
class PrimaryActor : ReceiveActor
{
private readonly ILoggingAdapter log = Context.GetLogger();
public PrimaryActor()
{
Receive<string>(message => System.Console.WriteLine(message));
}
}
}
the problem is there are no errors and the message does not get processsed by the Actor, am i missing something ?
Messages to and between actors in Akka.NET are passed asynchronously. What happens in your example, is that you're Telling a message to an actor and exiting the program immediately afterwards, before actor got a chance to process the message.
You can either suspend main thread (using eg. Console.ReadLine()) in your example, or - if you need to be sure that actor had processed the message before going forward - use combination of actor.Ask(message, cancellationToken) on the caller side (which will return Task, that completes once actor sends a response back) and Sender.Tell(response) inside your actor's receive method:
class PrimaryActor : ReceiveActor
{
private readonly ILoggingAdapter log = Context.GetLogger();
public PrimaryActor()
{
Receive<string>(message => {
System.Console.WriteLine(message);
Sender.Tell(new object()); // or any other response you want
});
}
}
class Program
{
static async Task Main(string[] args)
{
var MyActorSystem = ActorSystem.Create("ActorSystem");
var Primary = MyActorSystem.ActorOf<PrimaryActor>();
await Primary.Ask<object>("Test", default(CancellationToken));
}
}
AKA "Send messages to clients from a background service"
I would like my SignalR server to update a dashboard every n seconds. I'm using this right now:
public class MyHub : Hub
{
public override Task OnConnectedAsync()
{
Observable.Interval(TimeSpan.FromSeconds(0.5)).Subscribe(l =>
{
var alt = CalcAltitude(l);
SendMessage(alt);
});
return Task.CompletedTask;
}
private void SendMessage(double alt)
{
Clients.All.SendAsync("SendAction", new Status() {Altitude = alt});
}
private double CalcAltitude(long l)
{
return 100 * Math.Sin((double) l / 100) + 200;
}
}
public class Status
{
public double Altitude { get; set; }
}
When my code is executed, it throws an exception saying that
cannot access a disposed object
I suspect I'm doing something wrong here.
So, what's the correct way to make send messages to all the clients on a timely manner?
OK, this time the answer didn't come from Stack Overflow, but from another source.
This can be done with this code in ASP.NET Core + SignalR Core.
You'll need 2 parts.
the Background Service itself (updates the clients): https://github.com/davidfowl/UT3/blob/fb12e182d42d2a5a902c1979ea0e91b66fe60607/UTT/Scavenger.cs
the Setup (wiring) part: https://github.com/davidfowl/UT3/blob/fb12e182d42d2a5a902c1979ea0e91b66fe60607/UTT/Startup.cs#L46
Big thanks to David Fowler for the reply!
var hubContext = GlobalHost.ConnectionManager.GetHubContext<MyHub>();
hubContext.Clients.All.SendnewData(Data);
Put this code where your data is updated or calculated in every second.
Like: Your DB process is done then Broadcast new data
using Microsoft.AspNet.SignalR;
using Microsoft.AspNet.SignalR.Infrastructure;
using Microsoft.AspNet.Mvc;
public class TestController : Controller
{
private IHubContext testHub;
public TestController(IConnectionManager connectionManager)
{
testHub = connectionManager.GetHubContext<TestHub>();
testHub.Clients.All.SendnewData(Data);
}
}
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].
I'm tring to implement my first WCF call-back server. This is my code:
[ServiceContract(SessionMode = SessionMode.Required, CallbackContract = typeof(ILogCallback))]
public interface ILog
{
}
public interface ILogCallback
{
[OperationContract(IsOneWay = true)]
void Push(string callbackValue);
}
public class MyLog : ILog
{
}
class Log
{
public static void initialize()
{
using (ServiceHost host = new ServiceHost(
typeof (MyLog),
new Uri[]
{
new Uri("net.pipe://localhost")
}))
{
host.AddServiceEndpoint(typeof (ILog),
new NetNamedPipeBinding(),
"PipeReverse");
host.Open();
// TODO: host.Close();
}
}
public static void Push(string s)
{
ILogCallback callbacks = OperationContext.Current.GetCallbackChannel<ILogCallback>();
callbacks.Push(s);
}
}
then I try to use my server using this code:
Log.initialize();
while (true)
{
Log.Push("Hello");
System.Threading.Thread.Sleep(1000);
}
But I got NPE, because OperationContext.Current is null. Why, what's wrong and how to fix that?
Because you are NOT in the context of an operation.
You're simply calling a static method of the Log class.
For you to be in an Operation Context your call MUST have been come from a client that is being serviced by your WCF server.
OperationContext.Current is a thread-static property that is initialized when request arrives to the server. Here's what you do to call the callback
[ServiceContract(SessionMode = SessionMode.Required, CallbackContract = typeof(ILogCallback))]
public interface ILog
{
void PushOnTheClient();
}
public class MyLog : ILog
{
void PushOnTheClient()
{
ILogCallback callbacks = OperationContext.Current.GetCallbackChannel<ILogCallback>();
callbacks.Push(s);
}
}
You are missing the subscriber's subscription. The way you do this is to create a [oneway] operation in your MyLog WCF server called something like: "void SendMeLogs()".
This will open the client callback channel. You Then have to implement SendMeLogs() in lines of something like:
void SendMeLogs()
{
while(CheckLogsForNewData())
{
PushOnTheClient();
}
}
Since the SendMeLogs() function is oneway, the client will not block, but will start the subscription to your log server. (you can search the net for sample code for duplex calculator in wcf for a good example of this architecture).
The key however is that you must have a nice unsubscribe method like "StopSendingMeLogs" to break the loop, and also make the PushOnTheClient function fail safe, in case the client terminates or the specific client connection goes down.
The "CheckLogsForNewData" function should ideally be a shared (static) implementation in your case