├── .gitignore ├── 1.Websockets ├── CustomServer │ ├── CustomServer.csproj │ └── Program.cs ├── Websockets.sln └── Websockets │ ├── Program.cs │ ├── Properties │ └── launchSettings.json │ ├── Startup.cs │ ├── Websockets.csproj │ ├── appsettings.Development.json │ ├── appsettings.json │ └── wwwroot │ └── index.html ├── 2.SSE_and_LP ├── LongPolling │ ├── LongPolling.csproj │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ ├── Startup.cs │ ├── appsettings.Development.json │ ├── appsettings.json │ └── wwwroot │ │ └── index.html ├── ServerSentEvents.sln └── ServerSentEvents │ ├── Program.cs │ ├── Properties │ └── launchSettings.json │ ├── ServerSentEvents.csproj │ ├── Startup.cs │ ├── appsettings.Development.json │ ├── appsettings.json │ └── wwwroot │ └── index.html ├── 3.FeatureOverview ├── FeatureOverview.sln └── FeatureOverview │ ├── CustomHub.cs │ ├── Data.cs │ ├── FeatureOverview.csproj │ ├── GroupsHub.cs │ ├── Program.cs │ ├── Properties │ └── launchSettings.json │ ├── Startup.cs │ ├── TestController.cs │ ├── appsettings.Development.json │ ├── appsettings.json │ └── wwwroot │ ├── groups.html │ ├── index.html │ ├── signalr.js │ ├── signalr.js.map │ ├── signalr.min.js │ └── signalr.min.js.map ├── 4.StreamingData ├── StreamingData.sln ├── StreamingData_7 │ ├── Data.cs │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ ├── Startup.cs │ ├── StreamingData_7.csproj │ ├── StreamingHub.cs │ ├── appsettings.Development.json │ ├── appsettings.json │ └── wwwroot │ │ ├── index.html │ │ ├── signalr.js │ │ ├── signalr.js.map │ │ ├── signalr.min.js │ │ └── signalr.min.js.map ├── StreamingData_8 │ ├── Data.cs │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ ├── Startup.cs │ ├── StreamingData_8.csproj │ ├── StreamingHub.cs │ ├── appsettings.Development.json │ ├── appsettings.json │ └── wwwroot │ │ ├── index.html │ │ ├── signalr.js │ │ ├── signalr.js.map │ │ ├── signalr.min.js │ │ └── signalr.min.js.map └── global.json ├── 5.Authentication ├── Authentication.sln ├── Authentication │ ├── Authentication.csproj │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ ├── ProtectedHub.cs │ ├── Startup.cs │ ├── appsettings.Development.json │ ├── appsettings.json │ └── wwwroot │ │ ├── index.html │ │ ├── signalr.js │ │ ├── signalr.js.map │ │ ├── signalr.min.js │ │ └── signalr.min.js.map └── AuthenticationExpiry │ ├── AuthHubFilter.cs │ ├── AuthenticationExpiry.csproj │ ├── Program.cs │ ├── Properties │ └── launchSettings.json │ ├── ProtectedHub.cs │ ├── Startup.cs │ └── wwwroot │ ├── index.html │ ├── signalr.js │ ├── signalr.js.map │ ├── signalr.min.js │ └── signalr.min.js.map ├── 6.Recepies ├── ChatApp │ ├── ChatApp.csproj │ ├── ChatHub.cs │ ├── ChatRegistry.cs │ ├── DefaultController.cs │ ├── Models.cs │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ ├── Startup.cs │ ├── UserIdProvider.cs │ ├── appsettings.Development.json │ ├── appsettings.json │ └── wwwroot │ │ ├── index.html │ │ ├── signalr.js │ │ ├── signalr.js.map │ │ ├── signalr.min.js │ │ └── signalr.min.js.map ├── Collaboration │ ├── AuthenticationHandler.cs │ ├── Collaboration.csproj │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ ├── SquareHub.cs │ ├── Startup.cs │ ├── State.cs │ ├── UserIdProvider.cs │ ├── appsettings.Development.json │ ├── appsettings.json │ └── wwwroot │ │ ├── bad_v_good.png │ │ ├── index.html │ │ ├── signalr.js │ │ ├── signalr.js.map │ │ ├── signalr.min.js │ │ └── signalr.min.js.map ├── DrawingGame │ ├── AuthenticationHandler.cs │ ├── DrawingGame.csproj │ ├── GameHub.cs │ ├── HttpContextExtensions.cs │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ ├── RoomController.cs │ ├── RoomManager.cs │ ├── RoomView.cs │ ├── Startup.cs │ ├── UserIdProvider.cs │ ├── appsettings.Development.json │ ├── appsettings.json │ └── vue-client │ │ ├── .gitignore │ │ ├── .vscode │ │ └── extensions.json │ │ ├── README.md │ │ ├── gameConnection.js │ │ ├── index.html │ │ ├── package-lock.json │ │ ├── package.json │ │ ├── public │ │ └── favicon.ico │ │ ├── src │ │ ├── App.vue │ │ ├── Canvas.vue │ │ ├── Rooms.vue │ │ ├── ToolBar.vue │ │ └── main.js │ │ └── vite.config.js ├── Notifications │ ├── AuthenticationHandler.cs │ ├── DefaultController.cs │ ├── NotificationHub.cs │ ├── NotificationService.cs │ ├── Notifications.csproj │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ ├── Startup.cs │ ├── UserIdProvider.cs │ ├── appsettings.Development.json │ ├── appsettings.json │ ├── redis_pub_sub.linq │ └── wwwroot │ │ ├── index.html │ │ ├── signalr.js │ │ ├── signalr.js.map │ │ ├── signalr.min.js │ │ └── signalr.min.js.map ├── Recepies.sln └── VideoStreaming │ ├── Program.cs │ ├── Properties │ └── launchSettings.json │ ├── Startup.cs │ ├── StreamHub.cs │ ├── VideoStreaming.csproj │ ├── appsettings.Development.json │ ├── appsettings.json │ └── wwwroot │ ├── fetch.html │ ├── file.html │ ├── live.html │ ├── signalr.js │ ├── signalr.js.map │ ├── signalr.min.js │ ├── signalr.min.js.map │ ├── source.html │ ├── vid0.webm │ ├── vid1.webm │ └── vid2.webm ├── 7.CustomClient ├── CustomClient.sln └── CustomClient │ ├── CustomClient.csproj │ ├── Program.cs │ ├── Properties │ └── launchSettings.json │ ├── Startup.cs │ ├── appsettings.Development.json │ ├── appsettings.json │ └── python_client │ ├── README.md │ ├── main.py │ └── requirements.txt └── global.json /.gitignore: -------------------------------------------------------------------------------- 1 | bin 2 | obj 3 | .vs 4 | .idea -------------------------------------------------------------------------------- /1.Websockets/CustomServer/CustomServer.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net5.0 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /1.Websockets/CustomServer/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Net; 4 | using System.Net.Sockets; 5 | using System.Text; 6 | using System.Text.RegularExpressions; 7 | using System.Threading.Tasks; 8 | 9 | namespace CustomServer 10 | { 11 | class Program 12 | { 13 | // https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API/Writing_WebSocket_servers 14 | static async Task Main(string[] args) 15 | { 16 | var ip = new IPEndPoint(IPAddress.Loopback, 5000); 17 | var listenSocket = new Socket(SocketType.Stream, ProtocolType.Tcp); 18 | listenSocket.Bind(ip); 19 | listenSocket.Listen(); 20 | const string EOL = "\r\n"; 21 | 22 | while (true) 23 | { 24 | var socket = await listenSocket.AcceptAsync(); 25 | var stream = new NetworkStream(socket); 26 | var buffer = new byte[1024]; 27 | 28 | while (true) 29 | { 30 | var bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length); 31 | 32 | var data = Encoding.UTF8.GetString(buffer[..bytesRead]); 33 | if (data.StartsWith("GET")) 34 | { 35 | // handshake https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API/Writing_WebSocket_servers#the_websocket_handshake 36 | Console.WriteLine(data); 37 | var key = new Regex("Sec-WebSocket-Key: (.*)").Match(data).Groups[1].Value; 38 | var acceptWebsocket = Convert.ToBase64String(System.Security.Cryptography.SHA1.Create().ComputeHash(Encoding.UTF8.GetBytes(key.Trim() + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"))); 39 | 40 | var responseMessage = "HTTP/1.1 101 Switching Protocols" 41 | + EOL + "Connection: Upgrade" 42 | + EOL + "Upgrade: websocket" 43 | + EOL + "Sec-WebSocket-Accept: " + acceptWebsocket 44 | + EOL + EOL; 45 | 46 | var response = Encoding.UTF8.GetBytes(responseMessage); 47 | await stream.WriteAsync(response, 0, response.Length); 48 | stream.Flush(); 49 | } 50 | else 51 | { 52 | // https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API/Writing_WebSocket_servers#format 53 | // Only for small messages, < 126 bytes 54 | 55 | // var length = buffer[1] - 0b10000000; 56 | var length = buffer[1] - 0x80; 57 | var key = buffer[2..6]; 58 | var wsData = new byte[length]; 59 | 60 | for (int i = 0; i < length; i++) 61 | { 62 | var offset = 6 + i; 63 | wsData[i] = (byte)(buffer[offset] ^ key[i % 4]); 64 | } 65 | 66 | var text = Encoding.UTF8.GetString(wsData); 67 | Console.WriteLine("{0}", text); 68 | } 69 | } 70 | } 71 | } 72 | } 73 | } -------------------------------------------------------------------------------- /1.Websockets/Websockets.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Websockets", "Websockets\Websockets.csproj", "{7FF9F4A0-B2E8-44E8-8DCC-36DABF0CF8B4}" 4 | EndProject 5 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CustomServer", "CustomServer\CustomServer.csproj", "{724F1D50-9EF8-4C4F-BB11-7AA2302D0FA2}" 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 | {7FF9F4A0-B2E8-44E8-8DCC-36DABF0CF8B4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 14 | {7FF9F4A0-B2E8-44E8-8DCC-36DABF0CF8B4}.Debug|Any CPU.Build.0 = Debug|Any CPU 15 | {7FF9F4A0-B2E8-44E8-8DCC-36DABF0CF8B4}.Release|Any CPU.ActiveCfg = Release|Any CPU 16 | {7FF9F4A0-B2E8-44E8-8DCC-36DABF0CF8B4}.Release|Any CPU.Build.0 = Release|Any CPU 17 | {724F1D50-9EF8-4C4F-BB11-7AA2302D0FA2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 18 | {724F1D50-9EF8-4C4F-BB11-7AA2302D0FA2}.Debug|Any CPU.Build.0 = Debug|Any CPU 19 | {724F1D50-9EF8-4C4F-BB11-7AA2302D0FA2}.Release|Any CPU.ActiveCfg = Release|Any CPU 20 | {724F1D50-9EF8-4C4F-BB11-7AA2302D0FA2}.Release|Any CPU.Build.0 = Release|Any CPU 21 | EndGlobalSection 22 | EndGlobal 23 | -------------------------------------------------------------------------------- /1.Websockets/Websockets/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Hosting; 2 | using Microsoft.Extensions.Hosting; 3 | 4 | namespace Websockets 5 | { 6 | public class Program 7 | { 8 | public static void Main(string[] args) 9 | { 10 | CreateHostBuilder(args).Build().Run(); 11 | } 12 | 13 | public static IHostBuilder CreateHostBuilder(string[] args) => 14 | Host.CreateDefaultBuilder(args) 15 | .ConfigureWebHostDefaults(webBuilder => 16 | { 17 | webBuilder.UseStartup(); 18 | }); 19 | } 20 | } -------------------------------------------------------------------------------- /1.Websockets/Websockets/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/launchsettings.json", 3 | "iisSettings": { 4 | "windowsAuthentication": false, 5 | "anonymousAuthentication": true, 6 | "iisExpress": { 7 | "applicationUrl": "http://localhost:42077", 8 | "sslPort": 44363 9 | } 10 | }, 11 | "profiles": { 12 | "IIS Express": { 13 | "commandName": "IISExpress", 14 | "launchBrowser": true, 15 | "launchUrl": "swagger", 16 | "environmentVariables": { 17 | "ASPNETCORE_ENVIRONMENT": "Development" 18 | } 19 | }, 20 | "SignalRWebsockets": { 21 | "commandName": "Project", 22 | "dotnetRunMessages": "true", 23 | "launchBrowser": true, 24 | "launchUrl": "swagger", 25 | "applicationUrl": "https://localhost:5001;http://localhost:5000", 26 | "environmentVariables": { 27 | "ASPNETCORE_ENVIRONMENT": "Development" 28 | } 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /1.Websockets/Websockets/Startup.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Net.WebSockets; 4 | using System.Text; 5 | using System.Threading; 6 | using Microsoft.AspNetCore.Builder; 7 | using Microsoft.AspNetCore.Hosting; 8 | using Microsoft.Extensions.DependencyInjection; 9 | using Microsoft.Extensions.Hosting; 10 | 11 | namespace Websockets 12 | { 13 | public class Startup 14 | { 15 | private readonly List _connections = new(); 16 | 17 | public void ConfigureServices(IServiceCollection services) 18 | { 19 | 20 | } 21 | 22 | public void Configure(IApplicationBuilder app, IWebHostEnvironment env) 23 | { 24 | if (env.IsDevelopment()) 25 | { 26 | app.UseDeveloperExceptionPage(); 27 | } 28 | 29 | app.UseStaticFiles(); 30 | 31 | app.UseRouting(); 32 | 33 | app.UseWebSockets(new() {KeepAliveInterval = TimeSpan.FromSeconds(30)}); 34 | 35 | app.UseEndpoints(endpoints => 36 | { 37 | endpoints.Map("/ws", async ctx => 38 | { 39 | var buffer = new byte[1024 * 4]; 40 | var webSocket = await ctx.WebSockets.AcceptWebSocketAsync(); 41 | _connections.Add(webSocket); 42 | 43 | var result = await webSocket.ReceiveAsync(new(buffer), CancellationToken.None); 44 | int i = 0; 45 | while (!result.CloseStatus.HasValue) 46 | { 47 | var message = Encoding.UTF8.GetBytes($"message index {i++}"); 48 | foreach (var c in _connections) 49 | { 50 | await c.SendAsync(new(message, 0, message.Length), result.MessageType, result.EndOfMessage, CancellationToken.None); 51 | } 52 | 53 | result = await webSocket.ReceiveAsync(new(buffer), CancellationToken.None); 54 | 55 | Console.WriteLine($"Received: {Encoding.UTF8.GetString(buffer[..result.Count])}"); 56 | } 57 | 58 | await webSocket.CloseAsync(result.CloseStatus.Value, result.CloseStatusDescription, CancellationToken.None); 59 | _connections.Remove(webSocket); 60 | }); 61 | }); 62 | } 63 | } 64 | } -------------------------------------------------------------------------------- /1.Websockets/Websockets/Websockets.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net5.0 5 | Websockets 6 | Websockets 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /1.Websockets/Websockets/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /1.Websockets/Websockets/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | }, 9 | "AllowedHosts": "*" 10 | } 11 | -------------------------------------------------------------------------------- /1.Websockets/Websockets/wwwroot/index.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Title 6 | 7 | 8 | 9 | 25 | 26 | -------------------------------------------------------------------------------- /2.SSE_and_LP/LongPolling/LongPolling.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net5.0 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /2.SSE_and_LP/LongPolling/Program.cs: -------------------------------------------------------------------------------- 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 LongPolling 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 => 22 | { 23 | webBuilder.UseStartup(); 24 | }); 25 | } 26 | } -------------------------------------------------------------------------------- /2.SSE_and_LP/LongPolling/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:29405", 7 | "sslPort": 44343 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "environmentVariables": { 15 | "ASPNETCORE_ENVIRONMENT": "Development" 16 | } 17 | }, 18 | "LongPolling": { 19 | "commandName": "Project", 20 | "dotnetRunMessages": "true", 21 | "launchBrowser": true, 22 | "applicationUrl": "https://localhost:5001;http://localhost:5000", 23 | "environmentVariables": { 24 | "ASPNETCORE_ENVIRONMENT": "Development" 25 | } 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /2.SSE_and_LP/LongPolling/Startup.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Channels; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNetCore.Builder; 7 | using Microsoft.AspNetCore.Hosting; 8 | using Microsoft.AspNetCore.Http; 9 | using Microsoft.AspNetCore.Http.Connections; 10 | using Microsoft.AspNetCore.SignalR; 11 | using Microsoft.Extensions.DependencyInjection; 12 | using Microsoft.Extensions.Hosting; 13 | 14 | namespace LongPolling 15 | { 16 | public class Startup 17 | { 18 | private readonly Channel _channel; 19 | 20 | public Startup() 21 | { 22 | _channel = Channel.CreateUnbounded(); 23 | // Task.Run(async () => 24 | // { 25 | // int i = 0; 26 | // while (true) 27 | // { 28 | // await _channel.Writer.WriteAsync($"data: {i++}"); 29 | // await Task.Delay(5000); 30 | // } 31 | // }); 32 | } 33 | 34 | public void ConfigureServices(IServiceCollection services) 35 | { 36 | } 37 | 38 | public void Configure(IApplicationBuilder app, IWebHostEnvironment env) 39 | { 40 | if (env.IsDevelopment()) 41 | { 42 | app.UseDeveloperExceptionPage(); 43 | } 44 | 45 | app.UseStaticFiles(); 46 | 47 | app.UseRouting(); 48 | 49 | app.UseEndpoints(endpoints => 50 | { 51 | endpoints.Map("/listen", async ctx => 52 | { 53 | if (await _channel.Reader.WaitToReadAsync()) 54 | { 55 | if (_channel.Reader.TryRead(out var data)) 56 | { 57 | ctx.Response.StatusCode = 200; 58 | await ctx.Response.WriteAsync(data); 59 | return; 60 | } 61 | } 62 | 63 | ctx.Response.StatusCode = 200; 64 | }); 65 | 66 | endpoints.Map("/send", async ctx => 67 | { 68 | if (ctx.Request.Query.TryGetValue("m", out var m)) 69 | { 70 | Console.WriteLine("message to send: " + m); 71 | await _channel.Writer.WriteAsync(m); 72 | } 73 | 74 | ctx.Response.StatusCode = 200; 75 | }); 76 | }); 77 | } 78 | } 79 | } -------------------------------------------------------------------------------- /2.SSE_and_LP/LongPolling/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /2.SSE_and_LP/LongPolling/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | }, 9 | "AllowedHosts": "*" 10 | } 11 | -------------------------------------------------------------------------------- /2.SSE_and_LP/LongPolling/wwwroot/index.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Title 6 | 7 | 8 | 9 | 23 | 24 | -------------------------------------------------------------------------------- /2.SSE_and_LP/ServerSentEvents.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServerSentEvents", "ServerSentEvents\ServerSentEvents.csproj", "{1014F863-2B59-4E3A-9945-73D4F878597C}" 4 | EndProject 5 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LongPolling", "LongPolling\LongPolling.csproj", "{DA3534D6-5EB3-4EC9-9F0B-3C8A965BE8A0}" 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 | {1014F863-2B59-4E3A-9945-73D4F878597C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 14 | {1014F863-2B59-4E3A-9945-73D4F878597C}.Debug|Any CPU.Build.0 = Debug|Any CPU 15 | {1014F863-2B59-4E3A-9945-73D4F878597C}.Release|Any CPU.ActiveCfg = Release|Any CPU 16 | {1014F863-2B59-4E3A-9945-73D4F878597C}.Release|Any CPU.Build.0 = Release|Any CPU 17 | {DA3534D6-5EB3-4EC9-9F0B-3C8A965BE8A0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 18 | {DA3534D6-5EB3-4EC9-9F0B-3C8A965BE8A0}.Debug|Any CPU.Build.0 = Debug|Any CPU 19 | {DA3534D6-5EB3-4EC9-9F0B-3C8A965BE8A0}.Release|Any CPU.ActiveCfg = Release|Any CPU 20 | {DA3534D6-5EB3-4EC9-9F0B-3C8A965BE8A0}.Release|Any CPU.Build.0 = Release|Any CPU 21 | EndGlobalSection 22 | EndGlobal 23 | -------------------------------------------------------------------------------- /2.SSE_and_LP/ServerSentEvents/Program.cs: -------------------------------------------------------------------------------- 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 ServerSentEvents 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 => 22 | { 23 | webBuilder.UseStartup(); 24 | }); 25 | } 26 | } -------------------------------------------------------------------------------- /2.SSE_and_LP/ServerSentEvents/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/launchsettings.json", 3 | "iisSettings": { 4 | "windowsAuthentication": false, 5 | "anonymousAuthentication": true, 6 | "iisExpress": { 7 | "applicationUrl": "http://localhost:32636", 8 | "sslPort": 44384 9 | } 10 | }, 11 | "profiles": { 12 | "IIS Express": { 13 | "commandName": "IISExpress", 14 | "launchBrowser": true, 15 | "launchUrl": "swagger", 16 | "environmentVariables": { 17 | "ASPNETCORE_ENVIRONMENT": "Development" 18 | } 19 | }, 20 | "ServerSentEvents": { 21 | "commandName": "Project", 22 | "dotnetRunMessages": "true", 23 | "launchBrowser": true, 24 | "launchUrl": "swagger", 25 | "applicationUrl": "https://localhost:5001;http://localhost:5000", 26 | "environmentVariables": { 27 | "ASPNETCORE_ENVIRONMENT": "Development" 28 | } 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /2.SSE_and_LP/ServerSentEvents/ServerSentEvents.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net5.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /2.SSE_and_LP/ServerSentEvents/Startup.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Channels; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNetCore.Builder; 7 | using Microsoft.AspNetCore.Hosting; 8 | using Microsoft.AspNetCore.Http; 9 | using Microsoft.AspNetCore.HttpsPolicy; 10 | using Microsoft.AspNetCore.Mvc; 11 | using Microsoft.Extensions.Configuration; 12 | using Microsoft.Extensions.DependencyInjection; 13 | using Microsoft.Extensions.Hosting; 14 | using Microsoft.Extensions.Logging; 15 | using Microsoft.OpenApi.Models; 16 | 17 | namespace ServerSentEvents 18 | { 19 | public class Startup 20 | { 21 | private readonly Channel _channel; 22 | 23 | public Startup() 24 | { 25 | _channel = Channel.CreateUnbounded(); 26 | } 27 | 28 | public void ConfigureServices(IServiceCollection services) 29 | { 30 | } 31 | 32 | public void Configure(IApplicationBuilder app, IWebHostEnvironment env) 33 | { 34 | if (env.IsDevelopment()) 35 | { 36 | app.UseDeveloperExceptionPage(); 37 | } 38 | 39 | app.UseStaticFiles(); 40 | 41 | app.UseRouting(); 42 | 43 | app.UseEndpoints(endpoints => 44 | { 45 | endpoints.Map("/send", async ctx => 46 | { 47 | if (ctx.Request.Query.TryGetValue("m", out var m)) 48 | { 49 | Console.WriteLine("message to send: " + m); 50 | await _channel.Writer.WriteAsync(m); 51 | } 52 | 53 | ctx.Response.StatusCode = 200; 54 | }); 55 | }); 56 | 57 | app.Use(async (ctx, next) => 58 | { 59 | if (ctx.Request.Path.ToString().Equals("/sse")) 60 | { 61 | var response = ctx.Response; 62 | response.Headers.Add("Content-Type", "text/event-stream"); 63 | 64 | await response.WriteAsync("event: custom\r"); 65 | await response.WriteAsync("data: custom event data\r\r"); 66 | await response.Body.FlushAsync(); 67 | 68 | while (await _channel.Reader.WaitToReadAsync()) 69 | { 70 | var message = await _channel.Reader.ReadAsync(); 71 | Console.WriteLine("sending message: " + message); 72 | await response.WriteAsync($"data: {message}\r\r"); 73 | 74 | await response.Body.FlushAsync(); 75 | } 76 | } 77 | 78 | await next(); 79 | }); 80 | } 81 | } 82 | } -------------------------------------------------------------------------------- /2.SSE_and_LP/ServerSentEvents/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /2.SSE_and_LP/ServerSentEvents/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | }, 9 | "AllowedHosts": "*" 10 | } 11 | -------------------------------------------------------------------------------- /2.SSE_and_LP/ServerSentEvents/wwwroot/index.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Title 6 | 7 | 8 | 9 | 25 | 26 | -------------------------------------------------------------------------------- /3.FeatureOverview/FeatureOverview.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FeatureOverview", "FeatureOverview\FeatureOverview.csproj", "{8443E865-19C2-474A-B470-992D2391A8E7}" 4 | EndProject 5 | Global 6 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 7 | Debug|Any CPU = Debug|Any CPU 8 | Release|Any CPU = Release|Any CPU 9 | EndGlobalSection 10 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 11 | {8443E865-19C2-474A-B470-992D2391A8E7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 12 | {8443E865-19C2-474A-B470-992D2391A8E7}.Debug|Any CPU.Build.0 = Debug|Any CPU 13 | {8443E865-19C2-474A-B470-992D2391A8E7}.Release|Any CPU.ActiveCfg = Release|Any CPU 14 | {8443E865-19C2-474A-B470-992D2391A8E7}.Release|Any CPU.Build.0 = Release|Any CPU 15 | EndGlobalSection 16 | EndGlobal 17 | -------------------------------------------------------------------------------- /3.FeatureOverview/FeatureOverview/CustomHub.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Microsoft.AspNetCore.SignalR; 4 | using Microsoft.Extensions.Logging; 5 | 6 | namespace FeatureOverview 7 | { 8 | public interface IClientInterface 9 | { 10 | Task ClientHook(Data data); 11 | } 12 | 13 | public class CustomHub : Hub 14 | { 15 | private readonly ILogger _logger; 16 | 17 | public CustomHub(ILogger logger) => _logger = logger; 18 | 19 | public void ServerHook(Data data) 20 | { 21 | _logger.LogInformation("Receiving data: {0}, {1}", data, Context.ConnectionId); 22 | } 23 | 24 | public Task PingAll() 25 | { 26 | _logger.LogInformation("pinging everyone"); 27 | return Clients.All.ClientHook(new(111, "ping all")); 28 | } 29 | 30 | public Task SelfPing() 31 | { 32 | _logger.LogInformation("self pinging"); 33 | return Clients.Caller.ClientHook(new(222, "self ping")); 34 | } 35 | 36 | [HubMethodName("invocation_with_return")] 37 | public Data JustAFunction() 38 | { 39 | return new(1, "returned data from JustAFunction"); 40 | } 41 | 42 | // ------------------------------- 43 | // on connected/disconnected hooks 44 | // ------------------------------- 45 | public override Task OnConnectedAsync() 46 | { 47 | return base.OnConnectedAsync(); 48 | } 49 | 50 | public override Task OnDisconnectedAsync(Exception exception) 51 | { 52 | return base.OnDisconnectedAsync(exception); 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /3.FeatureOverview/FeatureOverview/Data.cs: -------------------------------------------------------------------------------- 1 | namespace FeatureOverview 2 | { 3 | public record Data(int Id, string Message); 4 | } -------------------------------------------------------------------------------- /3.FeatureOverview/FeatureOverview/FeatureOverview.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net5.0 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /3.FeatureOverview/FeatureOverview/GroupsHub.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Microsoft.AspNetCore.SignalR; 3 | 4 | namespace FeatureOverview 5 | { 6 | public class GroupsHub : Hub 7 | { 8 | public Task Join() => Groups.AddToGroupAsync(Context.ConnectionId, "group_name"); 9 | 10 | public Task Leave() => Groups.RemoveFromGroupAsync(Context.ConnectionId, "group_name"); 11 | 12 | public Task Message() => Clients 13 | .Groups("group_name") 14 | .SendAsync("group_message", new Data(69, "secret group message")); 15 | 16 | } 17 | } -------------------------------------------------------------------------------- /3.FeatureOverview/FeatureOverview/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Hosting; 2 | using Microsoft.Extensions.Hosting; 3 | 4 | namespace FeatureOverview 5 | { 6 | public class Program 7 | { 8 | public static void Main(string[] args) 9 | { 10 | CreateHostBuilder(args).Build().Run(); 11 | } 12 | 13 | public static IHostBuilder CreateHostBuilder(string[] args) => 14 | Host.CreateDefaultBuilder(args) 15 | .ConfigureWebHostDefaults(webBuilder => 16 | { 17 | webBuilder.UseStartup(); 18 | }); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /3.FeatureOverview/FeatureOverview/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:6611", 7 | "sslPort": 44308 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "environmentVariables": { 15 | "ASPNETCORE_ENVIRONMENT": "Development" 16 | } 17 | }, 18 | "FeatureOverview": { 19 | "commandName": "Project", 20 | "dotnetRunMessages": "true", 21 | "launchBrowser": true, 22 | "applicationUrl": "https://localhost:5001;http://localhost:5000", 23 | "environmentVariables": { 24 | "ASPNETCORE_ENVIRONMENT": "Development" 25 | } 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /3.FeatureOverview/FeatureOverview/Startup.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Builder; 2 | using Microsoft.AspNetCore.Hosting; 3 | using Microsoft.Extensions.DependencyInjection; 4 | using Microsoft.Extensions.Hosting; 5 | 6 | namespace FeatureOverview 7 | { 8 | public class Startup 9 | { 10 | public void ConfigureServices(IServiceCollection services) 11 | { 12 | services.AddControllers(); 13 | services.AddSignalR(); 14 | } 15 | 16 | public void Configure(IApplicationBuilder app, IWebHostEnvironment env) 17 | { 18 | if (env.IsDevelopment()) 19 | { 20 | app.UseDeveloperExceptionPage(); 21 | } 22 | 23 | app.UseStaticFiles(); 24 | 25 | app.UseRouting(); 26 | 27 | app.UseEndpoints(endpoints => 28 | { 29 | endpoints.MapDefaultControllerRoute(); 30 | endpoints.MapHub("/custom"); 31 | endpoints.MapHub("/groups"); 32 | }); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /3.FeatureOverview/FeatureOverview/TestController.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Microsoft.AspNetCore.Mvc; 3 | using Microsoft.AspNetCore.SignalR; 4 | 5 | namespace FeatureOverview 6 | { 7 | public class TestController : ControllerBase 8 | { 9 | private readonly IHubContext _customHub; 10 | // private readonly IHubContext _customHub; 11 | 12 | public TestController( 13 | IHubContext customHub 14 | // IHubContext customHub 15 | ) 16 | { 17 | _customHub = customHub; 18 | } 19 | 20 | [HttpGet("/send")] 21 | public async Task SendData() 22 | { 23 | await _customHub.Clients.All.SendAsync("client_function_name", new Data(100, "Dummy Data")); 24 | return Ok(); 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /3.FeatureOverview/FeatureOverview/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /3.FeatureOverview/FeatureOverview/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | }, 9 | "AllowedHosts": "*" 10 | } 11 | -------------------------------------------------------------------------------- /3.FeatureOverview/FeatureOverview/wwwroot/groups.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Title 6 | 7 | 8 | 9 | 10 | 24 | 25 | -------------------------------------------------------------------------------- /3.FeatureOverview/FeatureOverview/wwwroot/index.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Title 6 | 7 | 8 | 9 | 40 | 41 | -------------------------------------------------------------------------------- /4.StreamingData/StreamingData.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StreamingData_8", "StreamingData_8\StreamingData_8.csproj", "{987616F4-075C-4216-AC78-AED415B2803B}" 4 | EndProject 5 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StreamingData_7", "StreamingData_7\StreamingData_7.csproj", "{D83D7CB5-64CB-4CF6-9467-5A003E8F72AD}" 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 | {987616F4-075C-4216-AC78-AED415B2803B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 14 | {987616F4-075C-4216-AC78-AED415B2803B}.Debug|Any CPU.Build.0 = Debug|Any CPU 15 | {987616F4-075C-4216-AC78-AED415B2803B}.Release|Any CPU.ActiveCfg = Release|Any CPU 16 | {987616F4-075C-4216-AC78-AED415B2803B}.Release|Any CPU.Build.0 = Release|Any CPU 17 | {D83D7CB5-64CB-4CF6-9467-5A003E8F72AD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 18 | {D83D7CB5-64CB-4CF6-9467-5A003E8F72AD}.Debug|Any CPU.Build.0 = Debug|Any CPU 19 | {D83D7CB5-64CB-4CF6-9467-5A003E8F72AD}.Release|Any CPU.ActiveCfg = Release|Any CPU 20 | {D83D7CB5-64CB-4CF6-9467-5A003E8F72AD}.Release|Any CPU.Build.0 = Release|Any CPU 21 | EndGlobalSection 22 | EndGlobal 23 | -------------------------------------------------------------------------------- /4.StreamingData/StreamingData_7/Data.cs: -------------------------------------------------------------------------------- 1 | namespace StreamingData_7 2 | { 3 | public class Data 4 | { 5 | public int Count { get; set; } 6 | public string Message { get; set; } 7 | } 8 | } -------------------------------------------------------------------------------- /4.StreamingData/StreamingData_7/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Hosting; 2 | using Microsoft.Extensions.Hosting; 3 | 4 | namespace StreamingData_7 5 | { 6 | public class Program 7 | { 8 | public static void Main(string[] args) 9 | { 10 | CreateHostBuilder(args).Build().Run(); 11 | } 12 | 13 | public static IHostBuilder CreateHostBuilder(string[] args) => 14 | Host.CreateDefaultBuilder(args) 15 | .ConfigureWebHostDefaults(webBuilder => 16 | { 17 | webBuilder.UseStartup(); 18 | }); 19 | } 20 | } -------------------------------------------------------------------------------- /4.StreamingData/StreamingData_7/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/launchsettings.json", 3 | "iisSettings": { 4 | "windowsAuthentication": false, 5 | "anonymousAuthentication": true, 6 | "iisExpress": { 7 | "applicationUrl": "http://localhost:47263", 8 | "sslPort": 44370 9 | } 10 | }, 11 | "profiles": { 12 | "IIS Express": { 13 | "commandName": "IISExpress", 14 | "launchBrowser": true, 15 | "environmentVariables": { 16 | "ASPNETCORE_ENVIRONMENT": "Development" 17 | } 18 | }, 19 | "StreamingData": { 20 | "commandName": "Project", 21 | "dotnetRunMessages": "true", 22 | "launchBrowser": true, 23 | "applicationUrl": "https://localhost:5001;http://localhost:5000", 24 | "environmentVariables": { 25 | "ASPNETCORE_ENVIRONMENT": "Development" 26 | } 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /4.StreamingData/StreamingData_7/Startup.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Builder; 2 | using Microsoft.AspNetCore.Hosting; 3 | using Microsoft.Extensions.DependencyInjection; 4 | using Microsoft.Extensions.Hosting; 5 | 6 | namespace StreamingData_7 7 | { 8 | public class Startup 9 | { 10 | public void ConfigureServices(IServiceCollection services) 11 | { 12 | services.AddSignalR(); 13 | } 14 | 15 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 16 | public void Configure(IApplicationBuilder app, IWebHostEnvironment env) 17 | { 18 | if (env.IsDevelopment()) 19 | { 20 | app.UseDeveloperExceptionPage(); 21 | } 22 | 23 | app.UseStaticFiles(); 24 | 25 | app.UseRouting(); 26 | 27 | app.UseEndpoints(endpoints => 28 | { 29 | endpoints.MapHub("/stream"); 30 | }); 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /4.StreamingData/StreamingData_7/StreamingData_7.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net5.0 5 | 7.3 6 | StreamingData_7 7 | StreamingData_7 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /4.StreamingData/StreamingData_7/StreamingHub.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Channels; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.SignalR; 6 | 7 | namespace StreamingData_7 8 | { 9 | public class StreamingHub : Hub 10 | { 11 | public ChannelReader Download( 12 | Data data, 13 | CancellationToken cancellationToken 14 | ) 15 | { 16 | var channel = Channel.CreateUnbounded(); 17 | 18 | Task.Run(async () => 19 | { 20 | try 21 | { 22 | for (var i = 0; i < data.Count; i++) 23 | { 24 | await channel.Writer.WriteAsync($"{i}_{data.Message}", cancellationToken); 25 | await Task.Delay(1000, cancellationToken); 26 | } 27 | } 28 | catch (Exception ignored) 29 | { 30 | } 31 | finally 32 | { 33 | channel.Writer.Complete(); 34 | } 35 | }, cancellationToken); 36 | 37 | 38 | return channel.Reader; 39 | } 40 | 41 | public async Task Upload(ChannelReader dataStream) 42 | { 43 | while (await dataStream.WaitToReadAsync()) 44 | { 45 | if (dataStream.TryRead(out var data)) 46 | { 47 | Console.WriteLine("Received Data: {0},{1}", data.Count, data.Message); 48 | } 49 | } 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /4.StreamingData/StreamingData_7/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /4.StreamingData/StreamingData_7/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | }, 9 | "AllowedHosts": "*" 10 | } 11 | -------------------------------------------------------------------------------- /4.StreamingData/StreamingData_7/wwwroot/index.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Title 6 | 7 | 8 | 9 | 40 | 41 | -------------------------------------------------------------------------------- /4.StreamingData/StreamingData_8/Data.cs: -------------------------------------------------------------------------------- 1 | namespace StreamingData_8 2 | { 3 | public class Data 4 | { 5 | public int Count { get; set; } 6 | public string Message { get; set; } 7 | } 8 | } -------------------------------------------------------------------------------- /4.StreamingData/StreamingData_8/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Hosting; 2 | using Microsoft.Extensions.Hosting; 3 | 4 | namespace StreamingData_8 5 | { 6 | public class Program 7 | { 8 | public static void Main(string[] args) 9 | { 10 | CreateHostBuilder(args).Build().Run(); 11 | } 12 | 13 | public static IHostBuilder CreateHostBuilder(string[] args) => 14 | Host.CreateDefaultBuilder(args) 15 | .ConfigureWebHostDefaults(webBuilder => 16 | { 17 | webBuilder.UseStartup(); 18 | }); 19 | } 20 | } -------------------------------------------------------------------------------- /4.StreamingData/StreamingData_8/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/launchsettings.json", 3 | "iisSettings": { 4 | "windowsAuthentication": false, 5 | "anonymousAuthentication": true, 6 | "iisExpress": { 7 | "applicationUrl": "http://localhost:47263", 8 | "sslPort": 44370 9 | } 10 | }, 11 | "profiles": { 12 | "IIS Express": { 13 | "commandName": "IISExpress", 14 | "launchBrowser": true, 15 | "launchUrl": "swagger", 16 | "environmentVariables": { 17 | "ASPNETCORE_ENVIRONMENT": "Development" 18 | } 19 | }, 20 | "StreamingData": { 21 | "commandName": "Project", 22 | "dotnetRunMessages": "true", 23 | "launchBrowser": true, 24 | "applicationUrl": "https://localhost:5001;http://localhost:5000", 25 | "environmentVariables": { 26 | "ASPNETCORE_ENVIRONMENT": "Development" 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /4.StreamingData/StreamingData_8/Startup.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Builder; 2 | using Microsoft.AspNetCore.Hosting; 3 | using Microsoft.Extensions.DependencyInjection; 4 | using Microsoft.Extensions.Hosting; 5 | 6 | namespace StreamingData_8 7 | { 8 | public class Startup 9 | { 10 | public void ConfigureServices(IServiceCollection services) 11 | { 12 | services.AddSignalR(); 13 | } 14 | 15 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 16 | public void Configure(IApplicationBuilder app, IWebHostEnvironment env) 17 | { 18 | if (env.IsDevelopment()) 19 | { 20 | app.UseDeveloperExceptionPage(); 21 | } 22 | 23 | app.UseStaticFiles(); 24 | 25 | app.UseRouting(); 26 | 27 | app.UseEndpoints(endpoints => 28 | { 29 | endpoints.MapHub("/stream"); 30 | }); 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /4.StreamingData/StreamingData_8/StreamingData_8.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net5.0 5 | 8 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /4.StreamingData/StreamingData_8/StreamingHub.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Runtime.CompilerServices; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNetCore.SignalR; 7 | 8 | namespace StreamingData_8 9 | { 10 | public class StreamingHub : Hub 11 | { 12 | private readonly string _id; 13 | 14 | public StreamingHub() 15 | { 16 | _id = Guid.NewGuid().ToString(); 17 | } 18 | 19 | public string Call() => _id; 20 | 21 | public async IAsyncEnumerable Download( 22 | Data data, 23 | [EnumeratorCancellation] CancellationToken cancellationToken 24 | ) 25 | { 26 | for (var i = 0; i < data.Count; i++) 27 | { 28 | cancellationToken.ThrowIfCancellationRequested(); 29 | 30 | yield return $"{i}_{data.Message}_{_id}"; 31 | 32 | await Task.Delay(1000, cancellationToken); 33 | } 34 | } 35 | 36 | public async Task Upload(IAsyncEnumerable dataStream) 37 | { 38 | await foreach (var data in dataStream) 39 | { 40 | Console.WriteLine("Received Data: {0},{1},{2}", data.Count, data.Message, _id); 41 | } 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /4.StreamingData/StreamingData_8/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /4.StreamingData/StreamingData_8/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | }, 9 | "AllowedHosts": "*" 10 | } 11 | -------------------------------------------------------------------------------- /4.StreamingData/StreamingData_8/wwwroot/index.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Title 6 | 7 | 8 | 9 | 44 | 45 | -------------------------------------------------------------------------------- /4.StreamingData/global.json: -------------------------------------------------------------------------------- 1 | { 2 | "sdk": { 3 | "version": "5.0", 4 | "rollForward": "latestMajor", 5 | "allowPrerelease": false 6 | } 7 | } -------------------------------------------------------------------------------- /5.Authentication/Authentication.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Authentication", "Authentication\Authentication.csproj", "{D8E41CB0-DEDB-46BF-A36C-4DAC7FE518AC}" 4 | EndProject 5 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AuthenticationExpiry", "AuthenticationExpiry\AuthenticationExpiry.csproj", "{D811BD99-1995-48FF-8029-C89A87355D5C}" 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 | {D8E41CB0-DEDB-46BF-A36C-4DAC7FE518AC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 14 | {D8E41CB0-DEDB-46BF-A36C-4DAC7FE518AC}.Debug|Any CPU.Build.0 = Debug|Any CPU 15 | {D8E41CB0-DEDB-46BF-A36C-4DAC7FE518AC}.Release|Any CPU.ActiveCfg = Release|Any CPU 16 | {D8E41CB0-DEDB-46BF-A36C-4DAC7FE518AC}.Release|Any CPU.Build.0 = Release|Any CPU 17 | {D811BD99-1995-48FF-8029-C89A87355D5C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 18 | {D811BD99-1995-48FF-8029-C89A87355D5C}.Debug|Any CPU.Build.0 = Debug|Any CPU 19 | {D811BD99-1995-48FF-8029-C89A87355D5C}.Release|Any CPU.ActiveCfg = Release|Any CPU 20 | {D811BD99-1995-48FF-8029-C89A87355D5C}.Release|Any CPU.Build.0 = Release|Any CPU 21 | EndGlobalSection 22 | EndGlobal 23 | -------------------------------------------------------------------------------- /5.Authentication/Authentication/Authentication.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net5.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /5.Authentication/Authentication/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Hosting; 2 | using Microsoft.Extensions.Hosting; 3 | 4 | namespace Authentication 5 | { 6 | public class Program 7 | { 8 | public static void Main(string[] args) 9 | { 10 | CreateHostBuilder(args).Build().Run(); 11 | } 12 | 13 | public static IHostBuilder CreateHostBuilder(string[] args) => 14 | Host.CreateDefaultBuilder(args) 15 | .ConfigureWebHostDefaults(webBuilder => 16 | { 17 | webBuilder.UseStartup(); 18 | }); 19 | } 20 | } -------------------------------------------------------------------------------- /5.Authentication/Authentication/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/launchsettings.json", 3 | "iisSettings": { 4 | "windowsAuthentication": false, 5 | "anonymousAuthentication": true, 6 | "iisExpress": { 7 | "applicationUrl": "http://localhost:7620", 8 | "sslPort": 44310 9 | } 10 | }, 11 | "profiles": { 12 | "IIS Express": { 13 | "commandName": "IISExpress", 14 | "launchBrowser": true, 15 | "environmentVariables": { 16 | "ASPNETCORE_ENVIRONMENT": "Development" 17 | } 18 | }, 19 | "Auth_Cookie": { 20 | "commandName": "Project", 21 | "dotnetRunMessages": "true", 22 | "applicationUrl": "https://localhost:5001;http://localhost:5000", 23 | "environmentVariables": { 24 | "ASPNETCORE_ENVIRONMENT": "Development" 25 | } 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /5.Authentication/Authentication/ProtectedHub.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using Microsoft.AspNetCore.Authorization; 3 | using Microsoft.AspNetCore.SignalR; 4 | 5 | namespace Authentication 6 | { 7 | // [Authorize] 8 | // [Authorize(AuthenticationSchemes = Startup.CustomCookieScheme)] 9 | [Authorize(AuthenticationSchemes = Startup.CustomCookieScheme + "," + Startup.CustomTokenScheme)] 10 | public class ProtectedHub : Hub 11 | { 12 | [Authorize("Cookie")] 13 | public object CookieProtected() 14 | { 15 | return CompileResult(); 16 | } 17 | 18 | [Authorize("Token")] 19 | public object TokenProtected() 20 | { 21 | return CompileResult(); 22 | } 23 | 24 | private object CompileResult() => 25 | new 26 | { 27 | UserId = Context.UserIdentifier, 28 | Claims = Context.User.Claims.Select(x => new { x.Type, x.Value }) 29 | }; 30 | } 31 | } -------------------------------------------------------------------------------- /5.Authentication/Authentication/Startup.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Security.Claims; 4 | using System.Text.Encodings.Web; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNetCore.Authentication; 7 | using Microsoft.AspNetCore.Builder; 8 | using Microsoft.AspNetCore.Hosting; 9 | using Microsoft.AspNetCore.Http; 10 | using Microsoft.AspNetCore.Http.Connections; 11 | using Microsoft.AspNetCore.SignalR; 12 | using Microsoft.Extensions.DependencyInjection; 13 | using Microsoft.Extensions.Hosting; 14 | using Microsoft.Extensions.Logging; 15 | using Microsoft.Extensions.Options; 16 | 17 | namespace Authentication 18 | { 19 | public class Startup 20 | { 21 | public const string CustomCookieScheme = nameof(CustomCookieScheme); 22 | public const string CustomTokenScheme = nameof(CustomTokenScheme); 23 | 24 | public void ConfigureServices(IServiceCollection services) 25 | { 26 | services.AddAuthentication() 27 | .AddScheme(CustomCookieScheme, _ => 28 | { 29 | }) 30 | .AddJwtBearer(CustomTokenScheme, o => 31 | { 32 | o.Events = new() 33 | { 34 | OnMessageReceived = (context) => 35 | { 36 | var path = context.HttpContext.Request.Path; 37 | if (path.StartsWithSegments("/protected") 38 | || path.StartsWithSegments("/token")) 39 | { 40 | var accessToken = context.Request.Query["access_token"]; 41 | 42 | if (!string.IsNullOrWhiteSpace(accessToken)) 43 | { 44 | // context.Token = accessToken; 45 | 46 | var claims = new Claim[] 47 | { 48 | new("user_id", accessToken), 49 | new("token", "token_claim"), 50 | }; 51 | var identity = new ClaimsIdentity(claims, CustomTokenScheme); 52 | context.Principal = new(identity); 53 | context.Success(); 54 | } 55 | } 56 | 57 | return Task.CompletedTask; 58 | }, 59 | }; 60 | }); 61 | 62 | services.AddAuthorization(c => 63 | { 64 | c.AddPolicy("Cookie", pb => pb 65 | .AddAuthenticationSchemes(CustomCookieScheme) 66 | .RequireAuthenticatedUser()); 67 | 68 | c.AddPolicy("Token", pb => pb 69 | // schema get's ignored in signalr 70 | .AddAuthenticationSchemes(CustomTokenScheme) 71 | .RequireClaim("token") 72 | .RequireAuthenticatedUser()); 73 | }); 74 | 75 | services.AddSignalR(); 76 | 77 | services.AddSingleton(); 78 | } 79 | 80 | public void Configure(IApplicationBuilder app, IWebHostEnvironment env) 81 | { 82 | if (env.IsDevelopment()) 83 | { 84 | app.UseDeveloperExceptionPage(); 85 | } 86 | 87 | app.UseStaticFiles(); 88 | 89 | app.UseRouting(); 90 | 91 | app.UseAuthentication(); 92 | app.UseAuthorization(); 93 | 94 | app.UseEndpoints(endpoints => 95 | { 96 | endpoints.MapHub("/protected", o => 97 | { 98 | // o.Transports = HttpTransportType.LongPolling; 99 | }); 100 | 101 | endpoints.Map("/get-cookie", ctx => 102 | { 103 | ctx.Response.StatusCode = 200; 104 | ctx.Response.Cookies.Append("signalr-auth-cookie", Guid.NewGuid().ToString(), new() 105 | { 106 | Expires = DateTimeOffset.UtcNow.AddSeconds(30) 107 | }); 108 | return ctx.Response.WriteAsync(""); 109 | }); 110 | 111 | endpoints.Map("/token", ctx => 112 | { 113 | ctx.Response.StatusCode = 200; 114 | return ctx.Response.WriteAsync(ctx.User?.Claims.FirstOrDefault(x => x.Type == "user_id")?.Value); 115 | }).RequireAuthorization("Token"); 116 | 117 | endpoints.Map("/cookie", ctx => 118 | { 119 | ctx.Response.StatusCode = 200; 120 | return ctx.Response.WriteAsync(ctx.User?.Claims.FirstOrDefault(x => x.Type == "user_id")?.Value); 121 | }).RequireAuthorization("Cookie"); 122 | }); 123 | } 124 | 125 | public class CustomCookie : AuthenticationHandler 126 | { 127 | public CustomCookie( 128 | IOptionsMonitor options, 129 | ILoggerFactory logger, 130 | UrlEncoder encoder, 131 | ISystemClock clock 132 | ) : base(options, logger, encoder, clock) 133 | { 134 | } 135 | 136 | protected override Task HandleAuthenticateAsync() 137 | { 138 | if (Context.Request.Cookies.TryGetValue("signalr-auth-cookie", out var cookie)) 139 | { 140 | var claims = new Claim[] 141 | { 142 | new("user_id", cookie), 143 | new("cookie", "cookie_claim"), 144 | }; 145 | var identity = new ClaimsIdentity(claims, CustomCookieScheme); 146 | var principal = new ClaimsPrincipal(identity); 147 | var ticket = new AuthenticationTicket(principal, new(), CustomCookieScheme); 148 | return Task.FromResult(AuthenticateResult.Success(ticket)); 149 | } 150 | 151 | return Task.FromResult(AuthenticateResult.Fail("signalr-auth-cookie not found")); 152 | } 153 | } 154 | 155 | public class UserIdProvider : IUserIdProvider 156 | { 157 | public string GetUserId(HubConnectionContext connection) 158 | { 159 | return connection.User?.Claims.FirstOrDefault(x => x.Type == "user_id")?.Value; 160 | } 161 | } 162 | } 163 | } -------------------------------------------------------------------------------- /5.Authentication/Authentication/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /5.Authentication/Authentication/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | }, 9 | "AllowedHosts": "*" 10 | } 11 | -------------------------------------------------------------------------------- /5.Authentication/Authentication/wwwroot/index.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Title 6 | 7 | 8 | 9 | 27 | 28 | -------------------------------------------------------------------------------- /5.Authentication/AuthenticationExpiry/AuthHubFilter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Threading.Tasks; 4 | using Microsoft.AspNetCore.SignalR; 5 | 6 | namespace AuthenticationExpiry 7 | { 8 | public class AuthException : HubException 9 | { 10 | public AuthException(string message) : base(message) 11 | { 12 | } 13 | } 14 | 15 | public class AuthHubFilter : IHubFilter 16 | { 17 | public async ValueTask InvokeMethodAsync( 18 | HubInvocationContext invocationContext, 19 | Func> next 20 | ) 21 | { 22 | await Task.Delay(1000); 23 | var expiry = invocationContext.Context.User.Claims.FirstOrDefault(x => x.Type == "expires").Value; 24 | var expiryDate = new DateTimeOffset(long.Parse(expiry), TimeSpan.Zero); 25 | if (DateTimeOffset.UtcNow.Subtract(expiryDate) > TimeSpan.Zero) 26 | { 27 | throw new AuthException("auth_expired"); 28 | // await invocationContext.Hub.Clients.Caller.SendAsync("session_expired"); 29 | } 30 | 31 | return await next(invocationContext); 32 | } 33 | 34 | public Task OnConnectedAsync( 35 | HubLifetimeContext context, 36 | Func next 37 | ) 38 | { 39 | return next(context); 40 | } 41 | 42 | public Task OnDisconnectedAsync( 43 | HubLifetimeContext context, 44 | Exception exception, 45 | Func next 46 | ) 47 | { 48 | return next(context, exception); 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /5.Authentication/AuthenticationExpiry/AuthenticationExpiry.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net5.0 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /5.Authentication/AuthenticationExpiry/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Hosting; 2 | using Microsoft.Extensions.Hosting; 3 | 4 | namespace AuthenticationExpiry 5 | { 6 | public class Program 7 | { 8 | public static void Main(string[] args) 9 | { 10 | CreateHostBuilder(args).Build().Run(); 11 | } 12 | 13 | public static IHostBuilder CreateHostBuilder(string[] args) => 14 | Host.CreateDefaultBuilder(args) 15 | .ConfigureWebHostDefaults(webBuilder => 16 | { 17 | webBuilder.UseStartup(); 18 | }); 19 | } 20 | } -------------------------------------------------------------------------------- /5.Authentication/AuthenticationExpiry/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:3940", 7 | "sslPort": 44304 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "environmentVariables": { 14 | "ASPNETCORE_ENVIRONMENT": "Development" 15 | } 16 | }, 17 | "AuthenticationExpiry": { 18 | "commandName": "Project", 19 | "dotnetRunMessages": "true", 20 | "applicationUrl": "https://localhost:5001;http://localhost:5000", 21 | "environmentVariables": { 22 | "ASPNETCORE_ENVIRONMENT": "Development" 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /5.Authentication/AuthenticationExpiry/ProtectedHub.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Authorization; 2 | using Microsoft.AspNetCore.SignalR; 3 | 4 | namespace AuthenticationExpiry 5 | { 6 | [Authorize(AuthenticationSchemes = Startup.CustomCookieScheme)] 7 | public class ProtectedHub : Hub 8 | { 9 | public string AuthorizedResource() 10 | { 11 | return "authorized resource"; 12 | } 13 | 14 | // no reason can be specified 15 | public void Abort() => Context.Abort(); 16 | } 17 | } -------------------------------------------------------------------------------- /5.Authentication/AuthenticationExpiry/Startup.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Security.Claims; 4 | using System.Text.Encodings.Web; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNetCore.Authentication; 7 | using Microsoft.AspNetCore.Builder; 8 | using Microsoft.AspNetCore.Hosting; 9 | using Microsoft.AspNetCore.Http; 10 | using Microsoft.AspNetCore.SignalR; 11 | using Microsoft.Extensions.DependencyInjection; 12 | using Microsoft.Extensions.Hosting; 13 | using Microsoft.Extensions.Logging; 14 | using Microsoft.Extensions.Options; 15 | 16 | namespace AuthenticationExpiry 17 | { 18 | public class Startup 19 | { 20 | public const string CustomCookieScheme = nameof(CustomCookieScheme); 21 | 22 | public void ConfigureServices(IServiceCollection services) 23 | { 24 | services.AddAuthentication() 25 | .AddScheme(CustomCookieScheme, _ => 26 | { 27 | }); 28 | 29 | services.AddSignalR(o => 30 | { 31 | o.AddFilter(); 32 | }); 33 | 34 | services.AddSingleton(); 35 | } 36 | 37 | public void Configure(IApplicationBuilder app, IWebHostEnvironment env) 38 | { 39 | if (env.IsDevelopment()) 40 | { 41 | app.UseDeveloperExceptionPage(); 42 | } 43 | 44 | app.UseStaticFiles(); 45 | 46 | app.UseRouting(); 47 | 48 | app.UseAuthentication(); 49 | app.UseAuthorization(); 50 | 51 | app.UseEndpoints(endpoints => 52 | { 53 | endpoints.MapHub("/protected"); 54 | 55 | endpoints.Map("/get-cookie", ctx => 56 | { 57 | ctx.Response.StatusCode = 200; 58 | ctx.Response.Cookies.Append("signalr-auth-cookie", Guid.NewGuid().ToString(), new() 59 | { 60 | Expires = DateTimeOffset.UtcNow.AddSeconds(30) 61 | }); 62 | return ctx.Response.WriteAsync(""); 63 | }); 64 | }); 65 | } 66 | 67 | public class CustomCookie : AuthenticationHandler 68 | { 69 | public CustomCookie( 70 | IOptionsMonitor options, 71 | ILoggerFactory logger, 72 | UrlEncoder encoder, 73 | ISystemClock clock 74 | ) : base(options, logger, encoder, clock) 75 | { 76 | } 77 | 78 | protected override Task HandleAuthenticateAsync() 79 | { 80 | if (Context.Request.Cookies.TryGetValue("signalr-auth-cookie", out var cookie)) 81 | { 82 | var claims = new Claim[] 83 | { 84 | new("user_id", cookie), 85 | new("cookie", "cookie_claim"), 86 | // load from cookie 87 | new("expires", DateTimeOffset.UtcNow.AddSeconds(30).Ticks.ToString()), 88 | }; 89 | var identity = new ClaimsIdentity(claims, CustomCookieScheme); 90 | var principal = new ClaimsPrincipal(identity); 91 | var ticket = new AuthenticationTicket(principal, new(), CustomCookieScheme); 92 | return Task.FromResult(AuthenticateResult.Success(ticket)); 93 | } 94 | 95 | return Task.FromResult(AuthenticateResult.Fail("signalr-auth-cookie not found")); 96 | } 97 | } 98 | 99 | public class UserIdProvider : IUserIdProvider 100 | { 101 | public string GetUserId(HubConnectionContext connection) 102 | { 103 | return connection.User?.Claims.FirstOrDefault(x => x.Type == "user_id")?.Value; 104 | } 105 | } 106 | } 107 | } -------------------------------------------------------------------------------- /5.Authentication/AuthenticationExpiry/wwwroot/index.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Title 6 | 7 | 8 | 9 | 57 | 58 | -------------------------------------------------------------------------------- /6.Recepies/ChatApp/ChatApp.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net5.0 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /6.Recepies/ChatApp/ChatHub.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Authorization; 6 | using Microsoft.AspNetCore.SignalR; 7 | 8 | namespace ChatApp 9 | { 10 | [Authorize] 11 | public class ChatHub : Hub 12 | { 13 | private readonly ChatRegistry _registry; 14 | 15 | public ChatHub(ChatRegistry registry) 16 | { 17 | _registry = registry; 18 | } 19 | 20 | public async Task> JoinRoom(RoomRequest request) 21 | { 22 | await Groups.AddToGroupAsync(Context.ConnectionId, request.Room); 23 | 24 | return _registry.GetMessages(request.Room) 25 | .Select(m => m.Output) 26 | .ToList(); 27 | } 28 | 29 | public Task LeaveRoom(RoomRequest request) 30 | { 31 | return Groups.RemoveFromGroupAsync(Context.ConnectionId, request.Room); 32 | } 33 | 34 | public Task SendMessage(InputMessage message) 35 | { 36 | var username = Context.User.Claims.FirstOrDefault(x => x.Type == "username").Value; 37 | 38 | var userMessage = new UserMessage( 39 | new(Context.UserIdentifier, username), 40 | message.Message, 41 | message.Room, 42 | DateTimeOffset.Now 43 | ); 44 | 45 | _registry.AddMessage(message.Room, userMessage); 46 | return Clients.GroupExcept(message.Room, new[] { Context.ConnectionId }) 47 | .SendAsync("send_message", userMessage.Output); 48 | } 49 | } 50 | } -------------------------------------------------------------------------------- /6.Recepies/ChatApp/ChatRegistry.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | 4 | namespace ChatApp 5 | { 6 | public class ChatRegistry 7 | { 8 | private readonly Dictionary> _roomMessages = new(); 9 | 10 | public void CreateRoom(string room) 11 | { 12 | _roomMessages[room] = new(); 13 | } 14 | 15 | public void AddMessage(string room, UserMessage message) 16 | { 17 | _roomMessages[room].Add(message); 18 | } 19 | 20 | public List GetMessages(string room) 21 | { 22 | return _roomMessages[room]; 23 | } 24 | 25 | public List GetRooms() 26 | { 27 | return _roomMessages.Keys.ToList(); 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /6.Recepies/ChatApp/DefaultController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Security.Claims; 3 | using Microsoft.AspNetCore.Authentication; 4 | using Microsoft.AspNetCore.Authorization; 5 | using Microsoft.AspNetCore.Mvc; 6 | 7 | namespace ChatApp 8 | { 9 | [Authorize] 10 | public class DefaultController : ControllerBase 11 | { 12 | private readonly ChatRegistry _chatRegistry; 13 | 14 | public DefaultController(ChatRegistry chatRegistry) => _chatRegistry = chatRegistry; 15 | 16 | [AllowAnonymous] 17 | [HttpGet("/auth")] 18 | public IActionResult Authenticate(string username) 19 | { 20 | var claims = new Claim[] 21 | { 22 | new("user_id", Guid.NewGuid().ToString()), 23 | new("username", username), 24 | }; 25 | 26 | var identity = new ClaimsIdentity(claims, "Cookie"); 27 | var principal = new ClaimsPrincipal(identity); 28 | 29 | HttpContext.SignInAsync("Cookie", principal); 30 | return Ok(); 31 | } 32 | 33 | [HttpGet("/create")] 34 | public IActionResult CreateRoom(string room) 35 | { 36 | _chatRegistry.CreateRoom(room); 37 | return Ok(); 38 | } 39 | 40 | [HttpGet("/list")] 41 | public IActionResult ListRooms() 42 | { 43 | return Ok(_chatRegistry.GetRooms()); 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /6.Recepies/ChatApp/Models.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace ChatApp 4 | { 5 | public record User(string UserId, string UserName); 6 | 7 | public record RoomRequest(string Room); 8 | 9 | public record InputMessage( 10 | string Message, 11 | string Room 12 | ); 13 | 14 | public record OutputMessage( 15 | string Message, 16 | string UserName, 17 | string Room, 18 | DateTimeOffset SentAt 19 | ); 20 | 21 | public record UserMessage( 22 | User User, 23 | string Message, 24 | string Room, 25 | DateTimeOffset SentAt 26 | ) 27 | { 28 | public OutputMessage Output => new(Message, User.UserName, Room, SentAt); 29 | } 30 | } -------------------------------------------------------------------------------- /6.Recepies/ChatApp/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Hosting; 2 | using Microsoft.Extensions.Hosting; 3 | 4 | namespace ChatApp 5 | { 6 | public class Program 7 | { 8 | public static void Main(string[] args) 9 | { 10 | CreateHostBuilder(args).Build().Run(); 11 | } 12 | 13 | public static IHostBuilder CreateHostBuilder(string[] args) => 14 | Host.CreateDefaultBuilder(args) 15 | .ConfigureWebHostDefaults(webBuilder => 16 | { 17 | webBuilder.UseStartup(); 18 | }); 19 | } 20 | } -------------------------------------------------------------------------------- /6.Recepies/ChatApp/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:47217", 7 | "sslPort": 44370 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "environmentVariables": { 14 | "ASPNETCORE_ENVIRONMENT": "Development" 15 | } 16 | }, 17 | "ChatApp": { 18 | "commandName": "Project", 19 | "dotnetRunMessages": "true", 20 | "applicationUrl": "https://localhost:5001;http://localhost:5000", 21 | "environmentVariables": { 22 | "ASPNETCORE_ENVIRONMENT": "Development" 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /6.Recepies/ChatApp/Startup.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Builder; 2 | using Microsoft.AspNetCore.Hosting; 3 | using Microsoft.AspNetCore.SignalR; 4 | using Microsoft.Extensions.DependencyInjection; 5 | using Microsoft.Extensions.Hosting; 6 | 7 | namespace ChatApp 8 | { 9 | public class Startup 10 | { 11 | public void ConfigureServices(IServiceCollection services) 12 | { 13 | services.AddAuthentication("Cookie") 14 | .AddCookie("Cookie"); 15 | 16 | services.AddSingleton(); 17 | services.AddSingleton(); 18 | 19 | services.AddSignalR(); 20 | services.AddControllers(); 21 | } 22 | 23 | public void Configure(IApplicationBuilder app, IWebHostEnvironment env) 24 | { 25 | if (env.IsDevelopment()) 26 | { 27 | app.UseDeveloperExceptionPage(); 28 | } 29 | 30 | app.UseStaticFiles(); 31 | 32 | app.UseRouting(); 33 | 34 | app.UseAuthentication(); 35 | 36 | app.UseAuthorization(); 37 | 38 | app.UseEndpoints(endpoints => 39 | { 40 | endpoints.MapHub("/chat"); 41 | endpoints.MapDefaultControllerRoute(); 42 | }); 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /6.Recepies/ChatApp/UserIdProvider.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using Microsoft.AspNetCore.SignalR; 3 | 4 | namespace ChatApp 5 | { 6 | public class UserIdProvider : IUserIdProvider 7 | { 8 | public string? GetUserId(HubConnectionContext connection) 9 | { 10 | return connection.User?.Claims.FirstOrDefault(x => x.Type == "user_id")?.Value; 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /6.Recepies/ChatApp/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /6.Recepies/ChatApp/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | }, 9 | "AllowedHosts": "*" 10 | } 11 | -------------------------------------------------------------------------------- /6.Recepies/ChatApp/wwwroot/index.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Title 6 | 7 | 8 | 9 | 47 | 48 | -------------------------------------------------------------------------------- /6.Recepies/Collaboration/AuthenticationHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Security.Claims; 2 | using System.Text.Encodings.Web; 3 | using System.Threading.Tasks; 4 | using Microsoft.AspNetCore.Authentication; 5 | using Microsoft.Extensions.Logging; 6 | using Microsoft.Extensions.Options; 7 | 8 | namespace Collaboration 9 | { 10 | public class AuthenticationHandler : AuthenticationHandler 11 | { 12 | public AuthenticationHandler( 13 | IOptionsMonitor options, 14 | ILoggerFactory logger, 15 | UrlEncoder encoder, 16 | ISystemClock clock) 17 | : base(options, logger, encoder, clock) 18 | { 19 | } 20 | 21 | protected override Task HandleAuthenticateAsync() 22 | { 23 | if (!Context.Request.Query.ContainsKey("user") 24 | || !Context.Request.Query.ContainsKey("color")) 25 | { 26 | return Task.FromResult(AuthenticateResult.Fail("bad params")); 27 | } 28 | 29 | var claim = new Claim("userid", Context.Request.Query["user"]); 30 | var colorClaim = new Claim("color", Context.Request.Query["color"]); 31 | var identity = new ClaimsIdentity(new[] { claim, colorClaim }, "MyScheme"); 32 | var principal = new ClaimsPrincipal(identity); 33 | var ticket = new AuthenticationTicket(principal, "MyScheme"); 34 | return Task.FromResult(AuthenticateResult.Success(ticket)); 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /6.Recepies/Collaboration/Collaboration.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net5.0 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /6.Recepies/Collaboration/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Hosting; 2 | using Microsoft.Extensions.Hosting; 3 | 4 | namespace Collaboration 5 | { 6 | public class Program 7 | { 8 | public static void Main(string[] args) 9 | { 10 | CreateHostBuilder(args).Build().Run(); 11 | } 12 | 13 | public static IHostBuilder CreateHostBuilder(string[] args) => 14 | Host.CreateDefaultBuilder(args) 15 | .ConfigureWebHostDefaults(webBuilder => 16 | { 17 | webBuilder.UseStartup(); 18 | }); 19 | } 20 | } -------------------------------------------------------------------------------- /6.Recepies/Collaboration/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:35027", 7 | "sslPort": 44352 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "environmentVariables": { 15 | "ASPNETCORE_ENVIRONMENT": "Development" 16 | } 17 | }, 18 | "WordDocument": { 19 | "commandName": "Project", 20 | "dotnetRunMessages": "true", 21 | "launchBrowser": true, 22 | "applicationUrl": "http://localhost:5000", 23 | "environmentVariables": { 24 | "ASPNETCORE_ENVIRONMENT": "Development" 25 | } 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /6.Recepies/Collaboration/SquareHub.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using System.Threading.Tasks; 4 | using Microsoft.AspNetCore.Authorization; 5 | using Microsoft.AspNetCore.SignalR; 6 | 7 | namespace Collaboration 8 | { 9 | [Authorize] 10 | public class SquareHub : Hub 11 | { 12 | private readonly State _state; 13 | 14 | public SquareHub(State state) 15 | { 16 | _state = state; 17 | } 18 | 19 | public IEnumerable GetSquares() => _state.Squares; 20 | 21 | public Task SendDragEvent(DragEvent dEvent) 22 | { 23 | var username = Context.User?.Claims.FirstOrDefault(x => x.Type == "userid")?.Value ?? "barry"; 24 | var colour = Context.User?.Claims.FirstOrDefault(x => x.Type == "color")?.Value ?? "#ff0000"; 25 | return Clients.Others.SendAsync("on_drag", new UserDragEvent(dEvent, username, colour)); 26 | } 27 | 28 | public async Task> EndDrag(DragEvent dEvent) 29 | { 30 | var username = Context.User?.Claims.FirstOrDefault(x => x.Type == "userid")?.Value ?? "barry"; 31 | _state.Move(dEvent.OriginalPos, dEvent.CurrentPos); 32 | await Clients.Others.SendAsync("end_drag", new { _state.Squares, Username = username }); 33 | return _state.Squares; 34 | } 35 | } 36 | 37 | public record DragEvent(int CurrentPos, int OriginalPos); 38 | 39 | public record UserDragEvent(DragEvent Drag, string Username, string Colour) 40 | : DragEvent(Drag.CurrentPos, Drag.OriginalPos); 41 | } -------------------------------------------------------------------------------- /6.Recepies/Collaboration/Startup.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Authentication; 2 | using Microsoft.AspNetCore.Builder; 3 | using Microsoft.AspNetCore.Hosting; 4 | using Microsoft.AspNetCore.SignalR; 5 | using Microsoft.Extensions.DependencyInjection; 6 | using Microsoft.Extensions.Hosting; 7 | using Notifications; 8 | 9 | namespace Collaboration 10 | { 11 | public class Startup 12 | { 13 | public void ConfigureServices(IServiceCollection services) 14 | { 15 | services.AddAuthentication("MyScheme") 16 | .AddScheme("MyScheme", o => 17 | { 18 | }); 19 | 20 | services.AddSignalR(); 21 | services.AddSingleton(); 22 | services.AddSingleton(); 23 | } 24 | 25 | public void Configure(IApplicationBuilder app, IWebHostEnvironment env) 26 | { 27 | if (env.IsDevelopment()) 28 | { 29 | app.UseDeveloperExceptionPage(); 30 | } 31 | 32 | app.UseStaticFiles(); 33 | 34 | app.UseRouting(); 35 | 36 | app.UseAuthentication(); 37 | app.UseAuthorization(); 38 | 39 | app.UseEndpoints(endpoints => 40 | { 41 | endpoints.MapHub("/square"); 42 | }); 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /6.Recepies/Collaboration/State.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | 4 | namespace Collaboration 5 | { 6 | public class State 7 | { 8 | private readonly List _squares = Enumerable.Range(1, 9).ToList(); 9 | 10 | public IEnumerable Squares => _squares; 11 | 12 | public void Move(int from, int to) 13 | { 14 | if (from == to) 15 | return; 16 | 17 | var n = _squares[from]; 18 | _squares.RemoveAt(from); 19 | if (_squares.Count <= to) 20 | { 21 | _squares.Add(n); 22 | return; 23 | } 24 | 25 | _squares.Insert(to, n); 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /6.Recepies/Collaboration/UserIdProvider.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using Microsoft.AspNetCore.SignalR; 3 | 4 | namespace Notifications 5 | { 6 | public class UserIdProvider : IUserIdProvider 7 | { 8 | public string? GetUserId(HubConnectionContext connection) 9 | { 10 | return connection.User.Claims.FirstOrDefault(x => x.Type == "userid").Value; 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /6.Recepies/Collaboration/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /6.Recepies/Collaboration/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | }, 9 | "AllowedHosts": "*" 10 | } 11 | -------------------------------------------------------------------------------- /6.Recepies/Collaboration/wwwroot/bad_v_good.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raw-coding-youtube/aspnetcore-signalr/eaa47b1ada280a3b81b1485f444001d5ec23ba53/6.Recepies/Collaboration/wwwroot/bad_v_good.png -------------------------------------------------------------------------------- /6.Recepies/Collaboration/wwwroot/index.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Title 6 | 7 | 43 | 44 | 45 |
46 |
47 | 48 | 192 | 193 | -------------------------------------------------------------------------------- /6.Recepies/DrawingGame/AuthenticationHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Security.Claims; 3 | using System.Text.Encodings.Web; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Authentication; 6 | using Microsoft.AspNetCore.Authentication.Cookies; 7 | using Microsoft.Extensions.Logging; 8 | using Microsoft.Extensions.Options; 9 | 10 | namespace DrawingGame 11 | { 12 | public class AuthenticationHandler : CookieAuthenticationHandler 13 | { 14 | public AuthenticationHandler( 15 | IOptionsMonitor options, 16 | ILoggerFactory logger, 17 | UrlEncoder encoder, 18 | ISystemClock clock 19 | ) : base(options, logger, encoder, clock) 20 | { 21 | } 22 | 23 | protected override async Task HandleAuthenticateAsync() 24 | { 25 | var result = await base.HandleAuthenticateAsync(); 26 | if (result.Succeeded) 27 | { 28 | return result; 29 | } 30 | 31 | var guid = Guid.NewGuid().ToString(); 32 | var claim = new Claim("UserId", guid); 33 | 34 | ClaimsIdentity identity = new ClaimsIdentity(new[] { claim }, "Default"); 35 | ClaimsPrincipal claimsPrincipal = new ClaimsPrincipal(identity); 36 | await Context.SignInAsync("Default", claimsPrincipal); 37 | AuthenticationTicket ticket = new AuthenticationTicket(claimsPrincipal, "Default"); 38 | return AuthenticateResult.Success(ticket); 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /6.Recepies/DrawingGame/DrawingGame.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net5.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /6.Recepies/DrawingGame/GameHub.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Threading.Tasks; 4 | using Microsoft.AspNetCore.SignalR; 5 | 6 | namespace DrawingGame 7 | { 8 | public interface IGameClient 9 | { 10 | Task UpdateRooms(); 11 | Task Draw(DrawEvent e); 12 | Task ClearCanvas(); 13 | } 14 | 15 | public record RoomRequest(string Room); 16 | 17 | public class GameHub : Hub 18 | { 19 | private readonly RoomManager _manager; 20 | 21 | public GameHub(RoomManager manager) 22 | { 23 | _manager = manager; 24 | } 25 | 26 | public async Task Create() 27 | { 28 | var room = new Room { Id = Guid.NewGuid().ToString() }; 29 | room.Users.Add(Context.UserIdentifier); 30 | _manager.Rooms.Add(room); 31 | 32 | await Groups.AddToGroupAsync(Context.ConnectionId, room.Id); 33 | 34 | await Clients.All.UpdateRooms(); 35 | 36 | return new(room, Context.UserIdentifier); 37 | } 38 | 39 | public async Task Join(RoomRequest request) 40 | { 41 | var room = _manager.Rooms.FirstOrDefault(x => x.Id == request.Room); 42 | if (room == null) 43 | { 44 | return null; 45 | } 46 | 47 | var userId = Context.UserIdentifier; 48 | 49 | if (room.Users.All(x => x != userId)) 50 | { 51 | room.Users.Add(userId); 52 | } 53 | 54 | await Groups.AddToGroupAsync(Context.ConnectionId, room.Id); 55 | 56 | return new(room, userId); 57 | } 58 | 59 | public async Task Leave() 60 | { 61 | var room = _manager.GetRoomByUserId(Context.UserIdentifier); 62 | 63 | if (room != null) 64 | { 65 | room.Users.Remove(Context.UserIdentifier); 66 | await Groups.RemoveFromGroupAsync(Context.ConnectionId, room.Id); 67 | return true; 68 | } 69 | 70 | return false; 71 | } 72 | 73 | public Task Draw(DrawEvent drawEvent) 74 | { 75 | var room = _manager.GetRoomByUserId(Context.UserIdentifier); 76 | room.DrawEvents.Add(drawEvent); 77 | return Clients.GroupExcept(room.Id, new[] { Context.ConnectionId }).Draw(drawEvent); 78 | } 79 | 80 | public Task ClearCanvas() 81 | { 82 | var room = _manager.GetRoomByUserId(Context.UserIdentifier); 83 | room.DrawEvents.Clear(); 84 | return Clients.GroupExcept(room.Id, new[] { Context.ConnectionId }).ClearCanvas(); 85 | } 86 | 87 | public async Task ReDraw() 88 | { 89 | var room = _manager.GetRoomByUserId(Context.UserIdentifier); 90 | foreach (var drawEvent in room.DrawEvents) 91 | { 92 | await Clients.Caller.Draw(drawEvent); 93 | } 94 | } 95 | } 96 | } -------------------------------------------------------------------------------- /6.Recepies/DrawingGame/HttpContextExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using Microsoft.AspNetCore.Http; 3 | 4 | namespace DrawingGame 5 | { 6 | public static class HttpContextExtensions 7 | { 8 | public static string UserId(this HttpContext ctx) 9 | { 10 | return ctx.User.Claims.FirstOrDefault(x => x.Type == "UserId").Value; 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /6.Recepies/DrawingGame/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Hosting; 2 | using Microsoft.Extensions.Hosting; 3 | 4 | namespace DrawingGame 5 | { 6 | public class Program 7 | { 8 | public static void Main(string[] args) 9 | { 10 | CreateHostBuilder(args).Build().Run(); 11 | } 12 | 13 | public static IHostBuilder CreateHostBuilder(string[] args) => 14 | Host.CreateDefaultBuilder(args) 15 | .ConfigureWebHostDefaults(webBuilder => 16 | { 17 | webBuilder.UseStartup(); 18 | }); 19 | } 20 | } -------------------------------------------------------------------------------- /6.Recepies/DrawingGame/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:24866", 7 | "sslPort": 44336 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "environmentVariables": { 14 | "ASPNETCORE_ENVIRONMENT": "Development" 15 | } 16 | }, 17 | "DrawingGame": { 18 | "commandName": "Project", 19 | "dotnetRunMessages": "true", 20 | "launchBrowser": true, 21 | "applicationUrl": "http://localhost:5000", 22 | "environmentVariables": { 23 | "ASPNETCORE_ENVIRONMENT": "Development" 24 | } 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /6.Recepies/DrawingGame/RoomController.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using Microsoft.AspNetCore.Authorization; 3 | using Microsoft.AspNetCore.Mvc; 4 | 5 | namespace DrawingGame 6 | { 7 | [ApiController] 8 | [Authorize] 9 | [Route("/api/rooms")] 10 | public class RoomController : ControllerBase 11 | { 12 | private readonly RoomManager _manager; 13 | 14 | public RoomController(RoomManager manager) => _manager = manager; 15 | 16 | [HttpGet("my")] 17 | public IActionResult MyRoom() 18 | { 19 | var userId = HttpContext.UserId(); 20 | 21 | var myRoom = _manager.GetRoomByUserId(userId); 22 | 23 | if (myRoom == null) 24 | return NoContent(); 25 | 26 | return Ok(new RoomView(myRoom, userId)); 27 | } 28 | 29 | public IActionResult List() 30 | { 31 | return Ok(_manager.Rooms.Select(x => new RoomView(x, HttpContext.UserId()))); 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /6.Recepies/DrawingGame/RoomManager.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | 4 | namespace DrawingGame 5 | { 6 | public class RoomManager 7 | { 8 | public List Rooms { get; } = new(); 9 | public Room GetRoomByUserId(string userId) => Rooms.FirstOrDefault(x => x.Users.Contains(userId)); 10 | } 11 | 12 | public class Room 13 | { 14 | public string Id { get; set; } 15 | public List Users { get; } = new(); 16 | public List DrawEvents { get; } = new(); 17 | public string Admin => Users.FirstOrDefault(); 18 | public string DrawingUser { get; set; } 19 | } 20 | 21 | public record DrawEvent(int FromY, int FromX, int ToX, int ToY, string Color, int Size); 22 | } -------------------------------------------------------------------------------- /6.Recepies/DrawingGame/RoomView.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace DrawingGame 4 | { 5 | public class RoomView 6 | { 7 | public RoomView(Room room, string userId) 8 | { 9 | Id = room.Id; 10 | Users = room.Users; 11 | IsAdmin = userId == room.Admin; 12 | DrawingUser = room.DrawingUser; 13 | MyUserId = userId; 14 | } 15 | 16 | public string Id { get; set; } 17 | public List Users { get; set; } 18 | public bool IsAdmin { get; set; } 19 | public string DrawingUser { get; set; } 20 | public string MyUserId { get; set; } 21 | } 22 | } -------------------------------------------------------------------------------- /6.Recepies/DrawingGame/Startup.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Authentication.Cookies; 2 | using Microsoft.AspNetCore.Builder; 3 | using Microsoft.AspNetCore.Hosting; 4 | using Microsoft.AspNetCore.SignalR; 5 | using Microsoft.Extensions.DependencyInjection; 6 | using Microsoft.Extensions.DependencyInjection.Extensions; 7 | using Microsoft.Extensions.Hosting; 8 | using Microsoft.Extensions.Options; 9 | 10 | namespace DrawingGame 11 | { 12 | public class Startup 13 | { 14 | public void ConfigureServices(IServiceCollection services) 15 | { 16 | services.TryAddEnumerable(ServiceDescriptor.Singleton, PostConfigureCookieAuthenticationOptions>()); 17 | services.AddAuthentication("Default") 18 | .AddScheme("Default", options => 19 | { 20 | options.Cookie.HttpOnly = false; 21 | options.Cookie.Name = "AuthCookie"; 22 | }); 23 | 24 | services.AddControllers(); 25 | services.AddSignalR(); 26 | 27 | services.AddSingleton(); 28 | services.AddSingleton(); 29 | } 30 | 31 | public void Configure(IApplicationBuilder app, IWebHostEnvironment env) 32 | { 33 | if (env.IsDevelopment()) 34 | { 35 | app.UseDeveloperExceptionPage(); 36 | } 37 | 38 | app.UseRouting(); 39 | 40 | app.UseAuthentication(); 41 | app.UseAuthorization(); 42 | 43 | app.UseEndpoints(endpoints => 44 | { 45 | endpoints.MapDefaultControllerRoute(); 46 | endpoints.MapHub("/game"); 47 | }); 48 | 49 | app.UseSpa(builder => 50 | { 51 | if (env.IsDevelopment()) 52 | { 53 | builder.UseProxyToSpaDevelopmentServer("http://localhost:3000/"); 54 | } 55 | }); 56 | } 57 | } 58 | } -------------------------------------------------------------------------------- /6.Recepies/DrawingGame/UserIdProvider.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using Microsoft.AspNetCore.SignalR; 3 | 4 | namespace DrawingGame 5 | { 6 | public class UserIdProvider : IUserIdProvider 7 | { 8 | public string GetUserId(HubConnectionContext connection) 9 | { 10 | return connection?.User?.Claims.FirstOrDefault(x => x.Type == "UserId")?.Value; 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /6.Recepies/DrawingGame/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /6.Recepies/DrawingGame/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | }, 9 | "AllowedHosts": "*" 10 | } 11 | -------------------------------------------------------------------------------- /6.Recepies/DrawingGame/vue-client/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | dist 4 | dist-ssr 5 | *.local -------------------------------------------------------------------------------- /6.Recepies/DrawingGame/vue-client/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["johnsoncodehk.volar"] 3 | } 4 | -------------------------------------------------------------------------------- /6.Recepies/DrawingGame/vue-client/README.md: -------------------------------------------------------------------------------- 1 | # Vue 3 + Vite 2 | 3 | This template should help get you started developing with Vue 3 in Vite. The template uses Vue 3 ` 12 | 13 | 14 | -------------------------------------------------------------------------------- /6.Recepies/DrawingGame/vue-client/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-client", 3 | "version": "0.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@babel/parser": { 8 | "version": "7.15.8", 9 | "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.15.8.tgz", 10 | "integrity": "sha512-BRYa3wcQnjS/nqI8Ac94pYYpJfojHVvVXJ97+IDCImX4Jc8W8Xv1+47enbruk+q1etOpsQNwnfFcNGw+gtPGxA==" 11 | }, 12 | "@microsoft/signalr": { 13 | "version": "5.0.11", 14 | "resolved": "https://registry.npmjs.org/@microsoft/signalr/-/signalr-5.0.11.tgz", 15 | "integrity": "sha512-sek3lNroiutZ6XAb2UxwBkAgLWIXS7DQpRU5zKtus63xyZ2UUKQvCyT8US++1+UG3HSkSfEgaMa+o1xQfmuGGg==", 16 | "requires": { 17 | "abort-controller": "^3.0.0", 18 | "eventsource": "^1.0.7", 19 | "fetch-cookie": "^0.7.3", 20 | "node-fetch": "^2.6.0", 21 | "ws": "^6.0.0" 22 | } 23 | }, 24 | "@vitejs/plugin-vue": { 25 | "version": "1.9.3", 26 | "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-1.9.3.tgz", 27 | "integrity": "sha512-yW6H/q+4Mc2PcVjSOelcsMrg/k15DnMUz8jyCFsI04emc3aLwo4AoofUfGnjHUkgirrDxSJLVqQVGhonQ3yykA==", 28 | "dev": true 29 | }, 30 | "@vue/compiler-core": { 31 | "version": "3.2.20", 32 | "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.2.20.tgz", 33 | "integrity": "sha512-vcEXlKXoPwBXFP5aUTHN9GTZaDfwCofa9Yu9bbW2C5O/QSa9Esdt7OG4+0RRd3EHEMxUvEdj4RZrd/KpQeiJbA==", 34 | "requires": { 35 | "@babel/parser": "^7.15.0", 36 | "@vue/shared": "3.2.20", 37 | "estree-walker": "^2.0.2", 38 | "source-map": "^0.6.1" 39 | } 40 | }, 41 | "@vue/compiler-dom": { 42 | "version": "3.2.20", 43 | "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.2.20.tgz", 44 | "integrity": "sha512-QnI77ec/JtV7R0YBbcVayYTDCRcI9OCbxiUQK6izVyqQO0658n0zQuoNwe+bYgtqnvGAIqTR3FShTd5y4oOjdg==", 45 | "requires": { 46 | "@vue/compiler-core": "3.2.20", 47 | "@vue/shared": "3.2.20" 48 | } 49 | }, 50 | "@vue/compiler-sfc": { 51 | "version": "3.2.20", 52 | "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.2.20.tgz", 53 | "integrity": "sha512-03aZo+6tQKiFLfunHKSPZvdK4Jsn/ftRCyaro8AQIWkuxJbvSosbKK6HTTn+D2c3nPScG155akJoxKENw7rftQ==", 54 | "requires": { 55 | "@babel/parser": "^7.15.0", 56 | "@vue/compiler-core": "3.2.20", 57 | "@vue/compiler-dom": "3.2.20", 58 | "@vue/compiler-ssr": "3.2.20", 59 | "@vue/ref-transform": "3.2.20", 60 | "@vue/shared": "3.2.20", 61 | "estree-walker": "^2.0.2", 62 | "magic-string": "^0.25.7", 63 | "postcss": "^8.1.10", 64 | "source-map": "^0.6.1" 65 | } 66 | }, 67 | "@vue/compiler-ssr": { 68 | "version": "3.2.20", 69 | "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.2.20.tgz", 70 | "integrity": "sha512-rzzVVYivm+EjbfiGQvNeyiYZWzr6Hkej97RZLZvcumacQlnKv9176Xo9rRyeWwFbBlxmtNdrVMslRXtipMXk2w==", 71 | "requires": { 72 | "@vue/compiler-dom": "3.2.20", 73 | "@vue/shared": "3.2.20" 74 | } 75 | }, 76 | "@vue/reactivity": { 77 | "version": "3.2.20", 78 | "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.2.20.tgz", 79 | "integrity": "sha512-nSmoLojUTk+H8HNTAkrUduB4+yIUBK2HPihJo2uXVSH4Spry6oqN6lFzE5zpLK+F27Sja+UqR9R1+/kIOsHV5w==", 80 | "requires": { 81 | "@vue/shared": "3.2.20" 82 | } 83 | }, 84 | "@vue/ref-transform": { 85 | "version": "3.2.20", 86 | "resolved": "https://registry.npmjs.org/@vue/ref-transform/-/ref-transform-3.2.20.tgz", 87 | "integrity": "sha512-Y42d3PGlYZ1lXcF3dbd3+qU/C/a3wYEZ949fyOI5ptzkjDWlkfU6vn74fmOjsLjEcjs10BXK2qO99FqQIK2r1Q==", 88 | "requires": { 89 | "@babel/parser": "^7.15.0", 90 | "@vue/compiler-core": "3.2.20", 91 | "@vue/shared": "3.2.20", 92 | "estree-walker": "^2.0.2", 93 | "magic-string": "^0.25.7" 94 | } 95 | }, 96 | "@vue/runtime-core": { 97 | "version": "3.2.20", 98 | "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.2.20.tgz", 99 | "integrity": "sha512-d1xfUGhZPfiZzAN7SatStD4vRtT8deJSXib2+Cz3x0brjMWKxe32asQc154FF1E2fFgMCHtnfd4A90bQEzV4GQ==", 100 | "requires": { 101 | "@vue/reactivity": "3.2.20", 102 | "@vue/shared": "3.2.20" 103 | } 104 | }, 105 | "@vue/runtime-dom": { 106 | "version": "3.2.20", 107 | "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.2.20.tgz", 108 | "integrity": "sha512-4TCvZMLhESWCFHFYgqN4QmMA/onnINAlUovhopjlS8ST27G1A8Z0tyxPzLoXLa+b5JrOpbMPheEMPvdKExTJig==", 109 | "requires": { 110 | "@vue/runtime-core": "3.2.20", 111 | "@vue/shared": "3.2.20", 112 | "csstype": "^2.6.8" 113 | } 114 | }, 115 | "@vue/server-renderer": { 116 | "version": "3.2.20", 117 | "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.2.20.tgz", 118 | "integrity": "sha512-viIbZGep9XabnrRcaxWIi00cOh1x21QYm2upIL5W0zqzTJ54VdTzpI+zi1osNp+VfRQDTHpV2U7H3Kn4ljYJvg==", 119 | "requires": { 120 | "@vue/compiler-ssr": "3.2.20", 121 | "@vue/shared": "3.2.20" 122 | } 123 | }, 124 | "@vue/shared": { 125 | "version": "3.2.20", 126 | "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.2.20.tgz", 127 | "integrity": "sha512-FbpX+hD5BvXCQerEYO7jtAGHlhAkhTQ4KIV73kmLWNlawWhTiVuQxizgVb0BOkX5oG9cIRZ42EG++d/k/Efp0w==" 128 | }, 129 | "abort-controller": { 130 | "version": "3.0.0", 131 | "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", 132 | "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", 133 | "requires": { 134 | "event-target-shim": "^5.0.0" 135 | } 136 | }, 137 | "async-limiter": { 138 | "version": "1.0.1", 139 | "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", 140 | "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==" 141 | }, 142 | "csstype": { 143 | "version": "2.6.18", 144 | "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.18.tgz", 145 | "integrity": "sha512-RSU6Hyeg14am3Ah4VZEmeX8H7kLwEEirXe6aU2IPfKNvhXwTflK5HQRDNI0ypQXoqmm+QPyG2IaPuQE5zMwSIQ==" 146 | }, 147 | "es6-denodeify": { 148 | "version": "0.1.5", 149 | "resolved": "https://registry.npmjs.org/es6-denodeify/-/es6-denodeify-0.1.5.tgz", 150 | "integrity": "sha1-MdTV/pxVA+ElRgQ5MQ4WoqPznB8=" 151 | }, 152 | "esbuild": { 153 | "version": "0.13.5", 154 | "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.13.5.tgz", 155 | "integrity": "sha512-Q9/f1njsZaO+Qqe3dqAdtu4zGHNZIbcEtdg44/NooyPhqCerns4FeC1UPYeB4pKD08iDuWcmyINFJTqpdN+pqg==", 156 | "dev": true, 157 | "requires": { 158 | "esbuild-android-arm64": "0.13.5", 159 | "esbuild-darwin-64": "0.13.5", 160 | "esbuild-darwin-arm64": "0.13.5", 161 | "esbuild-freebsd-64": "0.13.5", 162 | "esbuild-freebsd-arm64": "0.13.5", 163 | "esbuild-linux-32": "0.13.5", 164 | "esbuild-linux-64": "0.13.5", 165 | "esbuild-linux-arm": "0.13.5", 166 | "esbuild-linux-arm64": "0.13.5", 167 | "esbuild-linux-mips64le": "0.13.5", 168 | "esbuild-linux-ppc64le": "0.13.5", 169 | "esbuild-openbsd-64": "0.13.5", 170 | "esbuild-sunos-64": "0.13.5", 171 | "esbuild-windows-32": "0.13.5", 172 | "esbuild-windows-64": "0.13.5", 173 | "esbuild-windows-arm64": "0.13.5" 174 | } 175 | }, 176 | "esbuild-android-arm64": { 177 | "version": "0.13.5", 178 | "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.13.5.tgz", 179 | "integrity": "sha512-xaNH58b9XRAWT5q0rwA2GNTgJynb51JhdotlNKdLmSCyKXPVlF87yqNLNdmlX/zndzRDrZdtpCWSALdn/J63Ug==", 180 | "dev": true, 181 | "optional": true 182 | }, 183 | "esbuild-darwin-64": { 184 | "version": "0.13.5", 185 | "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.13.5.tgz", 186 | "integrity": "sha512-ClGQeUObXIxEpZviGzjTinDikXy9XodojP9jLKwqLCBpZ9wdV3MW7JOmw60fgXgnbNRvkZCqM6uEi+ur8p80Ow==", 187 | "dev": true, 188 | "optional": true 189 | }, 190 | "esbuild-darwin-arm64": { 191 | "version": "0.13.5", 192 | "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.13.5.tgz", 193 | "integrity": "sha512-qro6M/qzs1dBPh14Ca+5moIkLo2KE3ll3dOpiN7aAususkM1HmqQptCEchi0XwX+6nfqWI96YvVqPJ3DfUUK5A==", 194 | "dev": true, 195 | "optional": true 196 | }, 197 | "esbuild-freebsd-64": { 198 | "version": "0.13.5", 199 | "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.13.5.tgz", 200 | "integrity": "sha512-vklf7L7fghREEvS1sjAFcxcw/Qqt+Z+L0ySN+pEeb7rA8nPLfRBSFdXAru8UNuHsMWns6CrcZ5eDOKTerZZ5ng==", 201 | "dev": true, 202 | "optional": true 203 | }, 204 | "esbuild-freebsd-arm64": { 205 | "version": "0.13.5", 206 | "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.13.5.tgz", 207 | "integrity": "sha512-kJoouhbZt4QvjiPak7/Lz57Azok0CgFnNtixiOsqEQXTabIaKmMmnq4qgjD6EBFeU/hvSXDrPe6U8dWhBZOrWQ==", 208 | "dev": true, 209 | "optional": true 210 | }, 211 | "esbuild-linux-32": { 212 | "version": "0.13.5", 213 | "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.13.5.tgz", 214 | "integrity": "sha512-/QufG6tTGKAf42pIYkOVZzKBPxF01xH1kCPyOFJZukZBV/Tk3TeOZfhJIAf7pxl4jhfa+c4Jcdp7CvIAjXrmJg==", 215 | "dev": true, 216 | "optional": true 217 | }, 218 | "esbuild-linux-64": { 219 | "version": "0.13.5", 220 | "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.13.5.tgz", 221 | "integrity": "sha512-NmNFMXEthuFJTFaD4cLhAHCxg+y3uXzo7nqH/WNNSZ8PPY11jbeOvMbdArYlbo2Wy1N/mTHXMcK1synSJj+4Iw==", 222 | "dev": true, 223 | "optional": true 224 | }, 225 | "esbuild-linux-arm": { 226 | "version": "0.13.5", 227 | "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.13.5.tgz", 228 | "integrity": "sha512-69nQmbKLBRaAxf88diyaOyarrI7yIdBkZ8bmVzQ7XVWneY+nYIcGtugTSOs5znNGfPqGOElAjh1lX+0sGYHNpA==", 229 | "dev": true, 230 | "optional": true 231 | }, 232 | "esbuild-linux-arm64": { 233 | "version": "0.13.5", 234 | "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.13.5.tgz", 235 | "integrity": "sha512-dOS5EZsZj8Lw0TgEj3zy1/slTBbfBw4v7uHEqZXP34dUaRq2oltNaUYIj735CtgB7I5/MXrXEUYkXLqcVfzJQQ==", 236 | "dev": true, 237 | "optional": true 238 | }, 239 | "esbuild-linux-mips64le": { 240 | "version": "0.13.5", 241 | "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.13.5.tgz", 242 | "integrity": "sha512-dmKA8ZI/nHwpxIQW/L5crk7Ac4wJJ2Kquvdo1CdXPW1UljMyKUDuHc4K7D1Iws5igqJmNO6U5vdRUKrdnIov6Q==", 243 | "dev": true, 244 | "optional": true 245 | }, 246 | "esbuild-linux-ppc64le": { 247 | "version": "0.13.5", 248 | "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.13.5.tgz", 249 | "integrity": "sha512-HkVGKkPL3XOhJqNOJ752Q1li5zeidrJHv+XWX6qCnCipNsVuGqaAGfxeWbL/+A/giolMlP7wvAuiKgoe+a5UAw==", 250 | "dev": true, 251 | "optional": true 252 | }, 253 | "esbuild-openbsd-64": { 254 | "version": "0.13.5", 255 | "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.13.5.tgz", 256 | "integrity": "sha512-BuOZzmdsdreSs0qDgbuiEhSbUDDW2Wyp4VtpNGBmaLwPMHftdprOJXLkeFud3HlnRB2n9qdiTVKg1B8YqMogSw==", 257 | "dev": true, 258 | "optional": true 259 | }, 260 | "esbuild-sunos-64": { 261 | "version": "0.13.5", 262 | "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.13.5.tgz", 263 | "integrity": "sha512-YJNB6Og1QYAPikvYDbqvk5xCqr6WL2i5cRWPGGgWOEItQPnq6gFsWogS3DiYM8TQKe50KRiD3Lwu7eNYsdPO4w==", 264 | "dev": true, 265 | "optional": true 266 | }, 267 | "esbuild-windows-32": { 268 | "version": "0.13.5", 269 | "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.13.5.tgz", 270 | "integrity": "sha512-CigOlBSKsZ61IS+FyhD3luqCpl7LN9ntDaBZXumls/0IZ/8BJ5txqw4a6pv4LtnfIgt0ixGHSH7kAUmApw/HAw==", 271 | "dev": true, 272 | "optional": true 273 | }, 274 | "esbuild-windows-64": { 275 | "version": "0.13.5", 276 | "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.13.5.tgz", 277 | "integrity": "sha512-pg2BZXLpcPcrIcmToGapLRExzj6sm0VmQlqlmnMOtIJh0YQV9c0CRbhfIT0gYvJqCz5JEGiRvYpArRlxWADN3Q==", 278 | "dev": true, 279 | "optional": true 280 | }, 281 | "esbuild-windows-arm64": { 282 | "version": "0.13.5", 283 | "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.13.5.tgz", 284 | "integrity": "sha512-KKRDmUOIE4oCvJp0I4p4QyazK2X79spF29vsZr2U8qHhmxbTLSQWvYmb2WlF5Clb1URRsX0L013rhwHx1SEu0w==", 285 | "dev": true, 286 | "optional": true 287 | }, 288 | "estree-walker": { 289 | "version": "2.0.2", 290 | "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", 291 | "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==" 292 | }, 293 | "event-target-shim": { 294 | "version": "5.0.1", 295 | "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", 296 | "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==" 297 | }, 298 | "eventsource": { 299 | "version": "1.1.0", 300 | "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-1.1.0.tgz", 301 | "integrity": "sha512-VSJjT5oCNrFvCS6igjzPAt5hBzQ2qPBFIbJ03zLI9SE0mxwZpMw6BfJrbFHm1a141AavMEB8JHmBhWAd66PfCg==", 302 | "requires": { 303 | "original": "^1.0.0" 304 | } 305 | }, 306 | "fetch-cookie": { 307 | "version": "0.7.3", 308 | "resolved": "https://registry.npmjs.org/fetch-cookie/-/fetch-cookie-0.7.3.tgz", 309 | "integrity": "sha512-rZPkLnI8x5V+zYAiz8QonAHsTb4BY+iFowFBI1RFn0zrO343AVp9X7/yUj/9wL6Ef/8fLls8b/vGtzUvmyAUGA==", 310 | "requires": { 311 | "es6-denodeify": "^0.1.1", 312 | "tough-cookie": "^2.3.3" 313 | } 314 | }, 315 | "fsevents": { 316 | "version": "2.3.2", 317 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", 318 | "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", 319 | "dev": true, 320 | "optional": true 321 | }, 322 | "function-bind": { 323 | "version": "1.1.1", 324 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", 325 | "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", 326 | "dev": true 327 | }, 328 | "has": { 329 | "version": "1.0.3", 330 | "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", 331 | "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", 332 | "dev": true, 333 | "requires": { 334 | "function-bind": "^1.1.1" 335 | } 336 | }, 337 | "is-core-module": { 338 | "version": "2.7.0", 339 | "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.7.0.tgz", 340 | "integrity": "sha512-ByY+tjCciCr+9nLryBYcSD50EOGWt95c7tIsKTG1J2ixKKXPvF7Ej3AVd+UfDydAJom3biBGDBALaO79ktwgEQ==", 341 | "dev": true, 342 | "requires": { 343 | "has": "^1.0.3" 344 | } 345 | }, 346 | "magic-string": { 347 | "version": "0.25.7", 348 | "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.7.tgz", 349 | "integrity": "sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA==", 350 | "requires": { 351 | "sourcemap-codec": "^1.4.4" 352 | } 353 | }, 354 | "nanoid": { 355 | "version": "3.1.29", 356 | "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.29.tgz", 357 | "integrity": "sha512-dW2pUSGZ8ZnCFIlBIA31SV8huOGCHb6OwzVCc7A69rb/a+SgPBwfmLvK5TKQ3INPbRkcI8a/Owo0XbiTNH19wg==" 358 | }, 359 | "node-fetch": { 360 | "version": "2.6.5", 361 | "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.5.tgz", 362 | "integrity": "sha512-mmlIVHJEu5rnIxgEgez6b9GgWXbkZj5YZ7fx+2r94a2E+Uirsp6HsPTPlomfdHtpt/B0cdKviwkoaM6pyvUOpQ==", 363 | "requires": { 364 | "whatwg-url": "^5.0.0" 365 | } 366 | }, 367 | "original": { 368 | "version": "1.0.2", 369 | "resolved": "https://registry.npmjs.org/original/-/original-1.0.2.tgz", 370 | "integrity": "sha512-hyBVl6iqqUOJ8FqRe+l/gS8H+kKYjrEndd5Pm1MfBtsEKA038HkkdbAl/72EAXGyonD/PFsvmVG+EvcIpliMBg==", 371 | "requires": { 372 | "url-parse": "^1.4.3" 373 | } 374 | }, 375 | "path-parse": { 376 | "version": "1.0.7", 377 | "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", 378 | "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", 379 | "dev": true 380 | }, 381 | "picocolors": { 382 | "version": "0.2.1", 383 | "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", 384 | "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==" 385 | }, 386 | "postcss": { 387 | "version": "8.3.9", 388 | "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.3.9.tgz", 389 | "integrity": "sha512-f/ZFyAKh9Dnqytx5X62jgjhhzttjZS7hMsohcI7HEI5tjELX/HxCy3EFhsRxyzGvrzFF+82XPvCS8T9TFleVJw==", 390 | "requires": { 391 | "nanoid": "^3.1.28", 392 | "picocolors": "^0.2.1", 393 | "source-map-js": "^0.6.2" 394 | } 395 | }, 396 | "psl": { 397 | "version": "1.8.0", 398 | "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", 399 | "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==" 400 | }, 401 | "punycode": { 402 | "version": "2.1.1", 403 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", 404 | "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" 405 | }, 406 | "querystringify": { 407 | "version": "2.2.0", 408 | "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", 409 | "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==" 410 | }, 411 | "requires-port": { 412 | "version": "1.0.0", 413 | "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", 414 | "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=" 415 | }, 416 | "resolve": { 417 | "version": "1.20.0", 418 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", 419 | "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", 420 | "dev": true, 421 | "requires": { 422 | "is-core-module": "^2.2.0", 423 | "path-parse": "^1.0.6" 424 | } 425 | }, 426 | "rollup": { 427 | "version": "2.58.0", 428 | "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.58.0.tgz", 429 | "integrity": "sha512-NOXpusKnaRpbS7ZVSzcEXqxcLDOagN6iFS8p45RkoiMqPHDLwJm758UF05KlMoCRbLBTZsPOIa887gZJ1AiXvw==", 430 | "dev": true, 431 | "requires": { 432 | "fsevents": "~2.3.2" 433 | } 434 | }, 435 | "source-map": { 436 | "version": "0.6.1", 437 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", 438 | "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" 439 | }, 440 | "source-map-js": { 441 | "version": "0.6.2", 442 | "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-0.6.2.tgz", 443 | "integrity": "sha512-/3GptzWzu0+0MBQFrDKzw/DvvMTUORvgY6k6jd/VS6iCR4RDTKWH6v6WPwQoUO8667uQEf9Oe38DxAYWY5F/Ug==" 444 | }, 445 | "sourcemap-codec": { 446 | "version": "1.4.8", 447 | "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", 448 | "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==" 449 | }, 450 | "tough-cookie": { 451 | "version": "2.5.0", 452 | "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", 453 | "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", 454 | "requires": { 455 | "psl": "^1.1.28", 456 | "punycode": "^2.1.1" 457 | } 458 | }, 459 | "tr46": { 460 | "version": "0.0.3", 461 | "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", 462 | "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=" 463 | }, 464 | "url-parse": { 465 | "version": "1.5.3", 466 | "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.3.tgz", 467 | "integrity": "sha512-IIORyIQD9rvj0A4CLWsHkBBJuNqWpFQe224b6j9t/ABmquIS0qDU2pY6kl6AuOrL5OkCXHMCFNe1jBcuAggjvQ==", 468 | "requires": { 469 | "querystringify": "^2.1.1", 470 | "requires-port": "^1.0.0" 471 | } 472 | }, 473 | "vite": { 474 | "version": "2.6.7", 475 | "resolved": "https://registry.npmjs.org/vite/-/vite-2.6.7.tgz", 476 | "integrity": "sha512-ewk//jve9k6vlU8PfJmWUHN8k0YYdw4VaKOMvoQ3nT2Pb6k5OSMKQi4jPOzVH/TlUqMsCrq7IJ80xcuDDVyigg==", 477 | "dev": true, 478 | "requires": { 479 | "esbuild": "^0.13.2", 480 | "fsevents": "~2.3.2", 481 | "postcss": "^8.3.8", 482 | "resolve": "^1.20.0", 483 | "rollup": "^2.57.0" 484 | } 485 | }, 486 | "vue": { 487 | "version": "3.2.20", 488 | "resolved": "https://registry.npmjs.org/vue/-/vue-3.2.20.tgz", 489 | "integrity": "sha512-81JjEP4OGk9oO8+CU0h2nFPGgJBm9mNa3kdCX2k6FuRdrWrC+CNe+tOnuIeTg8EWwQuI+wwdra5Q7vSzp7p4Iw==", 490 | "requires": { 491 | "@vue/compiler-dom": "3.2.20", 492 | "@vue/compiler-sfc": "3.2.20", 493 | "@vue/runtime-dom": "3.2.20", 494 | "@vue/server-renderer": "3.2.20", 495 | "@vue/shared": "3.2.20" 496 | } 497 | }, 498 | "webidl-conversions": { 499 | "version": "3.0.1", 500 | "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", 501 | "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=" 502 | }, 503 | "whatwg-url": { 504 | "version": "5.0.0", 505 | "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", 506 | "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", 507 | "requires": { 508 | "tr46": "~0.0.3", 509 | "webidl-conversions": "^3.0.0" 510 | } 511 | }, 512 | "ws": { 513 | "version": "6.2.2", 514 | "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.2.tgz", 515 | "integrity": "sha512-zmhltoSR8u1cnDsD43TX59mzoMZsLKqUweyYBAIvTngR3shc0W6aOZylZmq/7hqyVxPdi+5Ud2QInblgyE72fw==", 516 | "requires": { 517 | "async-limiter": "~1.0.0" 518 | } 519 | } 520 | } 521 | } 522 | -------------------------------------------------------------------------------- /6.Recepies/DrawingGame/vue-client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-client", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "dev": "vite", 6 | "build": "vite build", 7 | "serve": "vite preview" 8 | }, 9 | "dependencies": { 10 | "@microsoft/signalr": "^5.0.11", 11 | "vue": "^3.2.16" 12 | }, 13 | "devDependencies": { 14 | "@vitejs/plugin-vue": "^1.9.3", 15 | "vite": "^2.6.4" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /6.Recepies/DrawingGame/vue-client/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raw-coding-youtube/aspnetcore-signalr/eaa47b1ada280a3b81b1485f444001d5ec23ba53/6.Recepies/DrawingGame/vue-client/public/favicon.ico -------------------------------------------------------------------------------- /6.Recepies/DrawingGame/vue-client/src/App.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | 42 | 43 | 48 | -------------------------------------------------------------------------------- /6.Recepies/DrawingGame/vue-client/src/Canvas.vue: -------------------------------------------------------------------------------- 1 |  17 | 18 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /6.Recepies/DrawingGame/vue-client/src/Rooms.vue: -------------------------------------------------------------------------------- 1 |  38 | 39 | 46 | 47 | -------------------------------------------------------------------------------- /6.Recepies/DrawingGame/vue-client/src/ToolBar.vue: -------------------------------------------------------------------------------- 1 |  12 | 13 | 28 | 29 | -------------------------------------------------------------------------------- /6.Recepies/DrawingGame/vue-client/src/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue' 2 | import App from './App.vue' 3 | import gameConnection from "../gameConnection"; 4 | 5 | gameConnection() 6 | createApp(App).mount('#app') -------------------------------------------------------------------------------- /6.Recepies/DrawingGame/vue-client/vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import vue from '@vitejs/plugin-vue' 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [vue()] 7 | }) 8 | -------------------------------------------------------------------------------- /6.Recepies/Notifications/AuthenticationHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Security.Claims; 2 | using System.Text.Encodings.Web; 3 | using System.Threading.Tasks; 4 | using Microsoft.AspNetCore.Authentication; 5 | using Microsoft.Extensions.Logging; 6 | using Microsoft.Extensions.Options; 7 | 8 | namespace Notifications 9 | { 10 | public class AuthenticationHandler : AuthenticationHandler 11 | { 12 | public AuthenticationHandler( 13 | IOptionsMonitor options, 14 | ILoggerFactory logger, 15 | UrlEncoder encoder, 16 | ISystemClock clock) 17 | : base(options, logger, encoder, clock) 18 | { 19 | } 20 | 21 | protected override Task HandleAuthenticateAsync() 22 | { 23 | if (!Context.Request.Query.TryGetValue("user", out var userId)) 24 | return Task.FromResult(AuthenticateResult.Fail("no user id in query string")); 25 | 26 | 27 | Logger.LogInformation($"logged in as {userId}"); 28 | var claim = new Claim("userid", userId); 29 | var identity = new ClaimsIdentity(new[] { claim }, "MyScheme"); 30 | var principal = new ClaimsPrincipal(identity); 31 | var ticket = new AuthenticationTicket(principal, "MyScheme"); 32 | return Task.FromResult(AuthenticateResult.Success(ticket)); 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /6.Recepies/Notifications/DefaultController.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Microsoft.AspNetCore.Authorization; 3 | using Microsoft.AspNetCore.Mvc; 4 | 5 | namespace Notifications 6 | { 7 | public class DefaultController : ControllerBase 8 | { 9 | private readonly INotificationSink _notificationSink; 10 | 11 | public DefaultController(INotificationSink notificationSink) => _notificationSink = notificationSink; 12 | 13 | [Authorize] 14 | [HttpGet("/notify")] 15 | public async Task Notify(string user, string message) 16 | { 17 | await _notificationSink.PushAsync(new(user, message)); 18 | return Ok(); 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /6.Recepies/Notifications/NotificationHub.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Authorization; 2 | using Microsoft.AspNetCore.SignalR; 3 | 4 | namespace Notifications 5 | { 6 | [Authorize] 7 | public class NotificationHub : Hub 8 | { 9 | 10 | } 11 | } -------------------------------------------------------------------------------- /6.Recepies/Notifications/NotificationService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text.Json; 3 | using System.Threading; 4 | using System.Threading.Channels; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNetCore.SignalR; 7 | using Microsoft.Extensions.DependencyInjection; 8 | using Microsoft.Extensions.Hosting; 9 | using Microsoft.Extensions.Logging; 10 | using StackExchange.Redis; 11 | 12 | namespace Notifications 13 | { 14 | public record Notification(string ForUserId, string Message); 15 | 16 | public interface INotificationSink 17 | { 18 | ValueTask PushAsync(Notification notification); 19 | } 20 | 21 | public class NotificationService : BackgroundService, INotificationSink 22 | { 23 | private readonly IServiceProvider _serviceProvider; 24 | private readonly ILogger _logger; 25 | private readonly ConnectionMultiplexer _redis; 26 | private readonly Channel _channel; 27 | 28 | public ValueTask PushAsync(Notification notification) => _channel.Writer.WriteAsync(notification); 29 | 30 | public NotificationService( 31 | IServiceProvider serviceProvider, 32 | ILogger logger 33 | ) 34 | { 35 | _channel = Channel.CreateUnbounded(); 36 | _serviceProvider = serviceProvider; 37 | _logger = logger; 38 | _redis = ConnectionMultiplexer.Connect("127.0.0.1"); 39 | } 40 | 41 | protected override async Task ExecuteAsync(CancellationToken stoppingToken) 42 | { 43 | // Distributed Scenario 44 | await _redis.GetSubscriber().SubscribeAsync("notification", async (c, m) => 45 | { 46 | using var scope = _serviceProvider.CreateScope(); 47 | var hub = scope.ServiceProvider.GetRequiredService>(); 48 | var (forUserId, message) = JsonSerializer.Deserialize(m); 49 | 50 | var payload = new { Message = message }; 51 | _logger.LogInformation($"Sending redis notification '{m}'"); 52 | await hub.Clients.User(forUserId).SendAsync("Notify", payload, stoppingToken); 53 | }); 54 | 55 | // Local 56 | while (true) 57 | { 58 | try 59 | { 60 | if (stoppingToken.IsCancellationRequested) 61 | { 62 | return; 63 | } 64 | 65 | var (forUserId, message) = await _channel.Reader.ReadAsync(stoppingToken); 66 | 67 | using var scope = _serviceProvider.CreateScope(); 68 | 69 | var hub = scope.ServiceProvider.GetRequiredService>(); 70 | 71 | var payload = new { Message = message }; 72 | _logger.LogInformation($"Sending channel notification '{message}' to {forUserId}"); 73 | await hub.Clients.User(forUserId).SendAsync("Notify", payload, stoppingToken); 74 | } 75 | catch (Exception e) 76 | { 77 | _logger.LogError(e, "Error in notification service."); 78 | } 79 | } 80 | } 81 | } 82 | } -------------------------------------------------------------------------------- /6.Recepies/Notifications/Notifications.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net5.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /6.Recepies/Notifications/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Hosting; 2 | using Microsoft.Extensions.Hosting; 3 | 4 | namespace Notifications 5 | { 6 | public class Program 7 | { 8 | public static void Main(string[] args) 9 | { 10 | CreateHostBuilder(args).Build().Run(); 11 | } 12 | 13 | public static IHostBuilder CreateHostBuilder(string[] args) => 14 | Host.CreateDefaultBuilder(args) 15 | .ConfigureWebHostDefaults(webBuilder => 16 | { 17 | webBuilder.UseStartup() 18 | .UseUrls($"http://127.0.0.1:{args[0]}"); 19 | }); 20 | } 21 | } -------------------------------------------------------------------------------- /6.Recepies/Notifications/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:1989", 7 | "sslPort": 44301 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "environmentVariables": { 14 | "ASPNETCORE_ENVIRONMENT": "Development" 15 | } 16 | }, 17 | "Notifications": { 18 | "commandName": "Project", 19 | "dotnetRunMessages": "true", 20 | "applicationUrl": "http://localhost:5000", 21 | "environmentVariables": { 22 | "ASPNETCORE_ENVIRONMENT": "Development" 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /6.Recepies/Notifications/Startup.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Authentication; 2 | using Microsoft.AspNetCore.Builder; 3 | using Microsoft.AspNetCore.Hosting; 4 | using Microsoft.AspNetCore.SignalR; 5 | using Microsoft.Extensions.DependencyInjection; 6 | using Microsoft.Extensions.Hosting; 7 | 8 | namespace Notifications 9 | { 10 | public class Startup 11 | { 12 | public void ConfigureServices(IServiceCollection services) 13 | { 14 | services.AddAuthentication("MyScheme") 15 | .AddScheme("MyScheme", o => 16 | { 17 | }); 18 | 19 | services.AddSingleton(); 20 | services.AddHostedService(sp => (NotificationService)sp.GetService()); 21 | services.AddSingleton(); 22 | 23 | services.AddSignalR(); 24 | services.AddControllers(); 25 | } 26 | 27 | public void Configure(IApplicationBuilder app, IWebHostEnvironment env) 28 | { 29 | if (env.IsDevelopment()) 30 | { 31 | app.UseDeveloperExceptionPage(); 32 | } 33 | 34 | app.UseStaticFiles(); 35 | 36 | app.UseRouting(); 37 | 38 | app.UseAuthentication(); 39 | app.UseAuthorization(); 40 | 41 | app.UseEndpoints(endpoints => 42 | { 43 | endpoints.MapHub("/notificationHub"); 44 | endpoints.MapDefaultControllerRoute(); 45 | }); 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /6.Recepies/Notifications/UserIdProvider.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using Microsoft.AspNetCore.SignalR; 3 | 4 | namespace Notifications 5 | { 6 | public class UserIdProvider : IUserIdProvider 7 | { 8 | public string? GetUserId(HubConnectionContext connection) 9 | { 10 | return connection.User.Claims.FirstOrDefault(x => x.Type == "userid").Value; 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /6.Recepies/Notifications/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /6.Recepies/Notifications/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | }, 9 | "AllowedHosts": "*" 10 | } 11 | -------------------------------------------------------------------------------- /6.Recepies/Notifications/redis_pub_sub.linq: -------------------------------------------------------------------------------- 1 | 2 | StackExchange.Redis 3 | StackExchange.Redis 4 | System.Text.Json 5 | 6 | 7 | void Main() 8 | { 9 | var redis = ConnectionMultiplexer.Connect("127.0.0.1"); 10 | 11 | var payload = JsonSerializer.Serialize(new { ForUserId = "foo", Message = "hello world" }); 12 | 13 | redis.GetSubscriber().Publish("notification", payload); 14 | } 15 | -------------------------------------------------------------------------------- /6.Recepies/Notifications/wwwroot/index.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Title 6 | 7 | 8 | 9 | 30 | 31 | -------------------------------------------------------------------------------- /6.Recepies/Recepies.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ChatApp", "ChatApp\ChatApp.csproj", "{5627CBFF-C054-4533-BD68-A9253815B1B0}" 4 | EndProject 5 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DrawingGame", "DrawingGame\DrawingGame.csproj", "{339FEEEA-F8BD-4015-B26B-8B6C13A930B6}" 6 | EndProject 7 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Notifications", "Notifications\Notifications.csproj", "{467F8E09-0580-41CC-9720-21AB2DBB6271}" 8 | EndProject 9 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VideoStreaming", "VideoStreaming\VideoStreaming.csproj", "{79DBD588-6768-46DA-935D-A65E6575D9F1}" 10 | EndProject 11 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Collaboration", "Collaboration\Collaboration.csproj", "{711536C4-BFB8-4C54-B4E5-E0EF924B78DF}" 12 | EndProject 13 | Global 14 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 15 | Debug|Any CPU = Debug|Any CPU 16 | Release|Any CPU = Release|Any CPU 17 | EndGlobalSection 18 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 19 | {5627CBFF-C054-4533-BD68-A9253815B1B0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 20 | {5627CBFF-C054-4533-BD68-A9253815B1B0}.Debug|Any CPU.Build.0 = Debug|Any CPU 21 | {5627CBFF-C054-4533-BD68-A9253815B1B0}.Release|Any CPU.ActiveCfg = Release|Any CPU 22 | {5627CBFF-C054-4533-BD68-A9253815B1B0}.Release|Any CPU.Build.0 = Release|Any CPU 23 | {339FEEEA-F8BD-4015-B26B-8B6C13A930B6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 24 | {339FEEEA-F8BD-4015-B26B-8B6C13A930B6}.Debug|Any CPU.Build.0 = Debug|Any CPU 25 | {339FEEEA-F8BD-4015-B26B-8B6C13A930B6}.Release|Any CPU.ActiveCfg = Release|Any CPU 26 | {339FEEEA-F8BD-4015-B26B-8B6C13A930B6}.Release|Any CPU.Build.0 = Release|Any CPU 27 | {467F8E09-0580-41CC-9720-21AB2DBB6271}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 28 | {467F8E09-0580-41CC-9720-21AB2DBB6271}.Debug|Any CPU.Build.0 = Debug|Any CPU 29 | {467F8E09-0580-41CC-9720-21AB2DBB6271}.Release|Any CPU.ActiveCfg = Release|Any CPU 30 | {467F8E09-0580-41CC-9720-21AB2DBB6271}.Release|Any CPU.Build.0 = Release|Any CPU 31 | {79DBD588-6768-46DA-935D-A65E6575D9F1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 32 | {79DBD588-6768-46DA-935D-A65E6575D9F1}.Debug|Any CPU.Build.0 = Debug|Any CPU 33 | {79DBD588-6768-46DA-935D-A65E6575D9F1}.Release|Any CPU.ActiveCfg = Release|Any CPU 34 | {79DBD588-6768-46DA-935D-A65E6575D9F1}.Release|Any CPU.Build.0 = Release|Any CPU 35 | {711536C4-BFB8-4C54-B4E5-E0EF924B78DF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 36 | {711536C4-BFB8-4C54-B4E5-E0EF924B78DF}.Debug|Any CPU.Build.0 = Debug|Any CPU 37 | {711536C4-BFB8-4C54-B4E5-E0EF924B78DF}.Release|Any CPU.ActiveCfg = Release|Any CPU 38 | {711536C4-BFB8-4C54-B4E5-E0EF924B78DF}.Release|Any CPU.Build.0 = Release|Any CPU 39 | EndGlobalSection 40 | EndGlobal 41 | -------------------------------------------------------------------------------- /6.Recepies/VideoStreaming/Program.cs: -------------------------------------------------------------------------------- 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 VideoStreaming 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 => 22 | { 23 | webBuilder.UseStartup(); 24 | }); 25 | } 26 | } -------------------------------------------------------------------------------- /6.Recepies/VideoStreaming/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:51013", 7 | "sslPort": 44376 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "environmentVariables": { 14 | "ASPNETCORE_ENVIRONMENT": "Development" 15 | } 16 | }, 17 | "VideoStreaming": { 18 | "commandName": "Project", 19 | "dotnetRunMessages": "true", 20 | "launchBrowser": true, 21 | "applicationUrl": "http://localhost:5000", 22 | "environmentVariables": { 23 | "ASPNETCORE_ENVIRONMENT": "Development" 24 | } 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /6.Recepies/VideoStreaming/Startup.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Builder; 2 | using Microsoft.AspNetCore.Hosting; 3 | using Microsoft.Extensions.DependencyInjection; 4 | using Microsoft.Extensions.Hosting; 5 | 6 | namespace VideoStreaming 7 | { 8 | public class Startup 9 | { 10 | public void ConfigureServices(IServiceCollection services) 11 | { 12 | services.AddSignalR(); 13 | } 14 | 15 | public void Configure(IApplicationBuilder app, IWebHostEnvironment env) 16 | { 17 | if (env.IsDevelopment()) 18 | { 19 | app.UseDeveloperExceptionPage(); 20 | } 21 | 22 | app.UseStaticFiles(); 23 | 24 | app.UseRouting(); 25 | 26 | app.UseEndpoints(endpoints => 27 | { 28 | endpoints.MapHub("/stream"); 29 | }); 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /6.Recepies/VideoStreaming/StreamHub.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO; 3 | using System.Text.Json.Serialization; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Hosting; 6 | using Microsoft.AspNetCore.SignalR; 7 | 8 | namespace VideoStreaming 9 | { 10 | public class StreamHub : Hub 11 | { 12 | private readonly IWebHostEnvironment _env; 13 | 14 | public StreamHub(IWebHostEnvironment env) 15 | { 16 | _env = env; 17 | } 18 | 19 | public struct VideoData 20 | { 21 | public int Index { get; } 22 | public string Part { get; } 23 | 24 | 25 | [JsonConstructor] 26 | public VideoData(int index, string part) => (Index, Part) = (index, part); 27 | } 28 | 29 | public async Task SendVideoData(IAsyncEnumerable videoData) 30 | { 31 | await foreach (var d in videoData) 32 | { 33 | await Clients.Others.SendAsync("video-data", d); 34 | } 35 | } 36 | 37 | public async Task Send() 38 | { 39 | int c = 0; 40 | while (c < 6) 41 | { 42 | var bytes = GetFile(c++ % 3); 43 | await Clients.All.SendAsync("video-data", bytes); 44 | await Task.Delay(4000); 45 | } 46 | } 47 | 48 | public byte[] GetFile(int index) 49 | { 50 | var path = Path.Combine(_env.WebRootPath, $"vid{index}.webm"); 51 | return File.ReadAllBytes(path); 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /6.Recepies/VideoStreaming/VideoStreaming.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net5.0 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /6.Recepies/VideoStreaming/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /6.Recepies/VideoStreaming/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | }, 9 | "AllowedHosts": "*" 10 | } 11 | -------------------------------------------------------------------------------- /6.Recepies/VideoStreaming/wwwroot/fetch.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Title 6 | 7 | 8 | 9 | 10 | 11 | 44 | 45 | -------------------------------------------------------------------------------- /6.Recepies/VideoStreaming/wwwroot/file.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Title 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 51 | 52 | -------------------------------------------------------------------------------- /6.Recepies/VideoStreaming/wwwroot/live.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Title 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 97 | 98 | -------------------------------------------------------------------------------- /6.Recepies/VideoStreaming/wwwroot/source.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Title 6 | 7 | 8 | 9 | 10 | 11 | 12 | 52 | 53 | -------------------------------------------------------------------------------- /6.Recepies/VideoStreaming/wwwroot/vid0.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raw-coding-youtube/aspnetcore-signalr/eaa47b1ada280a3b81b1485f444001d5ec23ba53/6.Recepies/VideoStreaming/wwwroot/vid0.webm -------------------------------------------------------------------------------- /6.Recepies/VideoStreaming/wwwroot/vid1.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raw-coding-youtube/aspnetcore-signalr/eaa47b1ada280a3b81b1485f444001d5ec23ba53/6.Recepies/VideoStreaming/wwwroot/vid1.webm -------------------------------------------------------------------------------- /6.Recepies/VideoStreaming/wwwroot/vid2.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raw-coding-youtube/aspnetcore-signalr/eaa47b1ada280a3b81b1485f444001d5ec23ba53/6.Recepies/VideoStreaming/wwwroot/vid2.webm -------------------------------------------------------------------------------- /7.CustomClient/CustomClient.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CustomClient", "CustomClient\CustomClient.csproj", "{496BC520-74B8-4084-9A7D-C63D1B71B4B7}" 4 | EndProject 5 | Global 6 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 7 | Debug|Any CPU = Debug|Any CPU 8 | Release|Any CPU = Release|Any CPU 9 | EndGlobalSection 10 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 11 | {496BC520-74B8-4084-9A7D-C63D1B71B4B7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 12 | {496BC520-74B8-4084-9A7D-C63D1B71B4B7}.Debug|Any CPU.Build.0 = Debug|Any CPU 13 | {496BC520-74B8-4084-9A7D-C63D1B71B4B7}.Release|Any CPU.ActiveCfg = Release|Any CPU 14 | {496BC520-74B8-4084-9A7D-C63D1B71B4B7}.Release|Any CPU.Build.0 = Release|Any CPU 15 | EndGlobalSection 16 | EndGlobal 17 | -------------------------------------------------------------------------------- /7.CustomClient/CustomClient/CustomClient.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net5.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /7.CustomClient/CustomClient/Program.cs: -------------------------------------------------------------------------------- 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 CustomClient 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 => 22 | { 23 | webBuilder.UseStartup(); 24 | }); 25 | } 26 | } -------------------------------------------------------------------------------- /7.CustomClient/CustomClient/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:13227", 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 | "CustomClient": { 19 | "commandName": "Project", 20 | "dotnetRunMessages": "true", 21 | "launchBrowser": true, 22 | "applicationUrl": "https://localhost:5001;http://localhost:5000", 23 | "environmentVariables": { 24 | "ASPNETCORE_ENVIRONMENT": "Development" 25 | } 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /7.CustomClient/CustomClient/Startup.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading.Tasks; 4 | using Microsoft.AspNetCore.Builder; 5 | using Microsoft.AspNetCore.Hosting; 6 | using Microsoft.AspNetCore.SignalR; 7 | using Microsoft.Extensions.DependencyInjection; 8 | using Microsoft.Extensions.Hosting; 9 | using Microsoft.Extensions.Logging; 10 | 11 | namespace CustomClient 12 | { 13 | public class Startup 14 | { 15 | public void ConfigureServices(IServiceCollection services) 16 | { 17 | services.AddSignalR(); 18 | } 19 | 20 | public void Configure(IApplicationBuilder app, IWebHostEnvironment env) 21 | { 22 | if (env.IsDevelopment()) 23 | { 24 | app.UseDeveloperExceptionPage(); 25 | } 26 | 27 | app.UseRouting(); 28 | 29 | app.UseEndpoints(endpoints => 30 | { 31 | endpoints.MapHub("/hub"); 32 | }); 33 | } 34 | } 35 | 36 | public class MyHub : Hub 37 | { 38 | private readonly ILogger _logger; 39 | private readonly string _id; 40 | public MyHub(ILogger logger) 41 | { 42 | _logger = logger; 43 | _id = Guid.NewGuid().ToString(); 44 | } 45 | 46 | public string Get(string target) => $"Hello {target} {_id}"; 47 | 48 | public async Task ReceiveStream(IAsyncEnumerable messages, string param) 49 | { 50 | _logger.LogInformation($"starting to read stream: {param}"); 51 | 52 | await foreach (var message in messages) 53 | { 54 | _logger.LogInformation($"Receiving {message} {param} {_id}"); 55 | } 56 | 57 | _logger.LogInformation("finished stream"); 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /7.CustomClient/CustomClient/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Debug", 5 | "Microsoft": "Debug", 6 | "Microsoft.Hosting.Lifetime": "Debug" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /7.CustomClient/CustomClient/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Debug", 5 | "Microsoft": "Debug", 6 | "Microsoft.Hosting.Lifetime": "Debug" 7 | } 8 | }, 9 | "AllowedHosts": "*" 10 | } 11 | -------------------------------------------------------------------------------- /7.CustomClient/CustomClient/python_client/README.md: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | 3 | 1. [Download Python with Pip](https://www.python.org/downloads/) 4 | 2. virtualenv - `pip install virtualenv` 5 | 6 | # Setup 7 | 8 | 1. `virtualenv venv` 9 | 2. `./venv/Scripts/activate` or `.\venv\Scripts\activate.bat` 10 | 3. `pip install -r requirements.txt` -------------------------------------------------------------------------------- /7.CustomClient/CustomClient/python_client/main.py: -------------------------------------------------------------------------------- 1 | # 1. https://github.com/dotnet/aspnetcore/blob/main/src/SignalR/docs/specs/TransportProtocols.md 2 | # 2. https://github.com/dotnet/aspnetcore/blob/main/src/SignalR/docs/specs/HubProtocol.md 3 | 4 | import asyncio 5 | import websockets 6 | import requests 7 | import json 8 | 9 | negotiation = requests.post('http://localhost:5000/hub/negotiate?negotiateVersion=0').json() 10 | 11 | def toSignalRMessage(data): 12 | return f'{json.dumps(data)}\u001e' 13 | 14 | async def connectToHub(connectionId): 15 | uri = f"ws://localhost:5000/hub?id={connectionId}" 16 | async with websockets.connect(uri) as websocket: 17 | 18 | async def start_pinging(): 19 | while _running: 20 | await asyncio.sleep(10) 21 | await websocket.send(toSignalRMessage({"type": 6})) 22 | 23 | async def handshake(): 24 | await websocket.send(toSignalRMessage({"protocol": "json", "version": 1})) 25 | handshake_response = await websocket.recv() 26 | print(f"handshake_response: {handshake_response}") 27 | 28 | async def listen(): 29 | while _running: 30 | get_response = await websocket.recv() 31 | print(f"get_response: {get_response}") 32 | 33 | await handshake() 34 | 35 | _running = True 36 | listen_task = asyncio.create_task(listen()) 37 | ping_task = asyncio.create_task(start_pinging()) 38 | 39 | # for i in [1,2,3]: 40 | # message = { 41 | # "type": 1, 42 | # "invocationId": f"{i}", 43 | # "target": "Get", 44 | # "arguments": [ f"World {i}" ] 45 | # } 46 | # await websocket.send(toSignalRMessage(message)) 47 | # await asyncio.sleep(5) 48 | 49 | # start 50 | start_message = { 51 | "type": 1, 52 | "invocationId": "invocation_id", 53 | "target": "ReceiveStream", 54 | "arguments": [ 55 | 'Bob' 56 | ], 57 | "streamIds": [ 58 | "stream_id" 59 | ] 60 | } 61 | await websocket.send(toSignalRMessage(start_message)) 62 | # send 63 | for i in [1,2,3]: 64 | message = { 65 | "type": 2, 66 | "invocationId": "stream_id", 67 | "item": f'Foo {i}' 68 | } 69 | await websocket.send(toSignalRMessage(message)) 70 | await asyncio.sleep(2) 71 | 72 | # end 73 | completion_message = { 74 | "type": 3, 75 | "invocationId": "stream_id" 76 | } 77 | await websocket.send(toSignalRMessage(completion_message)) 78 | _running = False 79 | await ping_task 80 | await listen_task 81 | 82 | 83 | print(f"connectionId: {negotiation['connectionId']}") 84 | asyncio.run(connectToHub(negotiation['connectionId'])) -------------------------------------------------------------------------------- /7.CustomClient/CustomClient/python_client/requirements.txt: -------------------------------------------------------------------------------- 1 | websockets 2 | requests -------------------------------------------------------------------------------- /global.json: -------------------------------------------------------------------------------- 1 | { 2 | "sdk": { 3 | "version": "5.0", 4 | "rollForward": "latestMajor", 5 | "allowPrerelease": false 6 | } 7 | } --------------------------------------------------------------------------------