65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 MVC 5 的 MessageBoardApp

starIconstarIconstarIconstarIconstarIcon

5.00/5 (16投票s)

2015 年 5 月 23 日

CPOL

7分钟阅读

viewsIcon

39019

downloadIcon

4679

简单的消息/回复应用程序,使用 MVC 5

引言

网站内的消息传递在用户之间沟通想法方面起着重要作用。在页面内,可能有一些地方用户需要就某些事项与其他用户进行咨询。与其发送电子邮件或打电话,不如添加一个消息系统,在那里可以发布消息,并针对与关注的主题相关的问题发送回复。

背景

在本文中,我将解释一个简单的MessageBoard App,您可以在其中发布/创建消息,并为这些消息发送回复。我使用了SendGrid SMTP 客户端来向消息所有者发送电子邮件,以便在收到回复时通知他们。

我使用Visual Studio 2013中的MVC 5创建了演示应用程序,其中我只使用了两个视图 - 一个用于显示消息,另一个用于创建消息。我使用了PagedList来实现消息的显示分页,以显示固定数量的行。要将分页用于视图,我参考了以下两个关于在MVC中使用PagedList的文档:

http://www.asp.net/mvc/overview/getting-started/getting-started-with-ef-using-mvc/sorting-filtering-and-paging-with-the-entity-framework-in-an-asp-net-mvc-application

http://stackoverflow.com/questions/25125329/using-a-pagedlist-with-a-viewmodel-asp-net-mvc

在上面的第一个参考链接中,PageList已被用于单个模型,而第二个参考链接提供了在ViewModel中使用PageList的解决方案,其中组合了多个模型,并可以在ViewModel中为特定模型进行分页。

描述

MessageBoard App要求用户登录才能创建消息或回复消息。当用户第一次登录应用程序时,如果已经有一些消息由其他用户创建,则应用程序将如下所示。

在下面,左侧是带有回复消息的主页。右侧的视图是用于创建消息的。如果用户从主页视图点击Post New Message链接,用户将被重定向到Create页面,并在用户提交页面后,将用户重定向回主页,其中提交的消息已列在消息列表中。

 

现在让我们看看如何从头开始创建应用程序并运行它。

创建项目

我在Visual Studion 2013中创建了一个全新的MVC 5 Web 项目,并将其命名为MessageBoard App。项目创建完成后,它在Solution Explorer中看起来如下。

设置 MessageBoardApp 的用户

ASPNET Identity 2.0MVC 5 在数据库中不使用用户的全名。在我们的消息系统中,我们将使用用户的全名来轻松识别创建消息和发送消息回复的用户。

为此,我创建了一个ApplicationBaseController类,并更新了.LoginPartial.cshtml,以便它能够显示完整的用户名而不是默认的用户电子邮件。

我有一个关于这个的 CodeProject 技巧,地址是: CodeProject : https://codeproject.org.cn/Tips/991663/Displaying-User-Full-Name-instead-of-User-Email-in

对于我们的应用程序,ApplicationBaseController看起来如下。

using MessageBoardApp.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;

namespace MessageBoardApp.Controllers
{
    public abstract class ApplicationBaseController : Controller
    {
        protected override void OnActionExecuted(ActionExecutedContext filterContext)
        {
            if (User != null)
            {
                var context = new ApplicationDbContext();
                var username = User.Identity.Name;

                if (!string.IsNullOrEmpty(username))
                {
                    var user = context.Users.SingleOrDefault(u => u.UserName == username);
                    string fullName = string.Concat(new string[] { user.FirstName, " ", user.LastName });
                    ViewData.Add("FullName", fullName);
                }
            }
            base.OnActionExecuted(filterContext);
        }

        public ApplicationBaseController()
        {

        }
    }
}

 

完成此操作后,我们现在可以为我们的Messageboard App创建模型视图模型

创建模型

我在Models文件夹中像往常一样创建了两个模型:MessageReplyMessage类看起来如下。

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace MessageBoardApp.Models
{
    public class Message
    {
        [Key]
        public int Id { get; set; }
        [Required]
        public string Subject { get; set; }
        [Required]
        public string MessageToPost { get; set; }
        public string From { get; set; }
        public DateTime DatePosted { get; set; }

    }
}

Reply类则如下所示。

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace MessageBoardApp.Models
{
    public class Reply
    {
        [Key]
        public int Id { get; set; }
        public int MessageId { get; set; }
        public string ReplyFrom { get; set; }
        [Required]
        public string ReplyMessage { get; set; }
        public DateTime ReplyDateTime { get; set; }
    }
}

