Skip to content

Commit d777c1c

Browse files
committed
first commit
0 parents  commit d777c1c

20 files changed

+605
-0
lines changed

Diff for: README.md

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
websocket-controller
2+
====================
3+
4+
Easy WebSockets for AspNetCore 3.0.
5+
6+
7+
Implement your websocket controller:
8+
````c#
9+
public class MyController : AWebsocketController
10+
{
11+
public override async Task OnOpen(Client client)
12+
{
13+
await client.SendAsync("Hello there");
14+
}
15+
// OnTextMessage(Client client, string text)
16+
// OnBinaryMessage(...)
17+
// OnClose(...)
18+
}
19+
````
20+
21+
And add it in your `Startup.cs`
22+
````c#
23+
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
24+
{
25+
app.UseWebSockets();
26+
var myHandler = WebSocketHandler.CreateFor<MyController>(app.ApplicationServices);
27+
app.Run(myHandler.HandleRequest);
28+
}
29+
````
30+
31+
See example for a basic websocket chat. To run, `cd example` and `dotnet run`, then open http://localhost:5000/

Diff for: aspnetcore-websocket-controller.sln

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
2+
Microsoft Visual Studio Solution File, Format Version 12.00
3+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "src", "src\src.csproj", "{255700B0-36C9-4C82-A73B-35D18F7B8D31}"
4+
EndProject
5+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "example", "example\example.csproj", "{35212A5A-CD5A-4FA3-B40B-150BED908A97}"
6+
EndProject
7+
Global
8+
GlobalSection(SolutionConfigurationPlatforms) = preSolution
9+
Debug|Any CPU = Debug|Any CPU
10+
Release|Any CPU = Release|Any CPU
11+
EndGlobalSection
12+
GlobalSection(ProjectConfigurationPlatforms) = postSolution
13+
{255700B0-36C9-4C82-A73B-35D18F7B8D31}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
14+
{255700B0-36C9-4C82-A73B-35D18F7B8D31}.Debug|Any CPU.Build.0 = Debug|Any CPU
15+
{255700B0-36C9-4C82-A73B-35D18F7B8D31}.Release|Any CPU.ActiveCfg = Release|Any CPU
16+
{255700B0-36C9-4C82-A73B-35D18F7B8D31}.Release|Any CPU.Build.0 = Release|Any CPU
17+
{35212A5A-CD5A-4FA3-B40B-150BED908A97}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
18+
{35212A5A-CD5A-4FA3-B40B-150BED908A97}.Debug|Any CPU.Build.0 = Debug|Any CPU
19+
{35212A5A-CD5A-4FA3-B40B-150BED908A97}.Release|Any CPU.ActiveCfg = Release|Any CPU
20+
{35212A5A-CD5A-4FA3-B40B-150BED908A97}.Release|Any CPU.Build.0 = Release|Any CPU
21+
EndGlobalSection
22+
EndGlobal

Diff for: example/.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
/bin
2+
/obj

Diff for: example/ChatController.cs

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
using System.Linq;
2+
using System.Net.WebSockets;
3+
using System.Threading.Tasks;
4+
using TimoStamm.WebSockets.Controller;
5+
6+
namespace example
7+
{
8+
public class ChatController : AWebsocketController
9+
{
10+
private readonly ClientCollection _clients;
11+
private static int _clientCounter;
12+
13+
public ChatController(ClientCollection clients)
14+
{
15+
_clients = clients;
16+
}
17+
18+
19+
public override async Task OnOpen(Client client)
20+
{
21+
var nick = client.Context.Items["nick"] = ++_clientCounter;
22+
var msg = $"{nick} has entered the chat.";
23+
if (_clients.Count == 1)
24+
{
25+
msg += " You are alone. Open another browser window...";
26+
}
27+
await Task.WhenAll(_clients.Select(c => c.SendAsync(msg)));
28+
}
29+
30+
31+
public override async Task OnClose(Client client, WebSocketCloseStatus closeStatus,
32+
string closeStatusDescription)
33+
{
34+
var nick = client.Context.Items["nick"];
35+
var msg = $"{nick} has left the chat.";
36+
await Task.WhenAll(_clients.Select(c => c.SendAsync(msg)));
37+
}
38+
39+
40+
public override async Task OnTextMessage(Client client, string text)
41+
{
42+
var nick = client.Context.Items["nick"];
43+
var msg = $"{nick}: {text}";
44+
await Task.WhenAll(_clients.Select(c => c.SendAsync(msg)));
45+
}
46+
47+
}
48+
}

