Get WinForm "Cross-thread operation not valid" error for WCF host application ONLY while running in Visual Studio - winforms

I have a WCF service that I am self-hosting in a WinForm application. In the application I have a ListBox control to display diagnostic information. I pass a reference to the ListBox control to the WCF service. In that service there is a callback that uses it.
For some reason if I run the application outside of Visual Studio, I am able to use the ListBox to display information in the main hosting application, all methods in the WCF service, and the single callback in the WCF service. But, running in Visual Studio, the same application fails when trying to write info while in the callback.
Am I doing something that I fundamentally cannot do and getting away with it OR is there something incorrect going on while running in Visual Studio?
Here is part of my WinForm host
public WCFServiceHostGUI()
{
InitializeComponent();
// OutputBox is a ListBox control reference. Pass to WCF service to display diagnostics
WCFCallbacks.UsbBrokerService.initialize(OutputBox);
startService();
}
private void startService()
{
usbBrokerServiceHost = new ServiceHost(typeof(WCFCallbacks.UsbBrokerService));
ServiceDescription serviceDesciption = usbBrokerServiceHost.Description;
foreach (ServiceEndpoint endpoint in serviceDesciption.Endpoints)
{
OutputBox.Items.Add("Endpoint - address: " + endpoint.Address);
OutputBox.Items.Add(" - binding name: " + endpoint.Binding.Name);
OutputBox.Items.Add(" - contract name: " + endpoint.Contract.Name);
}
usbBrokerServiceHost.Open();
/*
ChannelFactory<WCFCallbacks.IUsbBroker> channelFactory = new ChannelFactory<WCFCallbacks.IUsbBroker>(BINDING, ADDRESS);
WCFCallbacks.IUsbBroker clientProxy = channelFactory.CreateChannel();
clientProxy.initialize(OutputBox);
*/
}
In the constructor a reference to the ListBox is passed to the WCF service. One can see the usual creation of the ServiceHost in the startService() method.
Here is the WCF service that gets the ListBox reference
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
public class UsbBrokerService : IUsbBroker
{
private static readonly List<IUsbBrokerCallback> subscribers = new List<IUsbBrokerCallback>();
private static ListBox OutputBox = null;
public UsbBrokerService()
{
Console.WriteLine("Service started");
UsbDevicePluginIndicator.DeviceNotify += new UsbDevicePluginIndicator.DeviceNotifyDelegate(UsbDevicePluginIndicator_DeviceNotify);
UsbDevicePluginIndicator.Start();
}
public static void initialize(ListBox outputBox)
{
OutputBox = outputBox;
}
public void AddMessage(string message)
{
// Use the following to see which endpoint is accessed
OperationContext oc = OperationContext.Current;
if (oc != null)
{
Console.WriteLine("A request was made on endpoint " + oc.Channel.LocalAddress.ToString());
if (OutputBox != null)
{
OutputBox.Items.Add("A request was made on endpoint " + oc.Channel.LocalAddress.ToString());
}
}
}
public bool RegisterDevices(UsbDevice[] usbDevices)
{
try
{
IUsbBrokerCallback callback = OperationContext.Current.GetCallbackChannel<IUsbBrokerCallback>();
if (!subscribers.Contains(callback))
{
subscribers.Add(callback);
}
return true;
}
catch
{
return false;
}
}
public bool UnRegisterDevices(UsbDevice[] usbDevices)
{
try
{
IUsbBrokerCallback callback = OperationContext.Current.GetCallbackChannel<IUsbBrokerCallback>();
if (subscribers.Contains(callback))
{
subscribers.Remove(callback);
}
return true;
}
catch
{
return false;
}
}
private void UsbDevicePluginIndicator_DeviceNotify(System.Windows.Forms.Message msg)
{
BroadcastHeader lBroadcastHeader;
Console.WriteLine("Wcf WM_DEVICECHANGE signaled");
if (OutputBox != null)
{
OutputBox.Items.Add("Wcf WM_DEVICECHANGE signaled");
}
}
}
In the callback routine UsbDevicePluginIndicator_DeviceNotify the attempt to write to OutputBox fails with "Cross-thread operation not valid: Control 'OutputBox' accessed from a thread other than the thread it was created on." but ONLY while running in Visual Studio.
So am I doing something fundamentally wrong?

