Skip to main content

Getting Started with StreamingHub

This tutorial introduces the basic steps to get started with StreamingHub.

Steps

To define, implement, and use StreamingHub, the following steps are required:

  • Define the StreamingHub interface to be shared between the server and the client
  • Implement the StreamingHub interface in the server project
  • Implement the StreamingHub receiver defined in the client project
  • Create a client proxy to call the StreamingHub defined in the client project

Define the StreamingHub interface to be shared between the server and the client

Define the StreamingHub interface in a shared library project.

The StreamingHub interface must inherit IStreamingHub<TSelf, TReceiver>. TSelf is the interface itself, and TReceiver is the receiver interface for sending and receiving messages from the server to the client.

The following is an example of a StreamingHub interface for a chat application. The client has a receiver interface that sends join, leave, and message events.

// A hub must inherit `IStreamingHub<TSelf, TReceiver>`.
public interface IChatHub : IStreamingHub<IChatHub, IChatHubReceiver>
{
ValueTask JoinAsync(string roomName, string userName);
ValueTask LeaveAsync();
ValueTask SendMessageAsync(string message);
}

public interface IChatHubReceiver
{
void OnJoin(string userName);
void OnLeave(string userName);
void OnSendMessage(string userName, string message);
}

The methods provided by StreamingHub are called Hub methods. Hub methods are methods called by the client, and the return type must be ValueTask, ValueTask<T>, Task, Task<T>, or void. Note that this is different from Unary services.

The receiver interface, which is the client's message receiving interface, also has methods. These are called receiver methods. Receiver methods are called when a message is received from the server. The return value of the receiver method must be void. Unless you are using client results, you should specify void as the return value.

Implement StreamingHub in the server project

To call StreamingHub from the client, you need to implement a StreamingHub that can be called from the client on the server. The server implementation must inherit StreamingHubBase<THub, TReceiver> and implement the defined StreamingHub interface.

public class ChatHub : StreamingHubBase<IChatHub, IChatHubReceiver>, IChatHub
{
public async ValueTask JoinAsync(string roomName, string userName)
=> throw new NotImplementedException();

public async ValueTask LeaveAsync()
=> throw new NotImplementedException();

public async ValueTask SendMessageAsync(string message)
=> throw new NotImplementedException();
}

At first, implement the method JoinAsync to join the chat room. This method joins the room with the specified name and user name.

Use the Group.AddAsync method to create a group and store a reference to the group in the StreamingHub for later use. You can get the receiver interface of the clients who have joined the group through the All property of the group, so call the OnJoin method to notify the join.

public class ChatHub : StreamingHubBase<IChatHub, IChatHubReceiver>, IChatHub
{
IGroup<IChatHubReceiver>? room;
string userName = "unknown";

public async ValueTask JoinAsync(string roomName, string userName)
{
this.room = await Group.AddAsync(roomName);
this.userName = userName;
room.All.OnJoin(userName);
}

public async ValueTask LeaveAsync()
=> throw new NotImplementedException();

public async ValueTask SendMessageAsync(string message)
=> throw new NotImplementedException();
}

Client property can be used to call only the clients connected to that SteramingHub. Here, let's send a welcome message only to the clients who have connected.

public class ChatHub : StreamingHubBase<IChatHub, IChatHubReceiver>, IChatHub
{
IGroup<IChatHubReceiver>? room;
string userName = "unknown";

public async ValueTask JoinAsync(string roomName, string userName)
{
this.room = await Group.AddAsync(roomName);
this.userName = userName;
room.All.OnJoin(userName);

Client.OnMessage("System", $"Welcome, hello {userName}!");
}

public async ValueTask LeaveAsync()
=> throw new NotImplementedException();

public async ValueTask SendMessageAsync(string message)
=> throw new NotImplementedException();
}

Next, implement the method LeaveAsync to exit. When a client exits, the client is removed from the group. This is done using the Group.RemoveAsync method. Pass the StreamingHubContext object (Context property) to the RemoveAsync method. When a client is removed from the group, messages through the group will no longer reach that client.

public class ChatHub : StreamingHubBase<IChatHub, IChatHubReceiver>, IChatHub
{
IGroup<IChatHubReceiver>? room;
string userName = "unknown";

public async ValueTask JoinAsync(string roomName, string userName)
{
this.room = await Group.AddAsync(roomName);
this.userName = userName;
room.All.OnJoin(userName);
}

public async ValueTask LeaveAsync()
{
room.All.OnLeave(Context.ConnectionId);
await room.RemoveAsync(Context);
}

public async ValueTask SendMessageAsync(string message)
=> throw new NotImplementedException();
}

Finally, implement the SendMessageAsync method to deliver messages to the group when the server receives a message from the client. In this method, notify the group by calling the OnMessage method of the clients who have joined the group through the All property of the group.

public class ChatHub : StreamingHubBase<IChatHub, IChatHubReceiver>, IChatHub
{
IGroup<IChatHubReceiver>? room;
string userName = "unknown";

public async ValueTask JoinAsync(string roomName, string userName)
{
this.room = await Group.AddAsync(roomName);
this.userName = userName;
room.All.OnJoin(userName);
}

public async ValueTask LeaveAsync()
{
room.All.OnLeave(Context.ConnectionId);
await room.RemoveAsync(Context);
}

public async ValueTask SendMessageAsync(string message)
{
room.All.OnMessage(userName, message);
}
}

Implement the StreamingHub receiver in the client project

In the client project, implement the receiver interface of StreamingHub. This interface is implemented in the client project and processes the messages received on the client side.

Create a simple ChatHubReceiver type and implement the receiver interface IChatHubReceiver. Each method receives a message sent from the server and outputs the message to the console.

class ChatHubReceiver : IChatHubReceiver
{
public void OnJoin(string userName)
=> Console.WriteLine($"{userName} joined.");
public void OnLeave(string userName)
=> Console.WriteLine($"{userName} left.");
public void OnMessage(string userName, string message)
=> Console.WriteLine($"{userName}: {message}");
}

Create a client proxy to call the StreamingHub defined in the client project

To connect from the client to the StreamingHub, use the StreamingHubClient.ConnectAsync method. This method establishes a connection and returns a client proxy.

ConnectAsync method takes a GrpcChannel object for the connection destination and an instance of the receiver interface. Messages received from the server will be method calls on the instance of the receiver passed here.

var channel = GrpcChannel.ForAddress("https://localhost:5001");
var receiver = new ChatHubReceiver();
var client = await StreamingHubClient.ConnectAsync<IChatHub, IChatHubReceiver>(channel, receiver);

await client.JoinAsync("room", "user1");
await client.SendMessageAsync("Hello, world!");