到目前为止,类定义中没有什么特别之处。

创建视图模型

我创建了一个新的ViewModels文件夹和一个名为MessageReplyViewModel的新类,该类具有以下方法和属性,如下所示。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using MessageBoardApp.Models;
using PagedList;
using PagedList.Mvc;

namespace MessageBoardApp.ViewModels
{
    public class MessageReplyViewModel
    {
       private List<MessageReply> _replies = new List<MessageReply>();
        public Reply Reply { get; set; }
        
        public Message Message {get;set;}
        
        public List<MessageReply> Replies
        {
            get { return _replies; }
            set { _replies = value; }
        }
        
        public PagedList.IPagedList<Message> Messages { get; set; }

        public class MessageReply
        {
            public int Id { get; set; }
            public int MessageId { get; set; }
            public string MessageDetails { get; set; }
            public string ReplyFrom { get; set; }
           
            public string ReplyMessage { get; set; }
            public DateTime ReplyDateTime { get; set; }
        }
        

    }
}

请注意,我已使用

 public PagedList.IPagedList<Message> Messages { get; set; }

用于MessageReplyViewModel中的Messages,而对于Replies,我没有使用PagedList,尽管两者都将在同一个视图中使用。我将在控制器操作和视图中解释如何使用分页。

 

创建视图

正如我之前所说,我将只使用两个视图,一个用于显示消息,用户可以在同一视图中回复消息,另一个用于创建新消息。第一个显示消息的视图,我在 ViewModel 中使用了 PagedList,其中一个模型使用了 PagedList,但另一个模型没有。

要在视图中使用PagedList,我们需要在视图页面的顶部定义以下内容。

@model MessageBoardApp.ViewModels.MessageReplyViewModel
@using PagedList;
@using PagedList.Mvc;
<link href="~/Content/PagedList.css" rel="stylesheet" type="text/css" />

之后,我们需要在从中传递数据的控制器中设置页面大小和页码。

让我们来看看控制器方法。

public ActionResult Index(int? Id, int? page)
        {
int pageSize = 5;
int pageNumber = (page ?? 1);
MessageReplyViewModel vm = new MessageReplyViewModel();
var count = dbContext.Messages.Count();
decimal totalPages = count / (decimal)pageSize;
ViewBag.TotalPages = Math.Ceiling(totalPages);
vm.Messages = dbContext.Messages
              .OrderBy(x => x.DatePosted).ToPagedList(pageNumber, pageSize);
ViewBag.MessagesInOnePage = vm.Messages;
ViewBag.PageNumber = pageNumber;  

--code block skipped for brevity
 }

我们可以看到上面的ActionResult有一些关于视图的页面大小和页码的设置。它还计算了数据库中已有的总消息数。设置好这些之后,我们就可以在视图中调用它们,如下所示。

Page @ViewBag.PageNumber of @ViewBag.TotalPages   @Html.PagedListPager((IPagedList)ViewBag.MessagesInOnePage, page => Url.Action("Index", new { page }))

为了清楚起见,下面是从主页(消息列表)的输出

已在Index.cshtml中的<div>中定义,如下所示。

--code above  

<div class="form-group">
                                    @using (Html.BeginForm("DeleteMessage", "Message", FormMethod.Post, new { @id = "form-message-delete", @class = "form-horizontal container" }))
                                    {
                                        <div class="col-sm-12">

                                            <!-- table  -->
                                            <table id="table-message-reply" class="table table-condensed table-striped table-message-reply">
                                                <thead>
                                                    <tr>
                                                        <th class="tbl-subject">Subject</th>
                                                        <th class="tbl-from">From</th>
                                                        <th class="tbl-date">Date Posted</th>
                                                        <th></th>
                                                        <th></th>
                                                    </tr>
                                                </thead>

                                                @foreach (var m in Model.Messages)
                                            {
                                                string selectedRow = "";
                                                if (m.Id == ViewBag.MessageId)
                                                {
                                                    selectedRow = "success";
                                                }
                                                <tr class="@selectedRow">
                                                    <td>
                                                        <div class="text">@m.Subject</div>
                                                    </td>

                                                    <td>
                                                        <div class="text">@m.From</div>
                                                    </td>
                                                    <td>
                                                        <div class="text">@m.DatePosted.ToString("dd/MM/yyyy")</div>
                                                    </td>
                                                    <td>
                                                        @Html.ActionLink("View Reply", "Index", new { Id = m.Id })
                                                    </td>
                                                    <td>
                                                        <div class="text edit">
                                                            <a class="delete" href="#" title="delete" onclick="messageDelete(@m.Id)"><img style="width: 17px; height: 15px" src="~/Images/no.png" /></a>
                                                        </div>
                                                    </td>
                                                    <td><input type="hidden" id="messageId" name="messageId" value="@m.Id"></td>
                                                </tr>

                                            }
                                            </table>
                                            Page @ViewBag.PageNumber of @ViewBag.TotalPages   @Html.PagedListPager((IPagedList)ViewBag.MessagesInOnePage, page => Url.Action("Index", new { page }))
                                            <!-- category table end-->

                                        </div>
                                    }
                                </div> 
