SignalR 从 WPF 服务器向 WPF 客户端发送消息

Posted

技术标签:

【中文标题】SignalR 从 WPF 服务器向 WPF 客户端发送消息【英文标题】:SignalR sending a message from WPF server to WPF client 【发布时间】:2018-12-06 01:07:45 【问题描述】:

我一直在研究一个在自托管 WPF 服务器应用程序中使用 SignalR 2 与 WPF 客户端通信的示例。我已经下载了这个项目——我在下面展示——它工作得很好:

WPF 服务器:

    public partial class MainWindow : Window

    public IDisposable SignalR  get; set; 
    const string ServerURI = "http://localhost:8080";

    public MainWindow()
    
        InitializeComponent();
    

    /// <summary>
    /// Calls the StartServer method with Task.Run to not
    /// block the UI thread. 
    /// </summary>
    private void ButtonStart_Click(object sender, RoutedEventArgs e)
    
        WriteToConsole("Starting server...");
        ButtonStart.IsEnabled = false;            
        Task.Run(() => StartServer());
    

    /// <summary>
    /// Stops the server and closes the form. Restart functionality omitted
    /// for clarity.
    /// </summary>
    private void ButtonStop_Click(object sender, RoutedEventArgs e)
    
        SignalR.Dispose();
        Close();
    

    /// <summary>
    /// Starts the server and checks for error thrown when another server is already 
    /// running. This method is called asynchronously from Button_Start.
    /// </summary>
    private void StartServer()
    
        try
        
            SignalR = WebApp.Start(ServerURI);
        
        catch (TargetInvocationException)
        
            WriteToConsole("A server is already running at " + ServerURI);
            this.Dispatcher.Invoke(() => ButtonStart.IsEnabled = true);
            return;
        
        this.Dispatcher.Invoke(() => ButtonStop.IsEnabled = true);
        WriteToConsole("Server started at " + ServerURI);
    
    ///This method adds a line to the RichTextBoxConsole control, using Dispatcher.Invoke if used
    /// from a SignalR hub thread rather than the UI thread.
    public void WriteToConsole(String message)
    
        if (!(RichTextBoxConsole.CheckAccess()))
        
            this.Dispatcher.Invoke(() =>
                WriteToConsole(message)
            );
            return;
        
        RichTextBoxConsole.AppendText(message + "\r");
    

/// <summary>
/// Used by OWIN's startup process. 
/// </summary>
class Startup

    public void Configuration(IAppBuilder app)
    
        app.UseCors(CorsOptions.AllowAll);
        app.MapSignalR();
    

/// <summary>
/// Echoes messages sent using the Send message by calling the
/// addMessage method on the client. Also reports to the console
/// when clients connect and disconnect.
/// </summary>
public class MyHub : Hub

    public void Send(string name, string message)
    
        Clients.All.addMessage(name, message);
    
    public override Task OnConnected()
    
        //Use Application.Current.Dispatcher to access UI thread from outside the MainWindow class
        Application.Current.Dispatcher.Invoke(() => 
            ((MainWindow)Application.Current.MainWindow).WriteToConsole("Client connected: " + Context.ConnectionId));

        return base.OnConnected();
    
    public override Task OnDisconnected()
    
        //Use Application.Current.Dispatcher to access UI thread from outside the MainWindow class
        Application.Current.Dispatcher.Invoke(() => 
            ((MainWindow)Application.Current.MainWindow).WriteToConsole("Client disconnected: " + Context.ConnectionId));

        return base.OnDisconnected();
    