Diff for: example/MyController.cs

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
using System.Threading.Tasks;
2+
using TimoStamm.WebSockets.Controller;
3+
4+
namespace example
5+
{
6+
public class MyController : AWebsocketController
7+
{
8+
9+
public override async Task OnOpen(Client client)
10+
{
11+
await client.SendAsync("Hello there");
12+
}
13+
14+
}
15+
}

Diff for: example/Program.cs

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Threading.Tasks;
5+
using Microsoft.AspNetCore.Hosting;
6+
using Microsoft.Extensions.Configuration;
7+
using Microsoft.Extensions.Hosting;
8+
using Microsoft.Extensions.Logging;
9+
10+
namespace example
11+
{
12+
public class Program
13+
{
14+
public static void Main(string[] args)
15+
{
16+
CreateHostBuilder(args).Build().Run();
17+
}
18+
19+
public static IHostBuilder CreateHostBuilder(string[] args) =>
20+
Host.CreateDefaultBuilder(args)
21+
.ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup<Startup>(); });
22+
}
23+
}

Diff for: example/Properties/launchSettings.json

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
{
2+
"iisSettings": {
3+
"windowsAuthentication": false,
4+
"anonymousAuthentication": true,
5+
"iisExpress": {
6+
"applicationUrl": "http://localhost:13053",
7+
"sslPort": 44318
8+
}
9+
},
10+
"profiles": {
11+
"IIS Express": {
12+
"commandName": "IISExpress",
13+
"launchBrowser": true,
14+
"environmentVariables": {
15+
"ASPNETCORE_ENVIRONMENT": "Development"
16+
}
17+
},
18+
"example": {
19+
"commandName": "Project",
20+
"launchBrowser": true,
21+
"applicationUrl": "http://localhost:5000",
22+
"environmentVariables": {
23+
"ASPNETCORE_ENVIRONMENT": "Development"
24+
}
25+
}
26+
}
27+
}

Diff for: example/Startup.cs

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
using Microsoft.AspNetCore.Builder;
2+
using Microsoft.AspNetCore.Hosting;
3+
using Microsoft.Extensions.DependencyInjection;
4+
using TimoStamm.WebSockets.Controller;
5+
6+
namespace example
7+
{
8+
public class Startup
9+
{
10+
// This method gets called by the runtime. Use this method to add services to the container.
11+
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
12+
public void ConfigureServices(IServiceCollection services)
13+
{
14+
}
15+
16+
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
17+
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
18+
{
19+
app.UseDefaultFiles();
20+
app.UseStaticFiles();
21+
22+
app.UseWebSockets();
23+
24+
app.Map("/ws-chat", a =>
25+
{
26+
var handler = WebSocketHandler.CreateFor<ChatController>(app.ApplicationServices);
27+
a.Run(handler.HandleRequest);
28+
});
29+
}
30+
}
31+
}

Diff for: example/appsettings.Development.json

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"Logging": {
3+
"LogLevel": {
4+
"Default": "Debug",
5+
"System": "Information",
6+
"Microsoft": "Information"
7+
}
8+
}
9+
}

Diff for: example/appsettings.json

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"Logging": {
3+
"LogLevel": {
4+
"Default": "Information",
5+
"Microsoft": "Warning",
6+
"Microsoft.Hosting.Lifetime": "Information"
7+
}
8+
},
9+
"AllowedHosts": "*"
10+
}

Diff for: example/example.csproj

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<Project Sdk="Microsoft.NET.Sdk.Web">
2+
3+
<PropertyGroup>
4+
<TargetFramework>netcoreapp3.0</TargetFramework>
5+
</PropertyGroup>
6+
7+
<ItemGroup>
8+
<ProjectReference Include="..\src\src.csproj" />
9+
</ItemGroup>
10+
11+
</Project>

