Consider creating Minimal Hubs
- Dominant language
- C#
- Stars
- 38.4k
- Forks
- 10.9k
- Avg merge
- 2d 6h
- Merged PRs (30d)
- 290
Description
Or Hub Events, or Hub APIs. Name to be bike-shedded.
Taking heavy inspiration from Minimal APIs and the comparison it has with MVC Controllers, we can apply the same idea to Hubs (which people compare to Controllers).
The general idea is that we can map a "Hub" and let the user add endpoints to that "Hub". The "Hub" would look similar to a Group in Minimal APIs and the endpoints look similar to `MapGet`, `MapPost`, etc. with a user provided delegate that is interpreted by something like RequestDelegateFactory (HubDelegateFactory?).
```csharp
var myHub = app.MapHubName("/myHub");
myHub.Map("Echo", (string message) => message);
```
Some interesting aspects to note:
* There is no user provided `Hub` type any more
- Should we allow mixing with a `Hub`? `var myHub = app.MapHubName("/myHub");` First instinct is no.
- Technically this would allow libraries to add methods to a Hub
* `IHubContext` would need to be accessed via KeyedDI for background services/other app code
- `[KeyedService("myHub")] IHubContext`
* Properties from the `Hub` will need to be automatically injected into user delegates
- Sadly less discoverable than `Hub` where you can just access `this.Context` etc.
- `IHubCallerClients`, `HubCallerContext`, `IGroupManager`
- `IHubContext` could also be automatically injected without using `KeyedService` if we wanted
* In the example below I show a new hub filter type that is similar to `AddEndpointFilter` where you can add it to a specific API call instead of the entire `Hub`. Seems like an optional feature we can add regardless of "Minimal Hubs"
Example program showing some of the ways you can do things:
```csharp
// Can apply endpoint metadata like usual
var hub = app.MapHubName("/myHub")
.AddMessagePackProtocol()
.AddStackExchangeRedis()
.RequireAuthorization()
.AddFilter();
var logger = app.Services.GetService();
hub.Map("Broadcast", async (string message, IHubCallerClients clients) =>
{
await clients.All.SendAsync("Broadcast", message);
});
hub.Map("EchoV1", (string message) =>
{
// :O captured variable
logger.LogInformation("Echo {Message}", message);
return message;
});
// Auto-inject IHubCallerClients and HubCallerContext
// Less discoverable than having a strongly typed Hub where they are just properties on the class
hub.Map("EchoV2", async (string message, IHubCallerClients clients, HubCallerContext self) =>
{
await clients.Client(self.ConnectionId).SendAsync("Echo", message);
});
hub.Map("Add", (int x, int y, ILogger injectedLogger) =>
{
injectedLogger.LogInformation("Adding {X} and {Y}", x, y);
return x + y;
}).AddFilter(context =>
{
// This is a completely different filter from what we have today. The way its written it's input only and method specific. Just a thought experiment.
context.Arguments[0] = 10;
});
// KeyedService automatically created and named based on MapHubName. We could also automatically deduce this is IHubContext from "myHub" because of the builder type.
// But it shows how someone could reference this IHubContext from other areas of the code (since there isn't a Hub type).
hub.Map("hubContext", async ([KeyedService("myHub")] IHubContext hubContext) =>
{
await hubContext.Clients.All.SendAsync("Echo", "test");
});
app.Run();
```
Contributor guide
Assessment
This issue has not been assessed yet.