这是 WPF 客户端:

    public partial class MainWindow : Window

    /// <summary>
    /// This name is simply added to sent messages to identify the user; this 
    /// sample does not include authentication.
    /// </summary>
    public String UserName  get; set; 
    public IHubProxy HubProxy  get; set; 
    const string ServerURI = "http://localhost:8080/signalr";
    public HubConnection Connection  get; set; 

    public MainWindow()
    
        InitializeComponent();
    

    private void ButtonSend_Click(object sender, RoutedEventArgs e)
    
        HubProxy.Invoke("Send", UserName, TextBoxMessage.Text);
        TextBoxMessage.Text = String.Empty;
        TextBoxMessage.Focus();
    

    /// <summary>
    /// Creates and connects the hub connection and hub proxy. This method
    /// is called asynchronously from SignInButton_Click.
    /// </summary>
    private async void ConnectAsync()
    
        Connection = new HubConnection(ServerURI);
        Connection.Closed += Connection_Closed;
        HubProxy = Connection.CreateHubProxy("MyHub");
        //Handle incoming event from server: use Invoke to write to console from SignalR's thread
        HubProxy.On<string, string>("AddMessage", (name, message) =>
            this.Dispatcher.Invoke(() =>
                RichTextBoxConsole.AppendText(String.Format("0: 1\r", name, message))
            )
        );
        try
        
            await Connection.Start();
        
        catch (HttpRequestException)
        
            StatusText.Content = "Unable to connect to server: Start server before connecting clients.";
            //No connection: Don't enable Send button or show chat UI
            return;
        

        //Show chat UI; hide login UI
        SignInPanel.Visibility = Visibility.Collapsed;
        ChatPanel.Visibility = Visibility.Visible;
        ButtonSend.IsEnabled = true;
        TextBoxMessage.Focus();
        RichTextBoxConsole.AppendText("Connected to server at " + ServerURI + "\r");
    

    /// <summary>
    /// If the server is stopped, the connection will time out after 30 seconds (default), and the 
    /// Closed event will fire.
    /// </summary>
    void Connection_Closed()
    
        //Hide chat UI; show login UI
        var dispatcher = Application.Current.Dispatcher;
        dispatcher.Invoke(() => ChatPanel.Visibility = Visibility.Collapsed);
        dispatcher.Invoke(() => ButtonSend.IsEnabled = false);
        dispatcher.Invoke(() => StatusText.Content = "You have been disconnected.");
        dispatcher.Invoke(() => SignInPanel.Visibility = Visibility.Visible);
    

    private void SignInButton_Click(object sender, RoutedEventArgs e)
    
        UserName = UserNameTextBox.Text;
        //Connect to server (use async method to avoid blocking UI thread)
        if (!String.IsNullOrEmpty(UserName))
             
            StatusText.Visibility = Visibility.Visible;
            StatusText.Content = "Connecting to server...";
            ConnectAsync();
        
    

    private void WPFClient_Closing(object sender, System.ComponentModel.CancelEventArgs e)
    
        if (Connection != null)
        
            Connection.Stop();
            Connection.Dispose();
        
      

让我感到困惑的是服务器 MyHub 类中的“Send”方法,它将文本推送到所有连接的客户端。在该方法中,它似乎在所有客户端上调用了一个名为“addMessage(String, String)”的方法,但该方法并未在客户端代码的任何地方声明。这是怎么回事?

【问题讨论】:

【参考方案1】:

addMessage(String, String) 方法在客户端代码中声明:

HubProxy.On<string, string>("AddMessage", (name, message) =>
        this.Dispatcher.Invoke(() =>
            RichTextBoxConsole.AppendText(String.Format("0: 1\r", name, message))
        )
    );

根据https://docs.microsoft.com/en-us/previous-versions/aspnet/jj917974%28v%3dvs.100%29

该方法是一个动作委托:

https://docs.microsoft.com/en-us/dotnet/api/system.action-2?&view=netframework-4.8

【讨论】:

以上是关于SignalR 从 WPF 服务器向 WPF 客户端发送消息的主要内容,如果未能解决你的问题,请参考以下文章

SignalR 自托管与 WCF 服务和客户端将是桌面用户

signalr服务端

在 .NET 客户端分布式方案中对 SignalR 进行故障排除

客户端未收到 SignalR 消息

将 SignalR 用于桌面应用程序是不是正确?

如何从同一 WPF 应用程序托管的 WCF 服务调用 WPF 应用程序中的方法?