您的当前位置:首页ASP.NET MVC4异步聊天室的示例代码

ASP.NET MVC4异步聊天室的示例代码

2020-11-27 来源:爱问旅游网

本文介绍了ASP.NET MVC4异步聊天室的示例代码,分享给大家,具体如下:

类图:

Domain层

IChatRoom.cs

using System;
using System.Collections.Generic;

namespace MvcAsyncChat.Domain
{
 public interface IChatRoom
 {
 void AddMessage(string message);
 void AddParticipant(string name);
 void GetMessages(
 DateTime since, 
 Action<IEnumerable<string>, DateTime> callback);
 void RemoveParticipant(string name);
 }
}
 

IMessageRepo.cs

using System;
using System.Collections.Generic;

namespace MvcAsyncChat.Domain
{
 public interface IMessageRepo
 {
 DateTime Add(string message);
 IEnumerable<string> GetSince(DateTime since);
 }
}

ICallbackQueue.cs

using System;
using System.Collections.Generic;

namespace MvcAsyncChat.Domain
{
 public interface ICallbackQueue
 {
 void Enqueue(Action<IEnumerable<string>, DateTime> callback);
 IEnumerable<Action<IEnumerable<string>, DateTime>> DequeueAll();
 IEnumerable<Action<IEnumerable<string>, DateTime>> DequeueExpired(DateTime expiry);
 }
}

ChatRoom.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using MvcAsyncChat.Svcs;

namespace MvcAsyncChat.Domain
{
 public class ChatRoom : IChatRoom
 {
 readonly ICallbackQueue callbackQueue;
 readonly IDateTimeSvc dateTimeSvc;
 readonly IMessageRepo messageRepo;

 public ChatRoom(
 ICallbackQueue callbackQueue,
 IDateTimeSvc dateTimeSvc,
 IMessageRepo messageRepo)
 {
 this.callbackQueue = callbackQueue;
 this.dateTimeSvc = dateTimeSvc;
 this.messageRepo = messageRepo;
 }

 public void AddMessage(string message)
 {
 var timestamp = messageRepo.Add(message);

 foreach (var callback in callbackQueue.DequeueAll())
 callback(new[] { message }, timestamp);
 }

 public void AddParticipant(string name)
 {
 AddMessage(string.Format("{0} 已进入房间.", name));
 }

 public void GetMessages(
 DateTime since,
 Action<IEnumerable<string>, DateTime> callback)
 {
 var messages = messageRepo.GetSince(since);

 if (messages.Count() > 0)
 callback(messages, since);
 else
 callbackQueue.Enqueue(callback);
 }

 public void RemoveParticipant(string name)
 {
 AddMessage(string.Format("{0} left the room.", name));
 }
 }
}

InMemMessageRepo.cs

using System;
using System.Collections.Generic;
using System.Linq;

namespace MvcAsyncChat.Domain
{
 public class InMemMessageRepo : IMessageRepo
 {
 public InMemMessageRepo()
 {
 Messages = new List<Tuple<string, DateTime>>();
 }

 public IList<Tuple<string, DateTime>> Messages { get; private set; }

 public DateTime Add(string message)
 {
 var timestamp = DateTime.UtcNow;

 Messages.Add(new Tuple<string, DateTime>(message, timestamp));

 return timestamp;
 }

 public IEnumerable<string> GetSince(DateTime since)
 {
 return Messages
 .Where(x => x.Item2 > since)
 .Select(x => x.Item1);
 }
 }
}

CallbackQueue.cs

using System;
using System.Collections.Generic;
using System.Linq;

namespace MvcAsyncChat.Domain
{
 public class CallbackQueue : ICallbackQueue
 {
 public CallbackQueue()
 {
 Callbacks = new Queue<Tuple<Action<IEnumerable<string>, DateTime>, DateTime>>();
 }

 public Queue<Tuple<Action<IEnumerable<string>, DateTime>, DateTime>> Callbacks { get; private set; }

 public void Enqueue(Action<IEnumerable<string>, DateTime> callback)
 {
 Callbacks.Enqueue(new Tuple<Action<IEnumerable<string>, DateTime>, DateTime>(callback, DateTime.UtcNow));
 }

 public IEnumerable<Action<IEnumerable<string>, DateTime>> DequeueAll()
 {
 while (Callbacks.Count > 0)
 yield return Callbacks.Dequeue().Item1;
 }

