ASP.NET Core Building chat room using WebSocket

Creating “Login form”

We use here simple form where user can insert his or her preferred nick name for chat. To keep us focused on WebSocket stuff we don’t add any logic or checks in this point. Let’s add view called InsertUserName.cshtml under Home folder.


<form action="@Url.Action("Index")" method="post">    <input type="text" placeholder="Insert user name" name="userName" />    <input type="submit" value="Eńter" /></form>


There will be another view for chat room and we will come back to it later when web sockets related code is done. Index() methods of home controller look like this.


[HttpGet]public IActionResult Index(){    return View("InsertUserName");}

[HttpPost]public IActionResult Index(string username){    return View("Index", username);}


If request method is GET then we show nick name form and if request method is POST we will show chat room view.

WebSockets middleware

Now let’s write ASP.Core middleware for WebSocket. To keep things simple I mixed together custom WebSocket middleware and custom WebSocket connection manager from Radu Matei’s post Creating a WebSockets middleware for ASP .NET Core. I like the work Radu has done but here we will keep things as small as possible. To get better understanding of WebSockets I suggest you to go through Radu’s post.

NB! To use WebSockets in ASP.NET Core project add reference to Microsoft.AspNetCore.WebSockets NuGet package!

In breaf, this is what our WebSocket middleware class does:

  1. Keep concurrent dictionary with connected WebSockets (this is needed for message broadcast)
  2. Read messages from WebSocket and broadcast there to all known WebSockets
  3. Try to keep WebSockets dictionary as clean as possible

Here is the WebSocket middleware class.


public class ChatWebSocketMiddleware{    private static ConcurrentDictionary<string, WebSocket> _sockets = new ConcurrentDictionary<string, WebSocket>();

    private readonly RequestDelegate _next;

    public ChatWebSocketMiddleware(RequestDelegate next)    {        _next = next;    }

