怎么在ASP.NET MVC4中实现一个异步聊天室
创始人
2024-11-27 21:41:27
0次

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, 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 GetSince(DateTime since);
  }
}

ICallbackQueue.cs

using System;
using System.Collections.Generic;

namespace MvcAsyncChat.Domain
{
  public interface ICallbackQueue
  {
    void Enqueue(Action, DateTime> callback);
    IEnumerable, DateTime>> DequeueAll();
    IEnumerable, 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, 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>();
    }

    public IList> Messages { get; private set; }

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

      Messages.Add(new Tuple(message, timestamp));

      return timestamp;
    }

    public IEnumerable 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, DateTime>, DateTime>>();
    }

    public Queue, DateTime>, DateTime>> Callbacks { get; private set; }

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

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

    public IEnumerable, 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 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 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("" + message + "
") } 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 {}


  
    

[MVC聊天]是使用ASP.NET MVC 3的异步聊天室                                
            进入聊天室             @using(Html.BeginForm()) {               @Html.EditorForModel()                            }           
               
    
   @section PostScript {    }

Room.cshtml

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

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

@section Head {
  
}


  


  

    操作:
    
      
  • @Html.RouteLink("离开房间", RouteName.Leave)
  •          @using (Ajax.BeginForm("say", new { }, new AjaxOptions() {        OnFailure = "onSayFailed",        OnSuccess = "onSay",        HttpMethod = "POST", }, new { id = "sayForm"})) {       @Html.EditorForModel()     }    @section PostScript {    }

    相关内容

    热门资讯

    空调连续工作多久需要停机休息 空调连续工作的时间取决于多个因素,包括空调的型号、功率、使用环境和维护情况等。一般来说,空调可以连续...
    空调哪个符号是除湿的 空调除湿功能通常用一个水滴形状的符号表示。不同品牌和型号的空调遥控器可能会有所差异,但一般来说,除湿...
    格力空调多长时间加一次氟利昂 1、一般情况下,空调对氟利昂的使用量是非常小的,所以在空调使用了四年左右的时候再考虑加也没问题。2、...
    空调用多长时间需要加氟一次 一般情况下是五年。当出现以下这几种情况时,空调才需要加氟。1、空调的室内机和室外机各有两个接口,当接...
    格调生活·C标思享荟:卡萨帝在... 9月12日的郑州龙湖金融岛,夜风轻拂,灯光璀璨。“C标时刻共传奇——卡萨帝思享荟暨20周年品牌盛典郑...
    品牌价值破千亿、高端份额持续领... 9月12日,“C标时刻共传奇——卡萨帝思享荟暨20周年品牌盛典郑州站”在郑州龙湖金融岛举办。2026...
    空调主板坏了修800还是换新的 空调主板坏了是修还是换新,需要考虑多个因素,以下是一些建议:1. 维修成本:如果维修费用较低,比如 ...
    空调开多久需要休息多长时间 问题:空调开多久需要休息多长时间回答:1、在使用空调的过程中,一般空调开7~8个小时左右需要休息2~...
    中央空调多长时间清洗一次 1、一般空调开机前清洗一次即可,空调开机中间时段再清洗一次,空调关机时清洗一次,这样比较合理。2、空...
    中央空调氟利昂多久加一次 看空调制热和制冷程度,很多人的观点认为氟利昂必须一年放一次,其实这个观点是错误的,理解上面有点偏差,...