Click here to Skip to main content
15,867,686 members
Articles / Web Development / HTML5

MessageBoardApp using MVC 5

Rate me:
Please Sign up or sign in to vote.
5.00/5 (16 votes)
23 May 2015CPOL7 min read 36.5K   4.6K   31   4
Simple Message/Reply app using MVC 5

Introduction

Messaging within a web site plays an important role in communicating ideas among the users. Within the pages, there might some places where the users need to consult with others for certain matters. Rather than sending email or calling via phones,  a messaging system can be added where a message can be posted and replies can be sent for the messages regarding the subject matter concerned. 

Background

In this article, I will explain a simple MessageBoard App where messages can be posted/created and replies can be sent for the messages. I have used SendGrid SMTP client for sending the emails to the message owners when a reply is made.

I have used MVC 5 in Visual Studio 2013  to create the demo app where I have only used two views - one for the message display and the other one for the message creation.   I have used PagedList to do the paging for displaying fixed number of rows for the messages.  For using paging in views I followed the following two references for using PagedList in MVC

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

In the first reference link above, the PageList has been used for single model, where there is a solution for using PageList in ViewModel in the later reference link where multiple models are combined and paging can be done for particular model in the ViewModel.

Description

The MessageBoard App will require user to be logged in  to create messages or reply to the messages.  When the user logins first time to the application the application will be resembled as below if there are already some messages created by some other users.

Image 1

Below here, on the left side is the home page with some messages with replies for them. On the right side the view is for creating messages. From the home view if a user clicks Post New Message link the user will be directed to the Create page and after the user submits the page, it will redirect the user to the home with the message that has been submitted listed to the messages list.

 

Image 2

Now let us look at how to create the app from scratch and run it.

Creating the Project

I have created a brand new MVC 5 web project in Visual Studion 2013 and named it as MessageBoard App. After the project has been created it looks as below in the Solution Explorer.

Image 3

Setting the Users for the MessageBoardApp

ASPNET Identity 2.0 with MVC 5 does not uses user's Full Name in the database. In our messaging system we will use user full names to easily identify the users who are creating messages and sending replies for the messages.

For this purposes, I have created an ApplicationBaseController class and updated the .LoginPartial.cshtml  so that it can display the full user name instead of the default user email.

I have a tip to the CodeProject for this at : http://www.codeproject.com/Tips/991663/Displaying-User-Full-Name-instead-of-User-Email-in

For our application the ApplicationBaseController looks as below:

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()
        {

        }
    }
}

 

Having done this, we can now create the models and viewmodels for our Messageboard App.

Creating Model

I have created two models: Message and Reply as usual in the Models fodler. The Message class looks as below:

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

    }
}

And the Reply  class looks as below:

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

There is nothing special here in the class definitions so far you can see.

Creating the ViewModel

I have created a new ViewModels  folder and a new class called MessageReplyViewModel which has the following methods and properties in it as below:

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

    }
}

Notice I have used

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

for Messages within the MessageReplyViewModel where as for Replies I have not used PagedList although both of them will be used in the same view.  I will explain it in the controller action and within the view how to use the Paging.

 

Image 4

Creating the Views

As I said earlier I will be using only two views, displaying the messages where user willl have the option to reply the messages within the same view and the other one is for creating new messages. The first view where the messages are displayed, I have used the PagedList in ViewModel where one of the model uses PagedList but the other one does not .

To use PagedList in the view  we need to define the followings on the top of the view page.

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

After that we need to setup the page size and page numbers in the controller from where the data is being passed through.

Let us have a look to the controller method.

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
 }

We can see the ActionResult above has some settings about the pagesizes and page numbers for the view. Also it counts the number of total messages that are already in the database. After setting these up we then can call them in the view as below:

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

To be clear, the output below from the home page (list of messages)

Image 5

has been defined in the <div>below in Index.cshtml.

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

The Replies for Messages  has panel has two parts: one for the Reply TextBox, Reply Button with the Message Detials which is being displayed based on the selection of View Reply from the top panel for a particular message.

The other part contains existing replies for the message with the details of each reply.

In controller method, I have defined the view for the replies as below:

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

 

To render the view with the controller method, the following code serves the purpose.

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

I have added one more thing to the page for deleting the messages.

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

 The  jquery method to delete the record which eventully calls the controller action is as below:

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

I have also used bootbox dialog for confirming the delete action. I have added to bootbox  package via nuget  and added it to the BundleConfig so that it can be used for the whole project. The definition of bootbox in BundleConfig.cs  as below:

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

And in .Layout.cshtml  I have called it as:

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

Now let us look at the Create Message view now, which will look as below:

Image 6

This is a simple view only takes the message Subject and Message. I have used the simple validation rules to validate the empty values for Subject and Message.

The Create.cshtml file looks as below:

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

 

Creating Controller Methods

I have already explained the method in the Home Controller for displaying the message. Now let us have a look to the other methods such as preparing the create view, post message and reply message.

To prepare the view for adding new message is very simple as below:

public ActionResult Create()
  {
      MessageReplyViewModel vm = new MessageReplyViewModel();

      return View(vm);
  }

When a user submits a message, the PostMessage ActionResult saves the message to the database using Enitty Framework. I have used code-first approach and migrated the database to my LocalDB  server. Here is the method for saving the posted message.

[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 });
 }

When the message is created, it is displayed on the home page. When  a user logins to the application the messages are displayed there and he/she can view the message and reply if he/she intends to do so.

When a user clicks View Reply  button from the right side of each of the messages, the bottom panel (Replies for Message) is displayed and user can send reply for the message.

Image 7

When a user types the reply and hits the Reply button, the reply is sent to the message owner to inform that a reply has been posted for his/her message. Then the message owner can login and see the details of the reply.

Here is the controller method for reply a message

[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 });

}

When a reply is submitted by any user, a notification email is sent to the message owner so that he/she can be informed about the reply.

Here below is an example of such reply:

Image 8

Deleting Message and Replies

Any message in the message list can be deleted if it is not related or relevant for the context of the page. To do this I have added a X button on the right side of the message so the user can click it to delete the message. I have not implemented the roles for the application, but based on user role, the button can be hidden or visible.

I have used bootbox for the delete confirmation as below where the message and the replies of the message will be deleted. Clicking the delete button (X) will popup the small window where the user can confirm or cancel the dialog. If Continue is clicked the record will be deleted from the database along with the replies for it.

Image 9

Points of Interests

  • PagedList for ViewModel having different models - with paging and without paging
  • MVC 5, ASPNET Identity 2.0
  • MessageBoardApp can be integrated with any page with little bit modifications having the same MVC frameworks

History

2015-05-23: Initial submission

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
Australia Australia
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
Questionhow to get rid of identity module and default login? Pin
Member 121861786-May-16 0:02
Member 121861786-May-16 0:02 
Questionwhat about database first created??!! Pin
Member 121861782-May-16 21:56
Member 121861782-May-16 21:56 
QuestionServer Error in '/' Application. Pin
Member 1235414826-Feb-16 20:15
Member 1235414826-Feb-16 20:15 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.