Related

Callback method on the SignalR Wpf client side

I have 2 .NET solutions. One is asp.net mvc web application which is signalr server. The other is SignalR WPF client that consumes the collection of custom class objects from the web signalr server. I run the signalr MVC web server with IIS. The signalr WPF client application is run in another VS instance.
The SignalR web server has a Hub method (named "MessageLogHub", please see the below codes) that uses SqlDependency to broadcast collection of custom class objects to all signalr clients if there is a change (e.g. manually inserting data into the SQL Server database table) in a target database table. The collection of custom class objects are all of the records of a SQL Server database server.
From the signalr WPF client application side, I let the client register the SignalR server method as follows:
Proxy.On<List<MessageLog>>("BroadcastMessage", OnBroadcastMessage);
And the call back method named "OnBroadcastMessage" looks like:
private void OnBroadcastMessage(List<MessageLog> logs)
{
// problem: this callback method is hits once there is a put from signalr server. but the "logs" parameter's value is never changed !!!
Dispatcher.Invoke(() =>
{
textblockTime.Text = DateTime.Now.ToLongTimeString() + " - logs.Count=" + logs.Count();
datagridLogs.ItemsSource = null;
datagridLogs.ItemsSource = logs;
});
}
So when there is a change of database, the SignalR web server will calls the method "BroadcastMessage" on all connected clients (please see the below Signalr Hub server codes) and the callback method (on the SignalR WPF client codes) "OnBroadcastMessage" will be fired automatically on clients (WPF or web app clients). In VS, I debugs the call back method named "OnBroadcastMessage" and I see it is hit when there is a change in the database that triggers the SignalR server codes to broadcast to all connected clients.
The problem is in the signalr Wpf client callback method of "OnBroadcastMessage" (please see the above code of callback method of "OnBroadcastMessage"), the value of the "logs" parameter is not changed anytime the debug break-point is hit in that callback method. If a row is inserted in the database table, then the returned value of "logs" parameter is supposed to be changed and the new "logs" parameter is put to the signalr client side. Please help me.
I have another signalr asp.net MVC web project that is signalr client and it is embedded in the SignalR MVC web server solution. It works fine with the signalr server and I see the changes in real time way with that signalR web client. But the signalr WPF client application is not working as mentioned above.
Here are the codes (the xaml code behind file) for signalr WPF client application:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using MessageWatchWpfClient.Models;
using Microsoft.AspNet.SignalR.Client;
namespace MessageWatchWpfClient
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private string hostUrl = "http://localhost/MessageWatch/signalr";
public IHubProxy Proxy { get; set; }
public HubConnection Connection { get; set; }
public MainWindow()
{
InitializeComponent();
}
private async void Window_Loaded(object sender, RoutedEventArgs e)
{
try
{
Connection = new HubConnection(hostUrl);
Proxy = Connection.CreateHubProxy("MessageLogHub");
// 1) Register so that Server automatically calls Client when database is changed
Proxy.On<List<MessageLog>>("BroadcastMessage", OnBroadcastMessage);
await Connection.Start();
// 2) Client calls Server on the Wpf application loading and the following is run one time only
await Proxy.Invoke<List<MessageLog>>("GetAllMessagesLog")
.ContinueWith((t) =>
{
Dispatcher.Invoke(() =>
{
textblockTime.Text = DateTime.Now.ToLongTimeString() + " - logs.Count=" + t.Result.Count();
datagridLogs.ItemsSource = t.Result;
});
}
);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
private void OnBroadcastMessage(List<MessageLog> logs)
{
// problem: this callback method is hits once there is a put from signalr server. but the "logs" parameter's value is never changed !!!
Dispatcher.Invoke(() =>
{
textblockTime.Text = DateTime.Now.ToLongTimeString() + " - logs.Count=" + logs.Count();
datagridLogs.ItemsSource = null;
datagridLogs.ItemsSource = logs;
});
}
}
}
The following codes are for SignalR Hub class on the server side application that is Asp.net MVC web and is hosted on IIS:
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Data.SqlClient;
using System.Globalization;
using MessageWatch.Models;
using Microsoft.AspNet.SignalR;
using SD = SignalR.Dal;
using MessageWatch.Utils;
namespace MessageWatch.Hubs
{
public class MessageLogHub : Hub
{
NLog.Logger _logger = NLog.LogManager.GetCurrentClassLogger();
public IEnumerable<MessageLog> GetAllMessagesLog()
{
var messageList = new List<MessageLog>();
try
{
var conStr = ConfigurationManager.ConnectionStrings["SignalRDatabase"].ToString();
var connection = new SqlConnection(conStr);
const string query = "SELECT Message,EventID,LL.Name as LogLevelID,OC.Name as OperationCodeID,ML.ServerName,ML.ComponentName,ML.SubComponentName FROM [dbo].[MessageLog] ML inner join [dbo].[LogLevel] LL on ML.LogLevelID = LL.ID inner join [dbo].[OperationCode] OC on ML.OperationCodeID = OC.ID order by ML.ID desc";
SqlDependency.Start(conStr); // to avoid error "When using SqlDependency without providing an options value, SqlDependency.Start() must be called prior to execution of a command added to the SqlDependency instance."
var command = new SqlCommand(query, connection);
var dependency = new SqlDependency(command);
//If Something will change in database and it will call dependency_OnChange method.
dependency.OnChange += dependency_OnChange;
connection.Open();
var da = new SqlDataAdapter(command);
var dt = new DataTable();
da.Fill(dt);
for (var i = 0; i < dt.Rows.Count; i++)
{
var ml = new MessageLog
{
Name = dt.Rows[i]["Message"].ToString(),
EventID = Convert.ToInt32(dt.Rows[i]["EventID"].ToString()),
LogLevelName = dt.Rows[i]["LogLevelID"].ToString(),
OperationCodeName = dt.Rows[i]["OperationCodeID"].ToString(),
ServerName = dt.Rows[i]["ServerName"].ToString(),
ComponentName = dt.Rows[i]["ComponentName"].ToString(),
SubComponentName = dt.Rows[i]["SubComponentName"].ToString()
};
messageList.Add(ml);
}
}
catch (Exception ex)
{
_logger.Error(CultureInfo.CurrentCulture, "{0}", ex.Message);
}
return messageList;
}
private void dependency_OnChange(object sender, SqlNotificationEventArgs e)
{
if (e.Type == SqlNotificationType.Change)
{
SendNotifications();
}
}
private void SendNotifications()
{
var messageList = GetAllMessagesLog();
IHubContext context = GlobalHost.ConnectionManager.GetHubContext<MessageLogHub>();
context.Clients.All.broadcastMessage(messageList);//Will update all the clients with message log.
}
public void CreateData()
{
try
{
IHubContext context = GlobalHost.ConnectionManager.GetHubContext<MessageLogHub>();
var dbContext = new SD.SignalREntities();
var repository = new SD.GenericRepository(dbContext);
var ml = new DataRandomer().Create();
repository.Add(ml);
dbContext.SaveChanges();
context.Clients.All.createData();
}
catch (Exception ex)
{
_logger.Error(CultureInfo.CurrentCulture, "{0}", ex.Message);
}
}
}
}