--code below

Replies for Messages(消息回复)面板包含两部分:一部分用于Reply(回复)文本框、Reply(回复)按钮以及根据顶部面板中某个消息选择的View Reply(查看回复)而显示的Message Detials(消息详情)。

另一部分包含现有消息回复以及每条回复的详细信息。

在控制器方法中,我已如下定义了回复的视图。

--code removed for brevity
if (Id != null)
            {

                var replies = dbContext.Replies.Where(x => x.MessageId == Id.Value).OrderByDescending(x => x.ReplyDateTime).ToList();
                if (replies != null)
                {
                    foreach (var rep in replies)
                    {
                        MessageReplyViewModel.MessageReply reply = new MessageReplyViewModel.MessageReply();
                        reply.MessageId = rep.MessageId;
                        reply.Id = rep.Id;
                        reply.ReplyMessage = rep.ReplyMessage;
                        reply.ReplyDateTime = rep.ReplyDateTime;
                        reply.MessageDetails = dbContext.Messages.Where(x => x.Id == rep.MessageId).Select(s => s.MessageToPost).FirstOrDefault();
                        reply.ReplyFrom = rep.ReplyFrom;
                        vm.Replies.Add(reply);
                    }

                }
                else
                {
                    vm.Replies.Add(null);
                }

                ViewBag.MessageId = Id.Value; 
            } 
--code removed for brevity

 

要使用控制器方法渲染视图,以下代码即可满足要求。

--code above

 @if (Model.Replies != null && ViewBag.MessageId != null)
                {
                    <div class="panel panel-default">
                        <div class="panel-heading">
                            <h3 class="panel-title">
                                Replies for Message
                            </h3>
                        </div>
                        <div class="panel-body">

                            <div class="form-horizontal container">

                                <div class="form-column col-lg-12 col-md-12 col-sm-12">

                                    <div class="form-group">
                                        <div class="col-sm-12">
                                            <table class="table">
                                                <tr>
                                                    <td>

                                                        <div class="form-group">
                                                            <span><b>Message Details: </b></span>
                                                            @foreach (var item in Model.Replies)
                                                            {
                                                                if (item.MessageId == ViewBag.MessageId)
                                                                {

                                                                    @item.MessageDetails
                                                                }
                                                            }
                                                        </div>
                                                    </td>
                                                </tr>
                                                <tr>
                                                    <div class="form-group">
                                                        @using (Html.BeginForm("ReplyMessage", "Message", new { id = "form-reply-message", messageId = @ViewBag.MessageId }, FormMethod.Post))
                                                        {

                                                            if (!ViewData.ModelState.IsValid)
                                                            {
                                                                <div class="row">
                                                                    <div class="col-lg-4 col-md-4 col-sm-4"></div>
                                                                    <div class="col-lg-8 col-md-8 col-sm-8">
                                                                        @Html.ValidationSummary(true)
                                                                    </div>
                                                                </div>
                                                            }
                                                            @Html.HiddenFor(model => model.Reply.MessageId);
                                                            <label class="col-sm-2 ">Reply</label>
                                                            <div class="col-sm-9">
                                                                @Html.TextAreaFor(p => p.Reply.ReplyMessage, new { @rows = 2, @class = "form-control" })
                                                                @Html.ValidationMessageFor(model => model.Reply.ReplyMessage)

                                                            </div>
                                                            <div class="col-sm-1">
                                                                <input type="submit" class="btn btn-primary btn-success" value="Reply" id="btn-reply-message">
                                                            </div>
                                                        }
                                                    </div>
                                                </tr>
                                            </table>

                                            <h4>Replies for the Message</h4>
                                            <table class="table">
                                                @foreach (var item in Model.Replies)
                                                {
                                                    if (item.MessageId == ViewBag.MessageId)
                                                    {
                                                        <tr>
                                                            <td>

                                                                <div>
                                                                    <span><b>Reply Message : </b></span>
                                                                    @item.ReplyMessage
                                                                </div>
                                                                <div>
                                                                    <span><b>Reply From : </b>  </span>
                                                                    @item.ReplyFrom
                                                                </div>
                                                                <div>
                                                                    <span>
                                                                        <b>Reply Date : </b>
                                                                    </span>
                                                                    @item.ReplyDateTime
                                                                </div>
                                                            </td>

                                                        </tr>
                                                    }
                                                }

                                            </table>

                                        </div>

                                    </div>
                                </div>
                            </div>

                        </div>
                    </div>

                <!-- start panel-->
                } 