Diff for: example/wwwroot/index.html

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<title>Chat</title>
6+
<style type="text/css">
7+
input[name=text] {
8+
font-size: 1.2rem;
9+
width: 90%;
10+
margin-bottom: 20px;
11+
}
12+
13+
div#output {
14+
white-space: pre-wrap;
15+
}
16+
</style>
17+
</head>
18+
<body>
19+
20+
<form onsubmit="onSubmit(event)">
21+
<input type="text" name="text" placeholder="enter message and hit enter" autocomplete="off">
22+
</form>
23+
24+
<div id="output" style="">
25+
</div>
26+
27+
28+
<script type="application/javascript">
29+
30+
var webSocket = new WebSocket("ws://localhost:5000/ws-chat");
31+
32+
webSocket.onmessage = function (event) {
33+
var output = document.getElementById('output');
34+
output.textContent = event.data + "\n" + output.textContent;
35+
};
36+
37+
function onSubmit(event) {
38+
event.preventDefault();
39+
var input = event.target.elements.namedItem('text');
40+
webSocket.send(input.value);
41+
input.value = "";
42+
}
43+
44+
</script>
45+
46+
</body>
47+
</html>

Diff for: src/.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
/bin
2+
/obj

Diff for: src/AWebsocketController.cs

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
using System.Net.WebSockets;
2+
using System.Threading.Tasks;
3+
4+
namespace TimoStamm.WebSockets.Controller
5+
{
6+
public abstract class AWebsocketController : IWebsocketController
7+
{
8+
public virtual Task OnOpen(Client client)
9+
{
10+
return Task.CompletedTask;
11+
}
12+
13+
public virtual async Task OnTextMessage(Client client, string text)
14+
{
15+
await client.CloseAsync(WebSocketCloseStatus.InvalidMessageType, "Cannot process text message.");
16+
}
17+
18+
public virtual async Task OnBinaryMessage(Client client, byte[] bytes)
19+
{
20+
await client.CloseAsync(WebSocketCloseStatus.InvalidMessageType, "Cannot process binary message.");
21+
}
22+
23+
public virtual Task OnClose(Client client, WebSocketCloseStatus closeStatus, string closeStatusDescription)
24+
{
25+
return Task.CompletedTask;
26+
}
27+
}
28+
}

Diff for: src/Client.cs

+64
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
using System;
2+
using System.Net.WebSockets;
3+
using System.Security.Claims;
4+
using System.Text;
5+
using System.Threading;
6+
using System.Threading.Tasks;
7+
using Microsoft.AspNetCore.Http;
8+
9+
namespace TimoStamm.WebSockets.Controller
10+
{
11+
public class Client
12+
{
13+
14+
private readonly WebSocket _webSocket;
15+
16+
private readonly HttpContext _context;
17+
18+
19+
public Client(WebSocket webSocket, HttpContext context)
20+
{
21+
_webSocket = webSocket;
22+
_context = context;
23+
24+
}
25+
26+
27+
public ClaimsPrincipal User => _context.User;
28+
29+
public WebSocket WebSocket => _webSocket;
30+
31+
public HttpContext Context => _context;
32+
33+
public string Id => _context.TraceIdentifier;
34+
35+
36+
public Task CloseAsync(
37+
WebSocketCloseStatus closeStatus = WebSocketCloseStatus.NormalClosure,
38+
string statusDescription = "Closing",
39+
CancellationToken cancellationToken = default)
40+
{
41+
return _webSocket.CloseAsync(closeStatus, statusDescription, cancellationToken);
42+
}
43+
44+
45+
public Task SendAsync(byte[] bytes, CancellationToken cancellationToken = default)
46+
{
47+
return _webSocket.SendAsync(new ArraySegment<byte>(bytes), WebSocketMessageType.Text, true, cancellationToken);
48+
}
49+
50+
51+
public Task SendAsync(string text, CancellationToken cancellationToken = default)
52+
{
53+
var bytes = Encoding.UTF8.GetBytes(text);
54+
return _webSocket.SendAsync(new ArraySegment<byte>(bytes), WebSocketMessageType.Text, true, cancellationToken);
55+
}
56+
57+
58+
public override string ToString()
59+
{
60+
return $"{base.ToString()}[{Id}]";
61+
}
62+
63+
}
64+
}

0 commit comments

Comments
 (0)