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>
}

运行结果如图:

这里写图片描述


这里写图片描述


  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值