--code below

我在页面上添加了更多功能,用于删除消息。

<td>
     <div class="text">
     <a class="delete" href="#" title="delete" onclick="messageDelete(@m.Id)"><img style="width: 17px; height: 15px" src="~/Images/no.png" /></a>
      </div>
     </td>
    <td><input type="hidden" id="messageId" name="messageId" value="@m.Id"></td>

 用于删除记录的 jQuery 方法,最终会调用控制器操作,如下所示。

<script>

    function messageDelete(index) {
        bootbox.dialog({
            message: "Are you sure you want to delete the message ?",
            title: "Delete Message Confirmation",
            buttons: {
                success: {
                    label: "Continue",
                    className: "btn-success",
                    callback: function deletemember() {
                        $('#messageId').val(index);
                        $('form#form-message-delete').submit();
                    },
                    danger: {
                        label: "Cancel",
                        className: "btn-danger",
                        callback: function () {
                            bootbox.hideAll();
                        }
                    }
                }
            }
        });

    };
</script>

我还使用了bootbox对话框来确认删除操作。我通过nuget添加了bootbox包,并将其添加到BundleConfig中,以便在整个项目中使用。BundleConfig.csbootbox的定义如下。

    bundles.Add(new ScriptBundle("~/bundles/bootbox").Include("~/Scripts/bootbox.js"));

而在.Layout.cshtml中,我像这样调用它。

 @Scripts.Render("~/bundles/bootbox")

现在让我们来看看Create消息视图,它看起来如下。

这是一个简单的视图,只接收消息的Subject(主题)和Message(消息)。我使用简单的验证规则来验证SubjectMessage的空值。

Create.cshtml文件如下。

@model MessageBoardApp.ViewModels.MessageReplyViewModel

<div class="row">
    <div class="col-12">
        <div class="row-fluid">
            <!-- form panel 1 -->
            <div class="col-lg-12 col-md-12 col-sm-12">
                <!-- panel start-->
                <div class="panel panel-default top">
                    <div class="panel-heading ">
                        <h3 class="panel-title active ">
                            Post New Message
                        </h3>
                    </div>
                 </div>
                <!-- end panel-->
                <div class="panel-body">
                    <div class="form-horizontal container">
                        @using (Html.BeginForm("PostMessage", "Message", FormMethod.Post, new { @id = "form-post-message", @class = "form-horizontal" }))
                        {
                            <div class="form-column col-lg-12 col-md-12 col-sm-12">
                                @Html.ValidationSummary(true, "", new { @class = "text-danger" })
                                <div class="form-group">
                                    <label class="col-sm-4 control-label">Subject</label>
                                    <div class="col-sm-8">

                                        @Html.TextBoxFor(p => p.Message.Subject, new { @class = "form-control" })
                                        @Html.ValidationMessageFor(model => model.Message.Subject)

                                    </div>
                                </div>
                                <div class="form-group">
                                    <label class="col-sm-4 control-label">Message</label>
                                    <div class="col-sm-8">
                                        @Html.TextAreaFor(p => p.Message.MessageToPost, new { @rows = 8, @class = "form-control" })
                                        @Html.ValidationMessageFor(model => model.Message.MessageToPost)
                                    </div>
                                </div>
                                <div class="form-group">
                                    <label class="col-sm-4 control-label"></label>
                                    <div class="col-sm-8">
                                        <input type="submit" class="btn btn-primary right" value="Post Message">
                                    </div>
                                </div>
                            </div>
                        }
                    </div>
                </div>
            </div>
        </div>
    </div>

</div>

 

创建控制器方法

我已经解释了Home Controller中用于显示消息的方法。现在让我们来看看其他方法,例如准备创建视图、发布消息和回复消息。

准备添加新消息的视图非常简单,如下所示。

      public ActionResult Create()
        {
            MessageReplyViewModel vm = new MessageReplyViewModel();
           
            return View(vm);
        }

