https://github.com/vadrsa/simpler
High Performance Pure WebSocket Server Library based on SignalR
https://github.com/vadrsa/simpler
csharp custom dotnet signalr websocket
Last synced: 5 months ago
JSON representation
High Performance Pure WebSocket Server Library based on SignalR
- Host: GitHub
- URL: https://github.com/vadrsa/simpler
- Owner: vadrsa
- License: mit
- Created: 2022-06-24T11:32:38.000Z (almost 4 years ago)
- Default Branch: master
- Last Pushed: 2025-05-12T10:00:38.000Z (about 1 year ago)
- Last Synced: 2025-11-10T00:43:59.896Z (7 months ago)
- Topics: csharp, custom, dotnet, signalr, websocket
- Language: C#
- Homepage:
- Size: 111 KB
- Stars: 32
- Watchers: 4
- Forks: 7
- Open Issues: 3
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README

# SimpleR [](https://www.nuget.org/packages/SimpleR.Server)
SimpleR is a streamlined version of [SignalR](https://github.com/dotnet/aspnetcore/blob/main/src/SignalR/README.md), a high-performance, opinionated, real-time web framework. By removing all the custom protocols from SignalR, we are left with a simpler library, hence the name SimpleR.
# When should I use SimpleR?
In short, If you can use SignalR, you should. If not, go with SimpleR.
SimpleR was created to address the need for an easy-to-use, high-performance WebSocket server on .NET, particularly in scenarios where the client cannot use SignalR. For instance, when the client is an IoT device operating with a specific protocol standard over which you have no control ([OCPP](https://openchargealliance.org) for example), SignalR may not be an option. In such cases, you're often left with very low-level programming APIs. SimpleR aims to solve this problem by providing simpler and more familiar APIs to expedite your high-performance WebSocket server development.
# Standard Protocols
- OCPP [](https://www.nuget.org/packages/SimpleR.Ocpp)
# Examples
Examples can be found [here](https://github.com/vadrsa/SimpleR/tree/master/examples)
# Getting Started
SimpleR can be installed using the Nuget package manager or the `dotnet` CLI.
```
dotnet add package SimpleR.Server --prerelease
```
## Configure SimpleR
Here is a simple configuration example.
```cs
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSimpleR();
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.MapSimpleR("thermostat/{deviceId}", b =>
{
b.UseDispatcher()
.UseEndOfMessageDelimitedProtocol(new ThermostatMessageProtocol());
})
.RequireAuthorization();
app.Run();
```
The preceding code adds SimpleR to the ASP.NET Core dependency injections, routing systems and defines the message protocol and the message dispatcher.
## Create a Message Dispatcher
A message dispatcher is a high-level pipeline that encapsulates the logic of where to dispatch connection messages.
```cs
public class ThermostatMessageDispatcher : IWebSocketMessageDispatcher
{
///
/// Called when a connection is established.
///
/// The connection.
public Task OnConnectedAsync(IWebsocketConnectionContext connection)
{
return Task.CompletedTask;
}
///
/// Called when a connection is disconnected.
///
/// The connection.
/// The exception that occurred, if any.
public Task OnDisconnectedAsync(IWebsocketConnectionContext connection, Exception? exception)
{
return Task.CompletedTask;
}
///
/// Dispatches a message to the application.
///
/// The connection.
/// The message to dispatch.
public async Task DispatchMessageAsync(IWebsocketConnectionContext connection, ThermostatMetric message)
{
var deviceId = connection.User.FindFirstValue(ClaimTypes.Name) ?? throw new InvalidOperationException("Current user is not a device");
var settings = GetSettings(deviceId);
if(message is ThermostatTemperatureMetric temperatureMetric)
{
if (temperatureMetric.Temperature < settings.TargetTemperature)
{
// If the temperature is below the target temperature, set the thermostat to heat mode
await connection.WriteAsync(new SetThermostatModeCommand(ThermostatMode.Heat));
}
else if (temperatureMetric.Temperature > settings.TargetTemperature)
{
// If the temperature is above the target temperature, set the thermostat to cool mode
await connection.WriteAsync(new SetThermostatModeCommand(ThermostatMode.Cool));
}
else
{
// If the temperature is at the target temperature, turn off the thermostat
await connection.WriteAsync(new SetThermostatModeCommand(ThermostatMode.Off));
}
}
}
}
```
Each SimpleR route has one message dispatcher instance.
## Defining Message Protocols
Since SimpleR is protocol-agnostic, it requires the user to provide a protocol definition to be able to construct a message from the stream of bytes each connection receives.
There are two categories of a message protocol:
- Messages are delimited by the [EndOfMessage flag of WebsocketReceiveResult](https://learn.microsoft.com/en-Us/dotnet/api/system.net.websockets.websocketreceiveresult)
- Messages have custom delimiters
### EndOfMessage Delimited Protocol
Here is a simple delimited protocol implementation:
```cs
public class ThermostatMessageProtocol: IDelimitedMessageProtocol
{
public ThermostatMetric ParseMessage(ref ReadOnlySequence input)
{
var jsonReader = new Utf8JsonReader(input);
return JsonSerializer.Deserialize(ref jsonReader)!;
}
public void WriteMessage(ThermostatCommand message, IBufferWriter output)
{
var jsonWriter = new Utf8JsonWriter(output);
JsonSerializer.Serialize(jsonWriter, message);
}
}
```
To use the delimited protocol call the `UseEndOfMessageDelimitedProtocol` method of the builder.
```cs
app.MapSimpleR("thermostat/{deviceId}", b =>
{
b.UseDispatcher()
.UseEndOfMessageDelimitedProtocol(new ThermostatMessageProtocol());
})
```
### Custom Protocol
Here is a simple custom protocol implementation:
```cs
public class ChatMessageProtocol : IMessageProtocol
{
public void WriteMessage(ChatMessage message, IBufferWriter output)
{
var span = output.GetSpan(Encoding.UTF8.GetByteCount(message.Content));
var bytesWritten = Encoding.UTF8.GetBytes(message.Content, span);
output.Advance(bytesWritten);
}
public bool TryParseMessage(ref ReadOnlySequence input, out ChatMessage message)
{
var reader = new SequenceReader(input);
if (reader.TryReadTo(out ReadOnlySequence payload, delimiter: 0, advancePastDelimiter: true))
{
message = new ChatMessage { Content = Encoding.UTF8.GetString(payload) };
input = reader.UnreadSequence;
return true;
}
message = default;
return false;
}
}
```
To use the delimited protocol call the `UseCustomProtocol` method of the builder.
```cs
app.MapSimpleR("/chat",
b =>
{
b.UseCustomProtocol(new ChatMessageProtocol())
.UseDispatcher();
}
);
```
### How to work with low level network buffers
To learn more about working with `ReadOnlySequence` and `IBufferWriter` check out [this article](https://learn.microsoft.com/en-us/dotnet/standard/io/buffers).