Binding ComboBox and ObservableCollection<KeyValue> in wpf

in My OpenViewModel i collect data:
private ObservableCollection<KeyValue> availableData;
public ObservableCollection<KeyValue> AvailableDatas
{
get { return availableData; }
set
{
if (value != availableData)
{
availableData= value;
NotifyOfPropertyChange("AvailableDatas");
}
}
}
method for collecting data:
public ObservableCollection<KeyValue> CollectData()
{
ConnectorClient client = null;
try
{
client = webservice.GetClient();
AvailableDatas = client.GetDatas();
client.Close();
}
catch (Exception ex)
{
webservice.HandleException(ex, client);
}
return AvailableDatas;
}
How to call the method CollectData in wpf and fill my COmboBox?
thx
You might simply call the method the first time the AvailableDatas property is accessed (e.g. from a binding in XAML):
private ObservableCollection<KeyValue> availableData;
public ObservableCollection<KeyValue> AvailableDatas
{
get
{
if (availableData == null)
{
availableData = CollectData();
}
return availableData;
}
set
{
if (value != availableData)
{
availableData = value;
NotifyOfPropertyChange("AvailableDatas");
}
}
}
Then you should change the CollectData method in a way that is does not also set the property:
public ObservableCollection<KeyValue> CollectData()
{
ConnectorClient client = null;
ObservableCollection<KeyValue> data = null;
try
{
client = webservice.GetClient();
data = client.GetDatas();
client.Close();
}
catch (Exception ex)
{
webservice.HandleException(ex, client);
}
return data;
}
You could override the OnActivated() event assuming you are using an IScreen implementation and load data in there, or just do it in the constructor or a custom Initialise method if you want to roll your own (or in the property accessor as someone has already said).
You can also use coroutines if you want some visual context for the user and a better tie in with CM actions
There is a nice simple implementation of a Loader class here which helps provide visual context to the user:
https://caliburnmicro.codeplex.com/wikipage?title=IResult%20and%20Coroutines&referringTitle=Documentation
This searches the visual tree for a BusyIndicator control and activates it whilst the content is loading e.g. ...
public class SomeViewModel : Screen
{
protected override void OnActivate()
{
RefreshData();
}
public void RefreshData()
{
Coroutine.BeginExecute(LoadData(), new ActionExecutionContext() { Target = this });
}
public IEnumerable<IResult> LoadData()
{
yield return Loader.Show("Loading Data...");
yield return new LoadSomeDataRoutine(client.GetDatas);
yield return Loader.Hide();
}
}
The reason to have a RefreshData method is that this also allows you to bind CM actions and allows the coroutine can grab more contextual information.
Obviously you have less need to worry about the async->sync benefits this gives in Silverlight because you are using WPF (but it still applies to async web service calls), however it still has many benefits and it also helps you to write reusable routines which become part of your application framework (e.g. some level of error handling/logging encapsulated in the IResult implementation etc)
You also mentioned filling the combobox... all you would need to do in CM is place a combobox on your control, and set it's Name property to the name of the property on your VM:
public class SomeViewModel : Screen
{
public ObservableCollection<MyObject> MyProperty { //blah blah... }
}
<UserControl .... blah>
<ComboBox x:Name="MyProperty" />
</UserControl>
This will fill the combobox with the items. You will still need to set the binding for SelectedItem/SelectedValue
I assume you know this already though - if not CM has some decent documentation:
https://caliburnmicro.codeplex.com/wikipage?title=Basic%20Configuration%2c%20Actions%20and%20Conventions&referringTitle=Documentation

Ria Service And OOB : check if service is reachable, MEF not importing my views

Hi currently I am trying to check if the Ria Service is available for our OOB application.
public static void IsServiceReachable(Action onServiceAvailable, Action onServiceUnavailable)
{
try {
DomainContext context = new DomainContext();
InvokeOperation<bool> invokeOperation = context.IsAlive();
invokeOperation.Completed += (s, arg) => onServiceAvailable();
}
catch (Exception) {
onServiceUnavailable();
}
}
When the exception happen my App hangs, and is now just a white screen. Am I doing it correctly?
I am also using MEF in the app, I am lazy importing my views, sadly when Ria Service is not reachable, MEF doesnt import my views :( I am calling CompositionInitializer.SatisfyImports(this).
[ImportMany(AllowRecomposition = true)]
public Lazy<BaseUserControl, IViewMetadata>[] Views { get; set; }
Have you tried checking if an error has occured in the OnServiceAvailable callback:
void OnServiceAvailable(object sender, EventArgs e)
{
InvokeOperation op = sender as InvokeOperation;
if (op.HasError) {
Exception exception = op.Error;
...
} else {
...
}
}
You should probably rename OnServiceAvailable something like OnOperationComplete.
You have to handle the errors in the callback - including the 'ServiceNotAvailable' error. Remember this is an asyncronous call - the client does does not wait for the server response before it continues.

Invoke operation in wcf ria service

I use some invoke operation method in wcf ria service.like following method:
[Invoke]
public int GetCountOfAccountsByRequestId(long requestId)
{
return ObjectContext.Accounts.Count(a => a.RequestId ==requestId);
}
When i want get data from this method , i use the following code to run and get value from invoke method:
int countOfAccounts = 0;
InvokeOperation<int> invokeOperation = context.GetCountOfAccountsByRequestId(selectedRequest.RequestId);
invokeOperation.Completed += (s, args) =>
{
if (invokeOperation.HasError)
{
var messageWindow = new MessageWindow(invokeOperation.Error.Message);
messageWindow.Show();
invokeOperation.MarkErrorAsHandled();
}
else
{
countOfAccounts = invokeOperation.Value;
if (countOfAccounts == 0)
{
// do some thing
}
}
};
But this method requires a lot of coding to Run invoke method and get value from this.Also as part of this code is executed asynchronously.Similarly, some method must be implemented in tandem.And the return value of each method is related to previous methods.
How can i implement this actions?!
How can i write better than this without any problem?
For WCF RIA Services I usually create a service class in my project to handle all the interactions with the service in one place. So I would use this service class to make the calls easier to understand and use.
I put your existing code in a separate class so you can see how it might be called. Note: This is just an example. :)
public class ServiceWrapper
{
...
public void GetCountOfAccountsByRequestId(int requestId, Action<int> callback)
{
context.GetCountOfAccountsByRequestId(requestId, InvokeComplete, callback);
}
private void InvokeComplete<T>(InvokeOperation<T> io)
{
var callback = (Action<T>)io.UserState;
if (io.HasError == false)
{
callback(io.Value);
}
else
{
var messageWindow = new MessageWindow(io.Error.Message);
messageWindow.Show();
io.MarkErrorAsHandled();
}
}
}
public class YourCode
{
public void SomeMethod()
{
serviceWrapper.GetCountOfAccountsByRequestId(selectedRequest.RequestId, GetCountCompleted);
}
public void GetCountCompleted(int countOfAccounts)
{
if (countOfAccounts == 0)
{
// do some thing
}
}
}

WCF host in Windows Form Application

hiii
I am new to WCF and I have written a code in Console application.
I have created a service like this
[ServiceContract]
public interface IHelloService
{
[OperationContract]
void SayHello(string msg);
}
and define the function
public class HelloService: IHelloService
{
public void SayHello(string msg)
{
Console.WriteLine("I rec message : " + msg);
}
}
and I am starting service from main program file
static void Main(string[] args)
{
Console.WriteLine("******* Service Console *******");
using(ServiceHost host = new ServiceHost(typeof(HelloWcfServiceLibrary.HelloService)))
{
host.AddServiceEndpoint(typeof(IHelloService), new NetTcpBinding(), "net.tcp://localhost:9000/HelloWcfService");
host.Open();
Console.Read();
}
}
and at client side the code is
static void Main(string[] args)
{
IHelloService proxy = ChannelFactory<IHelloService>.CreateChannel(new NetTcpBinding(), new EndpointAddress("net.tcp://localhost:9000/HelloWcfService"));
string msg;
while (true)
{
msg = Console.ReadLine();
msg = proxy.SayHello(msg);
Console.WriteLine("Server returned " + msg);
}
}
it is working properly but I want to do the same thing in Windows Form Application and show the received data i richtextbox but I dont know how to do that.
Please Someone help me
It's just the same as what you did in console application. You can start a ServiceHost in Load method, but one difference is that RichTextbox can only be access in GUI thread, so you may have to save the GUI SynchronizationContext somewhere and when you want to output something to that rich textbox, you need to call Post method or send on the SynchronizationContext, like:
public class HelloService: IHelloService {
private SynchronizationContext context;
private RichTextbox textbox;
public void SayHello(string msg)
{
context.Post((obj) => textbox.Add("I rec message : " + msg));
}
}
Note: this just shows a sample, it may not work.

Resources