当用户提交消息时,PostMessage ActionResult使用Entity Framework将消息保存到数据库。我使用了code-first方法,并将数据库迁移到了我的LocalDB服务器。这是保存已发布消息的方法。

       [HttpPost]
        [Authorize]
        public ActionResult PostMessage(MessageReplyViewModel vm)
        {
            var username = User.Identity.Name;
            string fullName = "";
            int msgid = 0;
            if (!string.IsNullOrEmpty(username))
            {
                var user = dbContext.Users.SingleOrDefault(u => u.UserName == username);
                fullName = string.Concat(new string[] { user.FirstName, " ", user.LastName });
            }
            Message messagetoPost = new Message();
            if (vm.Message.Subject != string.Empty && vm.Message.MessageToPost != string.Empty)
            {
                messagetoPost.DatePosted = DateTime.Now;
                messagetoPost.Subject = vm.Message.Subject;
                messagetoPost.MessageToPost = vm.Message.MessageToPost;
                messagetoPost.From = fullName;

                dbContext.Messages.Add(messagetoPost);
                dbContext.SaveChanges();
                msgid = messagetoPost.Id;
            }

            return RedirectToAction("Index", "Home", new { Id = msgid }); 
        }

消息创建后,它会显示在主页上。当用户登录应用程序时,消息会显示在那里,他们可以查看消息并选择是否回复。

当用户点击每条消息右侧的View Reply(查看回复)按钮时,底部的面板(Replies for Message - 消息回复)将显示出来,用户可以回复该消息。

当用户键入回复并点击Reply(回复)按钮时,回复将被发送给消息所有者,告知他们他们的消息收到了回复。然后消息所有者可以登录并查看回复的详细信息。

这是回复消息的控制器方法。

        [HttpPost]
        [Authorize]
        public ActionResult ReplyMessage(MessageReplyViewModel vm, int messageId)
        {
            var username = User.Identity.Name;
            string fullName = "";
            if (!string.IsNullOrEmpty(username))
            {
                var user = dbContext.Users.SingleOrDefault(u => u.UserName == username);
                fullName = string.Concat(new string[] { user.FirstName, " ", user.LastName });
            }
            if (vm.Reply.ReplyMessage != null)
            {
                Reply _reply = new Reply();
                _reply.ReplyDateTime = DateTime.Now;
                _reply.MessageId = messageId;
                _reply.ReplyFrom = fullName;
                _reply.ReplyMessage = vm.Reply.ReplyMessage;
                dbContext.Replies.Add(_reply);
                dbContext.SaveChanges();
            }
            //reply to the message owner          - using email template

            var messageOwner = dbContext.Messages.Where(x => x.Id == messageId).Select(s => s.From).FirstOrDefault();
            var users = from user in dbContext.Users
                        orderby user.FirstName
                        select new
                        {
                            FullName = user.FirstName + " " + user.LastName,
                            UserEmail = user.Email
                        };

            var uemail = users.Where(x => x.FullName == messageOwner).Select(s => s.UserEmail).FirstOrDefault();
            SendGridMessage replyMessage = new SendGridMessage();
            replyMessage.From = new MailAddress(username);
            replyMessage.Subject = "Reply for your message :" + dbContext.Messages.Where(i=>i.Id==messageId).Select(s=>s.Subject).FirstOrDefault();
            replyMessage.Text = vm.Reply.ReplyMessage;

           
            replyMessage.AddTo(uemail);

           
            var credentials = new NetworkCredential("YOUR SENDGRID USERNAME", PASSWORD);
            var transportweb = new Web(credentials);
            transportweb.DeliverAsync(replyMessage);
            return RedirectToAction("Index", "Home", new { Id = messageId });

        }

当任何用户提交回复时,都会向消息所有者发送一封通知电子邮件,以便他们能够得知有回复。

下面是一个这样的回复的例子。

删除消息和回复

如果消息列表中的任何消息与页面的上下文无关或不相关,都可以将其删除。为此,我在消息的右侧添加了一个X按钮,用户可以点击它来删除消息。我没有为该应用程序实现角色,但可以根据用户角色隐藏或显示该按钮。

我使用了bootbox进行删除确认,如下所示,消息及其回复将被删除。点击删除按钮(X)将弹出一个小窗口,用户可以在其中确认或取消对话框。如果点击Continue(继续),则该记录及其回复将从数据库中删除。

兴趣点

  • ViewModel 中包含不同模型的分页列表 - 带分页和不带分页
  • MVC 5, ASPNET Identity 2.0
  • MessageBoardApp 可以通过进行一些修改,集成到任何具有相同 MVC 框架的页面中。

历史

2015-05-23: 初次提交

© . All rights reserved.