 public IEnumerable<Action<IEnumerable<string>, DateTime>> DequeueExpired(DateTime expiry)
 {
 if (Callbacks.Count == 0)
 yield break;

 var oldest = Callbacks.Peek();
 while (Callbacks.Count > 0 && oldest.Item2 <= expiry)
 {
 yield return Callbacks.Dequeue().Item1;

 if (Callbacks.Count > 0)
 oldest = Callbacks.Peek();
 }
 }
 }
}

RequestModels文件夹实体类

EnterRequest.cs

using System;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;

namespace MvcAsyncChat.RequestModels
{
 public class EnterRequest
 {
 [DisplayName("名称")]
 [Required, StringLength(16), RegularExpression(@"^[A-Za-z0-9_\ -]+$", ErrorMessage="A name must be alpha-numeric.")]
 public string Name { get; set; }
 }
}

GetMessagesRequest.cs

using System;

namespace MvcAsyncChat.RequestModels
{
 public class GetMessagesRequest
 {
 public string since { get; set; }
 }
}

SayRequest.cs

using System;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;

namespace MvcAsyncChat.RequestModels
{
 public class SayRequest
 {
 [Required, StringLength(1024), DataType(DataType.MultilineText)]
 public string Text { get; set; }
 }
}

ResponseModels文件夹实体类

GetMessagesResponse.cs

using System;
using System.Collections.Generic;

namespace MvcAsyncChat.ResponseModels
{
 public class GetMessagesResponse
 {
 public string error { get; set; }
 public IEnumerable<string> messages { get; set; }
 public string since { get; set; }
 }
}

SayResponse.cs

using System;

namespace MvcAsyncChat.ResponseModels
{
 public class SayResponse
 {
 public string error { get; set; }
 }
}

ChatController.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Mvc.Async;
using MvcAsyncChat.Domain;
using MvcAsyncChat.RequestModels;
using MvcAsyncChat.ResponseModels;
using MvcAsyncChat.Svcs;

namespace MvcAsyncChat.Controllers
{
 public class ChatController : AsyncController
 {
 readonly IAuthSvc authSvc;
 readonly IChatRoom chatRoom;
 readonly IDateTimeSvc dateTimeSvc;

 public ChatController(
 IAuthSvc authSvc,
 IChatRoom chatRoom,
 IDateTimeSvc dateTimeSvc)
 {
 this.authSvc = authSvc;
 this.chatRoom = chatRoom;
 this.dateTimeSvc = dateTimeSvc;
 }

 [ActionName("enter"), HttpGet]
 public ActionResult ShowEnterForm()
 {
 if (User.Identity.IsAuthenticated)
 return RedirectToRoute(RouteName.Room);

 return View();
 }

 [ActionName("enter"), HttpPost]
 public ActionResult EnterRoom(EnterRequest enterRequest)
 {
 if (!ModelState.IsValid)
 return View(enterRequest);

 authSvc.Authenticate(enterRequest.Name);
 chatRoom.AddParticipant(enterRequest.Name);

 return RedirectToRoute(RouteName.Room);
 }

 [ActionName("room"), HttpGet, Authorize]
 public ActionResult ShowRoom()
 {
 return View();
 }

 [ActionName("leave"), HttpGet, Authorize]
 public ActionResult LeaveRoom()
 {
 authSvc.Unauthenticate();
 chatRoom.RemoveParticipant(User.Identity.Name);

 return RedirectToRoute(RouteName.Enter);
 }

 [HttpPost, Authorize]
 public ActionResult Say(SayRequest sayRequest)
 {
 if (!ModelState.IsValid)
 return Json(new SayResponse() { error = "该请求无效." });

 chatRoom.AddMessage(User.Identity.Name+" 说:"+sayRequest.Text);

 return Json(new SayResponse());
 }

 [ActionName("messages"), HttpPost, Authorize]
 public void GetMessagesAsync(GetMessagesRequest getMessagesRequest)
 {
 AsyncManager.OutstandingOperations.Increment();

 if (!ModelState.IsValid)
 {
 AsyncManager.Parameters["error"] = "The messages request was invalid.";
 AsyncManager.Parameters["since"] = null;
 AsyncManager.Parameters["messages"] = null;
 AsyncManager.OutstandingOperations.Decrement();
 return;
 }

 var since = dateTimeSvc.GetCurrentDateTimeAsUtc();
 if (!string.IsNullOrEmpty(getMessagesRequest.since))
 since = DateTime.Parse(getMessagesRequest.since).ToUniversalTime();

 chatRoom.GetMessages(since, (newMessages, timestamp) => 
 {
 AsyncManager.Parameters["error"] = null;
 AsyncManager.Parameters["since"] = timestamp;
 AsyncManager.Parameters["messages"] = newMessages;
 AsyncManager.OutstandingOperations.Decrement();
 });
 }