    public async Task Invoke(HttpContext context)    {        if (!context.WebSockets.IsWebSocketRequest)        {            await _next.Invoke(context);            return;        }

        CancellationToken ct = context.RequestAborted;        WebSocket currentSocket = await context.WebSockets.AcceptWebSocketAsync();        var socketId = Guid.NewGuid().ToString();

        _sockets.TryAdd(socketId, currentSocket);

        while (true)        {            if (ct.IsCancellationRequested)            {                break;            }

            var response = await ReceiveStringAsync(currentSocket, ct);            if(string.IsNullOrEmpty(response))            {                if(currentSocket.State != WebSocketState.Open)                {                    break;                }

                continue;            }

            foreach (var socket in _sockets)            {                if(socket.Value.State != WebSocketState.Open)                {                    continue;                }

                await SendStringAsync(socket.Value, response, ct);            }        }

        WebSocket dummy;        _sockets.TryRemove(socketId, out dummy);

        await currentSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closing", ct);        currentSocket.Dispose();    }

    private static Task SendStringAsync(WebSocket socket, string data, CancellationToken ct = default(CancellationToken))    {        var buffer = Encoding.UTF8.GetBytes(data);        var segment = new ArraySegment<byte>(buffer);        return socket.SendAsync(segment, WebSocketMessageType.Text, true, ct);    }

    private static async Task<string> ReceiveStringAsync(WebSocket socket, CancellationToken ct = default(CancellationToken))    {        var buffer = new ArraySegment<byte>(new byte[8192]);        using (var ms = new MemoryStream())        {            WebSocketReceiveResult result;            do            {                ct.ThrowIfCancellationRequested();

                result = await socket.ReceiveAsync(buffer, ct);                ms.Write(buffer.Array, buffer.Offset, result.Count);            }            while (!result.EndOfMessage);

            ms.Seek(0, SeekOrigin.Begin);            if (result.MessageType != WebSocketMessageType.Text)            {                return null;            }

            // Encoding UTF8: https://tools.ietf.org/html/rfc6455#section-5.6            using (var reader = new StreamReader(ms, Encoding.UTF8))            {                return await reader.ReadToEndAsync();            }        }    }}


Before using middleware we have to introduce it to request pipeline in Startup class of web application. We do it in configure method before initializing MVC.


app.UseWebSockets();app.UseMiddleware<ChatWebSocketMiddleware>();

app.UseMvc(routes =>{    routes.MapRoute(        name: "default",        template: "{controller=Home}/{action=Index}/{id?}");});

NB! It’s very important to add WebSockets before MVC. WebSockets middleware checks in the beginning if request is for WebSocket. If it is WebSocket request then middleware starts handling it. If MVC is added to pipeline before then MVC will handle all the requests and our WebSocket middleware is never used.

Chat room view

Now let’s add chat room view. We will use Index view of home controller for chat room. Chat room view initializes WebSocket connection and starts listening to it. If user writes something to chat box and presses Enter then message is sent to server over WebSocket and WebSocket middleware will broadcast it to all WebSocket clients it knows.


@model string@{    ViewData["Title"] = "Home Page";}<style>    body {margin:0px; padding:0px;}    .msg {        position: absolute;        top: 0;        bottom: 30px;        border: 1px solid green;        margin-bottom: auto;        display:block;        overflow: scroll;        width:100%;        white-space:nowrap;    }</style><div class="msg">    <div style="position:absolute; bottom:0;" id="msgs"></div></div>

<div style="position:absolute;height:20px;bottom:10px;left:0; display:block;width:100%">    <input type="text" style="max-width:unset;width:100%;max-width:100%" id="MessageField" placeholder="type message and press enter" /></div>

@section Scripts {    <script>    $(function () {        var userName = ‘@Model‘;

        var protocol = location.protocol === "https:" ? "wss:" : "ws:";        var wsUri = protocol + "//" + window.location.host;        var socket = new WebSocket(wsUri);        socket.onopen = e => {            console.log("socket opened", e);        };

        socket.onclose = function (e) {            console.log("socket closed", e);        };

        socket.onmessage = function (e) {            console.log(e);            $(‘#msgs‘).append(e.data + ‘<br />‘);        };

        socket.onerror = function (e) {            console.error(e.data);        };

        $(‘#MessageField‘).keypress(function (e) {            if (e.which != 13) {                return;            }

            e.preventDefault();

            var message = userName + ": " + $(‘#MessageField‘).val();            socket.send(message);            $(‘#MessageField‘).val(‘‘);        });    });    </script>}


Now let’s build application and run it.

WebSocket chat room in action

The screenshot below shows how our chat room looks like. It’s extremely primitive and simple but it works. There’s room enough for improvements but this is the fun I leave to all my dear readers.

Wrapping up

Using WebSockets in ASP.NET Core is simple. Without any additional libraries we can use the one by Microsoft. We had to write custom middleware class for WebSocket communication and in our case the class came pretty small. We used concurrent dictionary as a WebSockets cache and this enabled us to broadcast messages over sockets. Our solution is very primitive and simple, there is a lot of room for improvements but as a proof of concept it works well.

时间: 2024-08-01 13:53:46

ASP.NET Core Building chat room using WebSocket的相关文章

Asp.net core中的websocket

Websocket是html5后的产物,对于asp.net core中也得到了支持,首先在NuGet中添加Microsoft.AspNetCore.WebSockets的引用(现在是1.0.1版本,2017年3月7日发布的). 首先在Configure中添加中间件 //添加websocket中间件 app.UseWebSockets(); 接下来就要定义自己处理websocket的中间件了,代码如下: using Microsoft.AspNetCore.Http; using System;

Building microservices with ASP.NET Core (without MVC)(转)

There are several reasons why it makes sense to build super-lightweight HTTP services (or, despite all the baggage the word brings, “microservices”). I do not need to go into all the operational or architectural benefits of such approach to system de

快速搭建CentOS+ASP.NET Core环境支持WebSocket

以前用python,go尝试在linux下做web服务,python没有强类型支持与高性能,go又没有很好的集成开发环境(还有强迫症的语法),回头看了几次.net,都没有时间尝试,现终于实现了这些想法,与大家分享.做web大项目,做工程,必须要有称手的工具帮我调试.测试.开发.工程化很重要,VS是一个称职好选手. 环境:CentOS 7.x,.net core 2 以下.net core 2安装操作为官方方法.如果你使用Docker,那么更简单了,只需要docker pull microsoft

ASP.NET Core 中的 WebSocket 支持(转自MSDN)

本文介绍 ASP.NET Core 中 WebSocket 的入门方法. WebSocket (RFC 6455) 是一个协议,支持通过 TCP 连接建立持久的双向信道. 它用于从快速实时通信中获益的应用,如聊天.仪表板和游戏应用. 如果不明白什么是WebSocket可以参考这篇文章 系统必备 ASP.NET Core 1.1 或更高版本 支持 ASP.NET Core 的任何操作系统: Windows 7/Windows Server 2008 或更高版本 Linux macOS 如果应用在安

Building simple plug-ins system for ASP.NET Core(转)

Recently I built plug-ins support to my TemperatureStation IoT solution web site. The code for .NET Core is different from what we have seen on full .NET Framework (application domains etc) but there’s still nothing complex. This blog post describes

Building a Web App with ASP.NET Core, MVC, Entity Framework Core, Bootstrap, and Angular

Since I have spent many years on Windows Application development in my first three years of software career.  I was interested in the C#, had tried to understand the basic knowledge about C#. The programs, the patterns, the object-oriented methodolog

ASP.NET Core: 全新的ASP.NET !

背景 最新版本的 ASP.NET 叫做 ASP.NET Core (也被称为 ASP.NET 5)   它颠覆了过去的 ASP.NET. 什么是 ASP.NET Core? ASP.NET Core 1.0 是一个开源跨平台的开发框架,用于构建基于云的现代 Web 应用 .它是从底层开始重新构建来提供性能优良的Web应用开发框架,可以部署在云上或者本地服务器上.另外,它使得 ASP.NET 应用更加精简和模块化(可以根据你的应用需要向里面添加其他模块),跨平台(你可以很容易的在 Windows,

ASP.NET Core 3.0 入门

原文:ASP.NET Core 3.0 入门 课程简介 与2.x相比发生的一些变化,项目结构.Blazor.SignalR.gRPC等 课程预计结构 ASP.NET Core 3.0项目架构简介 ASP.NET Core MVC 简介 Blazor SignalR Web API gRPC 发布 一. 创建项目 dotnet core 本质上是控制台应用 1. DI 依赖注入(Dependency Injection) IoC 容器(Inversion of Control)控制反转 注册(服务

在Linux和Windows的Docker容器中运行ASP.NET Core

(此文章同时发表在本人微信公众号"dotNET每日精华文章",欢迎右边二维码来关注.) 译者序:其实过去这周我都在研究这方面的内容,结果周末有事没有来得及总结为文章,Scott Hanselman就捷足先登了.那么我就来翻译一下这篇文章,让更多的中文读者看到.当然Scott遇到的坑我也遇到了. 不过首先,对于不熟悉的朋友我还是来解释一下Linux容器和Windows容器的概念. 由于容器成为虚拟化和应用托管的一种不可避免的选项,Windows也开始为公众提供容器功能(其实微软具备和使用