 public ActionResult GetMessagesCompleted(
 string error, 
 DateTime? since, 
 IEnumerable<string> messages)
 {
 if (!string.IsNullOrWhiteSpace(error))
 return Json(new GetMessagesResponse() { error = error });

 var data = new GetMessagesResponse();
 data.since = since.Value.ToString("o");
 data.messages = messages;

 return Json(data);
 }
 }
}

room.js

var since = "",
 errorCount = 0,
 MAX_ERRORS = 6;

function addMessage(message, type) {
 $("#messagesSection > td").append("<div class='" + (type || "") + "'>" + message + "</div>")
}

function showError(error) {
 addMessage(error.toString(), "error");
}

function onSayFailed(XMLHttpRequest, textStatus, errorThrown) {
 showError("An unanticipated error occured during the say request: " + textStatus + "; " + errorThrown);
}

function onSay(data) {
 if (data.error) {
 showError("An error occurred while trying to say your message: " + data.error);
 return;
 }
}

function setSayHandler() {
 $("#Text").keypress(function (e) {
 if (e.keyCode == 13) {
 $("#sayForm").submit();
 $("#Text").val("");
 return false;
 }
 });
}

function retryGetMessages() {
 if (++errorCount > MAX_ERRORS) {
 showError("There have been too many errors. Please leave the chat room and re-enter.");
 }
 else {
 setTimeout(function () {
 getMessages();
 }, Math.pow(2, errorCount) * 1000);
 }
}

function onMessagesFailed(XMLHttpRequest, textStatus, errorThrown) {
 showError("An unanticipated error occured during the messages request: " + textStatus + "; " + errorThrown);
 retryGetMessages();
}

function onMessages(data, textStatus, XMLHttpRequest) {
 if (data.error) {
 showError("An error occurred while trying to get messages: " + data.error);
 retryGetMessages();
 return;
 }

 errorCount = 0;
 since = data.since;

 for (var n = 0; n < data.messages.length; n++)
 addMessage(data.messages[n]);

 setTimeout(function () {
 getMessages();
 }, 0);
}

function getMessages() {
 $.ajax({
 cache: false,
 type: "POST",
 dataType: "json",
 url: "/messages",
 data: { since: since },
 error: onMessagesFailed,
 success: onMessages,
 timeout: 100000
 });
}

Chat视图文件夹

Enter.cshtml

@model MvcAsyncChat.RequestModels.EnterRequest

@{
 View.Title = "Enter";
 Layout = "~/Views/Shared/_Layout.cshtml";
}

@section Head {}

<tr id="enterSection">
 <td>
 <h2>[MVC聊天]是使用ASP.NET MVC 3的异步聊天室
 <table>
 <tr>
 <td class="form-container">
 <fieldset>
 <legend>进入聊天室</legend>
 @using(Html.BeginForm()) {
 @Html.EditorForModel()
 <input type="submit" value="Enter" />
 }
 </fieldset>
 </td>
 </tr>
 </table>
 </td>
</tr>

@section PostScript {
 <script>
 $(document).ready(function() {
 $("#Name").focus(); 
 });
 </script>
}

Room.cshtml

@using MvcAsyncChat;
@using MvcAsyncChat.RequestModels;
@model SayRequest

@{
 View.Title = "Room";
 Layout = "~/Views/Shared/_Layout.cshtml";
}

@section Head {
 <script src="@Url.Content("~/Scripts/room.js")"></script>
}

<tr id="messagesSection">
 <td></td>
</tr>
<tr id="actionsSection">
 <td>

 <label for="actionsList">操作:</label>
 <ul id="actionsList">
 <li>@Html.RouteLink("离开房间", RouteName.Leave)</li>
 </ul>
 @using (Ajax.BeginForm("say", new { }, new AjaxOptions() { 
 OnFailure = "onSayFailed", 
 OnSuccess = "onSay", 
 HttpMethod = "POST", }, new { id = "sayForm"})) {
 @Html.EditorForModel()
 }
 </td>
</tr>

@section PostScript {
 <script>
 $(document).ready(function() {
 $("#Text").attr("placeholder", "你说:");
 $("#Text").focus();
 setSayHandler();
 getMessages();
 });
 </script>
}

运行结果如图:

这里写图片描述

这里写图片描述

显示全文