ASP.NET 4 Social Networking: Implementing a Complete Messaging System

Exclusive offer: get 50% off this eBook here
ASP.NET 4 Social Networking

ASP.NET 4 Social Networking — Save 50%

A truly hands-on book for ASP.NET 4 Developers

$29.99    $15.00
by Andrew Siemer Atul Gupta Sudhanshu Hate | March 2011 | .NET Web Development

This article by Atul Gupta, Sudhanshu Hate and Andrew Siemer, authors of ASP.NET 4 Social Networking, helps you create a messaging system that will resemble a webbased email application similar to Hotmail or Gmail. We will also learn how to implement the Xinha WYSIWYG editor in a way that can be re-used easily across the site for complex inputs.

Once a user can create and send messages, we will then create a way for other users to receive and read those messages queued up in their inbox. Once we have this messaging subsystem in place we can hook up our other features in a way that they too can send messages—such as when a user accepts a friend request we can show the acceptance of that request in the friend's Inbox. This article will not only allow our users to send messages to each other but also provide our system with a way to communicate with our user base efficiently. This is not only a good feature to have in your community site but also a basic requirement for it.

 

ASP.NET 4 Social Networking

ASP.NET 4 Social Networking

A truly hands-on book for ASP.NET 4 Developers

        Read more about this book      

(For more resources on ASP.NET, see here.)

ASP.NET 4 Social Networking: Implementing a Complete Messaging System

Problem

A basic messaging system should be able to manage messages, senders and recipients, folders that contain the messages, and email notifications. In our case, we are going to try and keep things simple where it makes sense to do so, but in one area, we will do things in a more complicated way simply because it will result in less wear and tear on the overall system. This is how the messages will be delivered to the users.

Rather than following a standard email messaging system where each person gets a physical copy of a message, we are going to build our system in the same way that the MS Exchange server works. We are going to make one copy of a message and subscribe users to that message. So rather than have 50 messages for 50 recipients, we will have one message and 50 recipient subscriptions.

The next problem lies in building a WYSIWYG (what you see is what you get) messaging editor. For this feature, there are many open source WYSIWYG editors; we will use one of those to save us a bit of time. We will be using one of the popular editors—X INHA. This editor can be downloaded for free here at http://xinha.webfactional.com/. You may have seen this editor already as it is widely used across many popular community sites.

Design

Let's take a look at the design of these features.

Messages

Messages are the core of any messaging system. Generally, a message would contain details like the sender of the message, receiver of the message, and other metadata like time sent, server from where it was sent, etc. and the message, subject, and body. In our case, the message will contain the sender, subject, body, and the data sent. It will also contain one additional field, i.e. the type of message (message, friend request, and so on).

We will need to create a page that allows a user to compose a new message (as seen in the image at the start of this article). This interface should also allow a user to add his/her friends easily rather than force them to remember everyone. Also, this interface should allow a user to quickly snap together some HTML without ever having to look at HTML. This can be accomplished with a WYSIWYG editor.

Recipients

As we have already discussed that we are going to move some of the complexity away from the message, following a subscription model instead, you will find that most of the complexity of this system lies around the recipient concepts.

In this case, the recipient subscription is what will be contained in a folder and will have a read status. With this design, we will remove some of the burden from the database. The overhead of doing this of course means that we now need to manage our data closely, as it is kept in many pieces.

A more simple design that would result in more copies of data to be managed would be to create one message for each recipient. This is easier as each message can easily be deleted and moved around without having to worry about the copies of that message of the other recipients. Having said that, if the message is quite large, and more importantly if we were to allow file attachments, all the copies of the messages would be identical for each recipient. This would quickly bloat your database!

Solution

Now let's take a look at our solution.

Implementing the database

First let's take a look at what tables are needed:

Messages

A message will primarily be made up of the subject and its body. In addition to that we will need to know what type of message we are sending so that we can do some more fancy things in the UI down the road. In addition to this, we are going to maintain who owns/created the message at this level.

There aren't really any major complexities to note here other than the fact that the Body is made up of a varchar(MAX) data type. If you feel this is too large for your system, feel free to make it anything you are comfortable with. The value you eventually use will drive the message for your system.

MessageTypes

Message Types allows us to assign a type to our messages. This is purely a lookup table that will allow us to know what the types are during queries. We will keep a list of enums in the code to make the lookups easier from that end.

MessageRecipients

A message recipient is simply the receiving party to the message. But as we try to minimize the data that we manage in our system, the message recipient is also a very important part of the message. In our case, it is the receiving party as well as all the things that the receiving party does with their subscription of that message. We will use this subscription to denote which folder the receiver is keeping the message in, and whether the receiver has read the message or not. Also, if the receiver chooses to delete the message, he/she can just delete the subscription to a message (unless they are the last subscription, in which case we will delete the message as well).

The SQL for this subscription is actually quite straightforward. It tracks a relationship to the message, a relationship to the receiver, which folder the subscription is currently in, and the status of the message for this receiver.

MessageRecipientTypes

The message recipient type allows us to track the receiver of this message addressed in the TO, CC, or BCC fields. Initially, our interface will only have a TO field. We should add this bit of metadata though just in case we want to expand our capabilities down the road! This is another example of a lookup table that we might need to use in the SQL queries. In our case, we will have an enum defined that maintains this lookup for us on the code side.

MessageStatusTypes

MessageStatusTypes allows us to track what a recipient is doing with his/her copy of the message, whether they have read the message, replied to the message, and so on. This is primarily so that we can change the UI to refiect its status to the recipient. However, we could also create a dashboard down the road for the senders of the messages to know whether their message was read or not and by whom (think of all the big brother things one could do...but probably should not do!).

MessageFolders

MessageFolders in our first round of implementation will simply hold copies of new messages in the Inbox and copies of sent messages in the Sent folder. We will also have a trash folder and a spam folder. That said, we always wanted to build a system with the future in mind if it doesn't require a lot of extra work, and so we have also baked in the concept of a user being able to create and manage his/her own folders.

Therefore, rather than just see the MessageFolders table as another lookup table, you will see that there is an IsSystem fiag to denote which folders are to be seen system-wide. And you will see an AccountID column for custom folders so that we know who owns which folders.

Creating the relationships

Once all the tables are created, we can create the relationships.

For this set of tables, we have relationships between the following tables:

  • Messages and MessageRecipients
  • Messages and Accounts
  • Messages and MessageTypes
  • MessageRecipients and MessageRecipientTypes
  • MessageRecipients and MessageFolders
  • MessageRecipients and MessageStatusTypes

Setting up the data access layer

The data access layer in this case is very straightforward. Open up your Fisharoo.edmx file and add all of your new message-oriented tables.

Once you save this, you should now have a list of new domain objects in your arsenal (see the previous screenshot).

Building repositories

With these new tables come some additional repositories. We will create the following repositories.

  • MessageRepository
  • MessageRecipientRepository
  • MessageFolderRepository

A detailed creation of repositories is out of the scope of this article. We will create a method for selecting a single entity by ID, a group of entities by their parents, saving entities, and deleting entities.

Having said that, there are a couple of methods that have something special in the set of repositories. As we are using message subscriptions, we don't necessarily want to delete recipients haphazardly. We may want to delete a recipient, and if that recipient is the last recipient with a subscription to a message, we may also want to delete the message. On the other end of the spectrum, if we do delete a message, we may also want to remove all the recipient subscriptions.

In addition to these different ways of deleting data, we will also run into a scenario where selecting a single entity from our repositories won't be quite good enough. So in this case, we have created an aggregate class that will allow us to select several entities at once for use in our inbox scenarios.

MessageRepository

When we think of a standard inbox, we know that we need to see the messages that we have, who sent them, when they were sent, and at least the subject of their message. In this case, we have discussed two different entities here. When we think about the fact that we also need to know who they were sent to, we have added a third entity. While we could run three separate queries for this data, it would be better for us to run one query (as we would have done in the old days) and return the data that we need in one shot.

What do we do? In this case, we need to create an aggregate. This is a class that contains other entities. We will therefore create a MessageWithRecipient class that will contain the sender's account info, the message, and the recipient. This should provide us with enough data to represent messages in our inbox view later.

Before we write any queries, we first need to create the aggregate.

//Fisharoo/DataAccess/MessageWithRecipient.cs
namespace Fisharoo.DataAccess
{
public class MessageWithRecipient
{
public Account Sender { get; set; }
public Message Message { get; set; }
public MessageRecipient MessageRecipient{ get; set; }
}
}

With this aggregate in place we can now turn our attention to the repository that will get all this data for us.

//Fisharoo/DataAccess/Repositories/MessageRepository.cs
public List<MessageWithRecipient> GetMessagesByAccountID(Int32
AccountID, Int32 PageNumber, MessageFolders Folder)
{
List<MessageWithRecipient> result = new
List<MessageWithRecipient>();
using(FisharooDataContext dc = conn.GetContext())
{
IEnumerable<MessageWithRecipient> messages =
(from r in dc.MessageRecipients
join m in dc.Messages on r.MessageID equals m.MessageID
join a in dc.Accounts on m.SentByAccountID equals
a.AccountID
where r.AccountID == AccountID &&
r.MessageFolderID == (int)Folder
orderby m.CreateDate descending
select new MessageWithRecipient()
{
Sender = a,
Message = m,
MessageRecipient = r
}).Skip((PageNumber - 1)*10).Take(10);
result = messages.ToList();
}
return result;
}

This is a fun method! This method involves selecting a list of our MessageWithRecipient aggregate objects. The LINQ query is joining all the tables that we need and selecting a new instance of the MessageWithRecipient aggregate, that is then populated with the three classes that we need in the aggregate. Additionally, we have introduced some paging logic with the .Skip and .Take methods to produce a subset of the MessageWithRecipient objects.

In addition to the selection method above, we also need to discuss the delete method for this repository. As we have the data holding a subscription to our message data, it is important that we first remove all the subscriptions prior to removing the message itself.

//Fisharoo/DataAccess/Repositories/MessageRepository.cs
public void DeleteMessage(Message message)
{
using (FisharooDataContext dc = conn.GetContext())
{
IEnumerable<MessageRecipient> recipients =
dc.MessageRecipients
.Where(mr => mr.MessageID == message.MessageID);
foreach (MessageRecipient mr in recipients)
{
dc.MessageRecipients.DeleteObject(mr);
}
dc.Messages.DeleteObject(message);
dc.SaveChanges();
}
}

This is easily accomplished by retrieving all the MessageRecipients for the needed MessageID from the MessageRecipients in DataContext. Once we have the list, we iterate over each recipient and remove it from DataContext's MessageRecipients list. Finally, we delete the message and save changes.

MessageRecipientRepository

The message recipient repository is considerably easier. It simply has an altered delete statement to adjust for the fact that if we delete the last subscription to a message, it will amount to deleting the message.

//Fisharoo/DataAccess/Repositories/MessageRecipientRepository.cs
public void DeleteMessageRecipient(MessageRecipient messageRecipient)
{
using (FisharooDataContext dc = conn.GetContext())
{
dc.MessageRecipients.DeleteObject(dc.MessageRecipients.Where
(mr=> mr.MessageRecipientID.Equals
(messageRecipient.MessageRecipientID))
.FirstOrDefault());
//if the last recipient was deleted
//...also delete the message
int RemainingRecipientCount =
dc.MessageRecipients.Where(mr => mr.MessageID ==
messageRecipient.MessageID).Count();
if (RemainingRecipientCount == 0)
{
dc.Messages.DeleteObject(dc.Messages.Where(m
=> m.MessageID == messageRecipient.MessageID).
FirstOrDefault());
}
dc.SaveChanges();
}
}

In this method, we delete the recipient in question. We then get a count of the remaining recipients for the message, which has the last recipient removed. If that count is zero, then there are no more recipients remaining for that message. In that case we perform a delete on that message and remove it from the system as well.

ASP.NET 4 Social Networking A truly hands-on book for ASP.NET 4 Developers
Published: March 2011
eBook Price: $29.99
Book Price: $49.99
See more
Select your format and quantity:
        Read more about this book      

(For more resources on ASP.NET, see here.)

Implementing the services/application layer

Once all the repositories are built for single serving, we can begin to create the services layer. Again, this layer is responsible for assembling aggregates and performing complex actions with our entities. We will create only one service for this article. We will also extend a couple of services.

  • MessageService
  • Email
  • AlertService
  • FriendService

MessageService

The MessageService will help us in one way—sending messages. Keep in mind that to send a message, we will need to create a Message then create a MessageRecipient for the sender's copy, and then create one-to-many MessageRecipients for the receivers of the message. While this is not a complex task, it is a very appropriate series of tasks for a service object!

public void SendMessage(string Body, string Subject, string[] To)
{
Message m = new Message();
m.Body = Body;
m.Subject = Subject;
m.CreateDate = DateTime.Now;
m.MessageTypeID = (int)MessageTypes.Message;
m.SentByAccountID = _userSession.CurrentUser.AccountID;
Int64 messageID = _messageRepository.SaveMessage(m);
//create a copy in the sent items folder for this user
MessageRecipient sendermr = new MessageRecipient();
sendermr.AccountID = _userSession.CurrentUser.AccountID;
sendermr.MessageFolderID = (int) MessageFolders.Sent;
sendermr.MessageRecipientTypeID = (int) MessageRecipientTypes.TO;
sendermr.MessageID = messageID;
sendermr.MessageStatusTypeID = (int)MessageStatusTypes.Unread;
_messageRecipientRepository.SaveMessageRecipient(sendermr);
//send to people in the To field
foreach (string s in To)
{
Account toAccount = null;
if (s.Contains("@"))
toAccount = _accountRepository.GetAccountByEmail(s);
else
toAccount = _accountRepository.GetAccountByUsername(s);
if(toAccount != null)
{
MessageRecipient mr = new MessageRecipient();
mr.AccountID = toAccount.AccountID;
mr.MessageFolderID = (int)MessageFolders.Inbox;
mr.MessageID = messageID;
mr.MessageRecipientTypeID = (int) MessageRecipientTypes.
TO;
mr.MessageStatusTypeID = (int)MessageStatusTypes.Unread;
_messageRecipientRepository.SaveMessageRecipient(mr);
Account acc = _userSession.CurrentUser;
_email.SendNewMessageNotification(acc.FirstName, acc.
LastName, toAccount.Email);
}
}

This should be very straightforward to follow. The first thing we do is spin up a new instance of the Message that we are sending. We then save it via the MessageRepository and get back the new MessageID to work with down the line.

The next task is to make sure that we paste a copy of the message in the sender's Sent Items folder. We do this by creating a MessageRecipient object and tying it to the sender's account. You will notice that we have assigned it to the Sent Items folder using an enum named MessageFolders, which has IDs that map to the MessageFolders lookup table. This enum is stored next to a new domain object named MessageFolder. Check the MessageFolder.cs file for details on this enum.

The next item in the MessageService is assigning the MessageRecipientTypeID to the sender's copy. The MessageRecipientType is to let the display know whether to show the MessageRecipient as a TO, CC, or BCC recipient. This is another enum value that maps back to a lookup table in the database and is in the MessageRecipientType.cs file.

We then move into a foreach loop in the MessageService, that is responsible for determining whether we are looking up a recipient via an email address or a username. It does this by testing the To value of the MessageRecipients to see if it has an ampersand or not, which would correspond to an email address.

Depending on whether the TO value is an email address or a username, we get a copy of an Account object. Once we have a valid Account object to work with, we move on to creating a new MessageRecipient for that Account. You should note that in this implementation of the MessageRecipient, we are placing the Message subscription into the Inbox instead of the Sent Items folder. This MessageRecipient is also of MessageRecipientType TO.

Email

Once the MessageRecipient is successfully created and persisted to the database, we then move on to sending an email notification to the MessageRecipient. The most important thing to note about the additional method of SendNewMessageNotification() is that we are sending a notification—not the whole message!

We mention this because we feel it is important to note that one of the quickest ways to get your sites' IP addresses banned is to be determined to be a sender of spam. Since you are entirely responsible for the content that goes out in your messages, you can't directly control what your users type in a message. So don't risk your reputation by sending the entire contents of their messages!

//Fisharoo/Components/Email.cs
public void SendNewMessageNotification(string FirstName, string
LastName, string ToEmail)
{
foreach (string s in ToEmail.Split(new char[] {',',';'}))
{
string message = FirstName + " " + LastName +
" has sent you a message on " + _configuration.SiteName + "!
Please log in at " + _configuration.SiteName +
" to view the message.<HR>";
SendEmail(s, "", "", FirstName + " " + LastName
+
" has sent you a message on " +
_configuration.SiteName + "!", message);
}
}

This method breaks down all the TO recipients and creates a new email notification using our existing subsystem telling the recipients that they have a new message waiting for them in their Inbox.

AlertService

Creating a placeholder in our AlertService allows us to insert an URL in our alerts that would allow the receiver of an alert to easily click into sending a message to the sender of that alert. We will now create these tools, so that we can fill this placeholder out.

//TODO: MESSAGING - point to send message URL
private string GetSendMessageUrl(Int32 AccountID)
{
return "<a href=\"[rootUrl]/mail/newmessage.aspx?AccountID=" +
AccountID.ToString() + "\">Click here to send message</a>";
}

This method is responsible for creating a link to the new message page passing in the user you want to send a message to. This method is already called from several of our existing AlertService methods.

FriendService

We now need to extend our FriendService.CreateFriendFromFriendInvitation() method. Remember that this method is responsible for creating a friend from an accepted invitation. We now want to add some logic to this so that when a friend request is accepted, we are able to send a new Message to the creator of that friend request letting them know that their request was accepted.

//Fisharoo/BusinessLogic/FriendService.cs
public bool CreateFriendFromFriendInvitation(Guid InvitationKey,
Account InvitationTo, bool ExistingMember)
{
//update friend invitation request
FriendInvitation friendInvitation = _friendInvitationRepository.Ge
tFriendInvitationByGUID(InvitationKey);
//validate if the friend request is for the same person who has
logged in
//however if this is a new member (new registration), then don't
validate as the
//person may use a different email during registration
if (ExistingMember && (friendInvitation.Email != InvitationTo.
Email))
return false;
friendInvitation.BecameAccountID = InvitationTo.AccountID;
_friendInvitationRepository.SaveFriendInvitation(friendInvitati
on);
_friendInvitationRepository.CleanUpFriendInvitationsForThisEmail(fr
iendInvitation);
//create friendship
Friend friend = new Friend();
friend.AccountID = friendInvitation.AccountID;
friend.MyFriendsAccountID = InvitationTo.AccountID;
_friendRepository.SaveFriend(friend);
Account InvitationFrom = _accountRepository.
GetAccountByID(friendInvitation.AccountID);
_alertService.AddFriendAddedAlert(InvitationFrom, InvitationTo);
return true;
//CHAPTER 6
//TODO: MESSAGING - Add message to inbox regarding new friendship!
}

This additional code is going through the motions of creating a new message and a recipient for that message. In this case though, we are giving this message the MessageTypeID of MessageTypes.FriendConfirm (or a friend confirmation). Other than that, this code is straightforward.

ASP.NET 4 Social Networking A truly hands-on book for ASP.NET 4 Developers
Published: March 2011
eBook Price: $29.99
Book Price: $49.99
See more
Select your format and quantity:
        Read more about this book      

(For more resources on ASP.NET, see here.)

Implementing the presentation layer

Now that our framework has been updated to handle the new concept of messaging, let's start creating some UI features so that we can use our new tools. We are going to need at least three pages to really utilize our messaging features. We will need a way to create and send a new message, a way to receive and view the messages, and a way to read an individual message. In addition to the creation of a message, we will need a way to easily choose from our list of friends, the recipients of our new messages. Also, while viewing our list of messages, we will need a way of drilling into different folders of messages. Let's get started!

New message

The UI for the new message page is trivial. It consists of a To field, a Subject field, a message field, and a button to signify that we are ready to send the message. Where things are significantly different in the use of the Xinha WYSIWYG editor! This is a JavaScript library that allows you to transform a multiline text box into a full-featured editor.

ASP.NET 4 Social Networking: Implementing a Complete Messaging System

To get started with the integration of Xinha, we will first need to get the latest code base. This can be acquired from the code base (Ch:6) of this article or by going to http://xinha.webfactional.com/wiki/DownloadsPage. Install this code base into a new Xinha directory off the root of the site.

Then open the Site.Master page. Add the following JavaScript directly after the body:

<script type="text/javascript">
xinha_editors = null;
xinha_editors = xinha_editors ? xinha_editors : [];
</script>

This code is not part of the standard installation process. What it does is allow us to spin up multiple instances of the editor all throughout a single page. You will see this later.

Then further down in our master page, just before the ending body tag, we will insert another huge blob of JavaScript. As this is a very large blob we are not going to show it here. Please open the existing master page from this article to see it.

You will see that this blob is responsible for setting the vast amount of configuration options that Xinha exposes. This code is heavily commented and so should be understandable. Also, the configuration of this package is covered extensively on the net!

The only thing, but definitely the special one, we added to in this configuration is the very first line that sets the base URL of the site. In this case, we changed it to point to the path where XINHA files are installed.

...
_editor_url = "/Scrits/Xinha";
...

This brings us back to our NewMessage.aspx page. Now that we have Xinha installed, we can add a line below our page UI that effectively ties our multiline text box control to the Xinha library. This is done with a snippet of JavaScript.

<script type="text/javascript">
xinha_editors[xinha_editors.length] = 'Content_txtMessage';
</script>

Note that this is using the same xinha_editors variable that we defined initially in the Master page! What we have done here is inserted this new control into an array of Xinha editors. Note the ClientID of the text box that we want associated as an editor. This is different than what we are used to in the previous versions of ASP.NET.

Now, we will move on to the presenter of this page. This page has three primary tasks:

  • To send a message
  • To preload a recipient of a message (if linked to from an Alert) and<
  • To send a reply to a message

This is really the same thing with the exception that to send a reply to a message, we would first have to load that reply. We will get to that down the road!

To send a message, we have to bubble up the button click event through our code behind and into our presenter. We will not be covering concept of MVP here. So, in the presenter, we have added a method called SendMessage(), that takes in the Subject, Message, and an array of To entries

//Fisharoo/Web/Mail/Presenters/NewMessagePresenter.cs
public void SendMessage(string Subject, string Message, string[] To)
{
_messageService.SendMessage(Message,Subject,To);
}

This method then calls the MessageService.SendMessage() method and sends the message.

The preloading of a recipient is handled in the Init() method of the presenter. It checks to see if we have an AccountID in the QueryString (via our WebContext wrapper). If so, it gets the Accounts Username property and adds that to the To field in the UI.

Loading a reply message into the UI is very similar to loading a username. The Init() method checks the WebContext to see if we have a MessageID in the QueryString and loads the previous Message details into the UI. This is done if the page request isn't a post back request.

//Fisharoo/Web/Mail/Presenters/NewMessagePresenter.cs
public void Init(INewMessage view, bool IsPostBack)
{
_view = view;
if(!IsPostBack)
{
if(_webContext.MessageID != 0)
_view.LoadReply(_messageService.GetMessageByMessageID(_
webContext.MessageID,_userSession.CurrentUser.
AccountID));
}
if(_webContext.AccountID != 0)
_view.LoadTo(_accountService.GetAccountByID(
_webContext.AccountID).Username);
}

Friends control

The other important feature that we have as part of the NewMessage.aspx page is the ability to easily select a friend from a list of friends as the recipient of a message—similar to the idea of a contacts book. To achieve this, we will create a user control that lists our friends.

ASP.NET 4 Social Networking: Implementing a Complete Messaging System

Th e UI for this control is just a simple Repeater object that outputs a friend's username. So let's take a look at what populates the UI. In the Init() method in the FriendPresenter.cs file, we load all the users' friends and bind to the repeater control in the UI.

//Fisharoo/Web/Mail/UserControls/Presenters/FriendsPresenter.css
public void Init(IFriends view)
{
_view = view;
List<Account> accounts =
_friendService.GetFriendsAccountsByAccountID(
_userSession.CurrentUser.
AccountID);
view.LoadFriends(Translator.AccountsToUserNames(accounts));
}

The Translator.AccountsToUserNames is taking in the accounts collection and returning a string of usernames of those accounts, which is what is finally bound to the repeater in the UI.

In the code behind for our friend control, we insert a snippet of JavaScript to allow us to click on a friend and carry his/her username into our To field in our NewMessage UI.

//Fisharoo/Web/Mail/UserControls/Friends.aspx.cs
public void repFriends_ItemDataBound(object sender,
RepeaterItemEventArgs e)
{
if(e.Item.ItemType == ListItemType.Item || e.Item.ItemType ==
ListItemType.AlternatingItem)
{
HyperLink linkFriend = e.Item.FindControl("linkFriend") as
HyperLink;
linkFriend.Attributes.Add("OnClick",
"javascript:document.forms[0].
Content_txtTo.value += '" +
e.Item.DataItem + ";';");
}
}

No te that we are adding an attribute for the OnClick event of our link that will call a JavaScript to move the username to our To field with a semicolon delimiter.

This gives us a list of friends, but how do we tie that back into our UIfi Open the NewMessage.aspx page. We will need to register the new control in the page and then add a reference to it in the page.

Just below the Page directive add the following code:

<%@ Register Src="~/Mail/UserControls/Friends.ascx"
TagPrefix="Fisharoo" TagName="Friends" %>

Then add a new Content section to our page where the friends control will live:

<asp:Content ContentPlaceHolderID="LeftNavTop" runat="server">
<Fisharoo:Friends ID="friends1" runat="server" />
</asp:Content>

This inserts the friends control into our left navigation bar as defined in the master page!

Default (or Inbox)

Now that we can successfully send a message, we need a way to receive those messages. This is done in folder view of our messages. This is really just a page that shows a list of messages, who sent them, and when. We can either click on the sender to go to their profile page, or can click on the message to view it. We can also navigate through pages of messages with a list of page navigation links and we will have the ability to delete one or many selected messages. Additionally, we will create a Folders control that will allow us to navigate through our various folders to see the messages in those containers.

The UI for this page has just a repeater that iterates through the MessageWithRecipient collection that is passed to it. So let's take a look at the presenter, which actually populates our list of messages. As always this is accomplished in the Init() method of the presenter.

//Fisharoo/Web/Mail/Presenters/DefaultPresenter.cs
public void Init(IDefault view)
{
_view = view;
if (_userSession.CurrentUser != null)
{
List<MessageWithRecipient> list =
messageService.GetMessagesByAccountID(
userSession.CurrentUser.AccountID, webContext.Page,
(MessageFolders)_webContext.FolderID);
_view.LoadMessages(
Translator.MessageWithRecipientToPEMessage(list));
view.DisplayPageNavigation(messageService.GetPageCount(
(MessageFolders)webContext.FolderID,
userSession.CurrentUser.AccountID),
(MessageFolders) _webContext.FolderID, webContext.
Page);
}
}

We first check to see that the user of the page is actually logged in. We then get a list of messages by the user's AccountID. We have a presentation entity PEMessage (HelperClasses folder) that we have defined which has only those fields that we want to show in the UI. We also get a page count (the number of pages of messages that we have to navigate through).

Loading messages into the UI is simply a matter of binding the DataSource to the repeater.

public void LoadMessages(List<PEMessage> Messages)
{
repMessages.DataSource = Messages;
repMessages.DataBind();
}

As this requires no further explanation, let's jump right into how we go about building our page navigation. Recall that in the Init() of our presenter we had a call into _view.DisplayPageNavigation(), which received the PageCount of the folder we were working with, and the current page we were viewing. Here is that method:

public void DisplayPageNavigation(Int32 PageCount, MessageFolders
folder, Int32 CurrentPage)
{
if(PageCount == CurrentPage)
linkNext.Visible = false;
if (CurrentPage == 1)
linkPrevious.Visible = false;
linkNext.NavigateUrl = "~/mail/default.aspx?folder=" + ((int)
folder).ToString() + "&page=" +
(CurrentPage + 1).ToString();
linkPrevious.NavigateUrl = "~/mail/default.aspx?folder=" + ((int)
folder).ToString() + "&page=" +
(CurrentPage - 1).ToString();
for(int i = 1; i<=PageCount;i++)
{
HyperLink link = new HyperLink();
link.Text = i.ToString();
link.NavigateUrl = "~/mail/default.aspx?folder=" +
((int)folder).ToString() + "&page=" + i.ToString();
phPages.Controls.Add(link);
phPages.Controls.Add(new LiteralControl("&nbsp;"));
}
}

Th is chunk of code interacts with three controls in our UI—two hyperlinks, one that displays Previous and one displaying Next, and a PlaceHolder control that will hold the individual page numbers of all the pages for this data set.

This method initially determines if we should show the Next or Previous links based on our current page and our total page count. We then hook up the navigation property of each of those links to take us to the next page or the previous page of data. After that we use a for loop to iterate through all the possible pages, from 1 to PageCount, making a new hyperlink for each iteration that contains the location of that page.

The other feature of this page is the ability to delete messages—as many or as few as we like. This is primarily achieved with a helper function in the code behind of the view that extracts all the messages that are selected, that can be called from within the presenter. This allows the presenter to remain in control!

public List<Int32> ExtractSelectedMessages()
{
List<Int32> result = new List<Int32>();
foreach (RepeaterItem item in repMessages.Items)
{
if(item.ItemType == ListItemType.Item || item.ItemType ==
ListItemType.AlternatingItem)
{
CheckBox chkMessage = item.FindControl("chkMessage") as
CheckBox;
Int32 messageID =
Convert.ToInt32(chkMessage.Attributes["MessageID"]);
if(chkMessage.Checked)
result.Add(messageID);
}
}
return result;
}

This method iterates through all the check boxes to see if they are selected or not. If they are, then it extracts the MessageID from an attribute that was created when we loaded the display. A collection of MessageIDs is then returned to the caller.

This then brings us to the Delete method in the presenter. It is called from a button click event that is bubbled up to the presenter, which then calls into the view to get a list of selected MessageIDs. Then using the MessageService we get a copy of that message. With the copy, we then call into the MessageRecipientService.DeleteMessageRecipient() method and delete each selected message.

Folders

Let's now look at the UI for the Folders user control. This UI is also made up of a Repeater that displays the bound data. In this case, we are displaying folders as hyperlinks, which then link to the same Default.aspx page, but additionally pass in the folder that we are interested in viewing.

ASP.NET 4 Social Networking: Implementing a Complete Messaging System

Let's quickly look at the presenter code

//Fisharoo/Web/Mail/UserControls/Presenters/FolderPresenter.cs
public void Init(IFolders view)
{
_view = view;
MEFManager.Compose(this);
_view.LoadFolders(_
messageFolderservice.GetMessageFoldersByAccountID(
_userSession.CurrentUser.AccountID));
}

I n this case, we are calling into the MessageFolderService to get a list of folders for this user. We bind that directly to the UI through our view. The view then iterates through the data in our repeater. In our ItemDataBound method—in the code behind of the view—we update each hyperlink in the UI.

//Fisharoo/Web/Mail/UserControls/Folders.ascx.cs
protected void repFolders_ItemDataBound(object sender,
RepeaterItemEventArgs e)
{
if (e.Item.ItemType == ListItemType.Item || e.Item.ItemType ==
ListItemType.AlternatingItem)
{
HyperLink linkFolder = e.Item.FindControl("linkFolder") as
HyperLink;
linkFolder.Text =
((MessageFolder)e.Item.DataItem).FolderName;
linkFolder.NavigateUrl = "~/Mail/Default.aspx?folder=" +
((MessageFolder) e.Item.DataItem).MessageFolderID.ToString();
linkFolder.Attributes.Add("FolderID",
((MessageFolder)e.Item.DataItem).
MessageFolderID.ToString());
}
}

This creates a list of folders for us in a control. But how do we get it into our UIfi To do this, we have to go back to our Default.aspx page. This will be done exactly the same way we did for our friends control in our new message page. Open the default page so that we can add the Folders control.

<%@ Register Src="~/Mail/UserControls/Folders.ascx"
TagPrefix="Fisharoo" TagName="Folders" %>
<asp:Content ContentPlaceHolderID="LeftNavTop" runat="server">
<Fisharoo:Folders id="Folders1"
runat="server"></Fisharoo:Folders>
</asp:Content>

Read message

Reading a message is not that difficult at all. We are just loading a message into a static UI based on the MessageID that is passed into the page. Let's first discuss the UI (which could easily be made more complex down the road!). This UI will consist of a From Label, a Subject Label, a Message Label, and a Reply button.

Here is the message view:

ASP.NET 4 Social Networking: Implementing a Complete Messaging System

And here is what will be seen after replying to the message:

As usual, as all the leg work is done in the presenter, let's jump straight to it.

//Fisharoo/Web/Mail/Presenters/ReadMessagePresenter.cs
public void Init(IReadMessage view)
{
_view = view;
_view.LoadMessage(_messageService.GetMessageByMessageID(_webContex
t.MessageID,_userSession.CurrentUser.AccountID));
}

As you can see here, we are populating the UI by getting the message based on the ID and then passing it to the view for display.

For the Reply button, we will bubble up the click event into the presenter. The presenter then makes a call to the Redirector class that passes the current MessageID to the NewMessage page.

Summary

In this article, we have built an entire messaging facility. This section has gone over extending the framework to allow for the creation and retrieval of messages and all the related items of a message. We then covered creating a UI to allow users to create and send messages to the other users of the system. Next, we covered how to receive and read those messages.

This article will not only allow our users to send messages to each other but also provide our system with a way to communicate with our user base efficiently. This is not only a good feature to have in your community site but also a basic requirement for it.


Further resources on this subject:


About the Author :


Andrew Siemer

Andrew Siemer is currently the enterprise architect at OTX Research. He has worked as a software engineer, enterprise architect, trainer, and author since 1998 when he got out of the Army. Andrew has consulted with many companies on the topics of e-commerce, social networking, and business systems. To name a few, he has worked with eUniverse (AllYouCanInk.com), PointVantage (MyInks.com), Callaway Golf (CallawayConnect.com), Guidance Software (GuidanceSoftware.com), and Intermix Media (Grab.com, AmericanIdol.com, FoxSports.com, FlowGo.com). In addition to his daily duties he also offers various classes in .NET, C#, and other web technologies to local students in his area as well as blogging in his *free* time.

Atul Gupta

Atul Gupta is Principal Technology Architect at the Microsoft Technology Center, Infosys Technologies Limited. He has over 15 years of experience working on Microsoft technologies. His expertise spans User Interface technologies and he has worked extensively withASP.NET. His current focus is on Windows Presentation Foundation (WPF), Silverlight and other technologies like Surface, Pivot, Windows Touch and rich data visualization.

In his career spanning over 15 years, Atul has also worked on COM, DCOM, C, C++ before getting onto .NET in year 2001. He has worked on all .NET version and alongside worked on Server products like Commerce Server and BizTalk Server. He has authored papers and handbooks that are available at Infosys’ Technology Showcase (http://www.infosys.com/microsoft/resour ... wcase.aspx). He also blogs along with other Infosys colleagues at http://blogs.infosys.com/microsoft.

His involved in community activities like forums, speaking session as part of Virtual TechDays and other such events got him the Microsoft Most Valuable Professional Award for 6 years in a row.

Sudhanshu Hate

Sudhanshu Hate is Senior Technology Architect with Microsoft Technology Center (MTC), Infosys Technologies Limited. He has over 12 years of industry experience with last seven years on Microsoft .NET

Currently his technology area of interestare .NET 4core framework and server side technologies such as WCF 4, WF 4, Entity Framework 4, WCF Data Services and App Fabric to name a few.

Sudhanshu has authored papers that are available at Infosys’ Technology Showcase (http://www.infosys.com/microsoft/resour ... wcase.aspx), presented in external forums such as Microsoft Virtual TechDays, 3rdIndiaSoftware Engineering Conference (ISEC 2010) and blogs at http://blogs.infosys.com/microsoft

Prior to this, Sudhanshu led Business Intelligence and Legacy Modernization solution development initiatives, and consulted to fortune 500 customers in EMEA and US across .NET, J2EE, ORACLE and Delphi technologies.

Books From Packt


ASP.NET MVC 2 Cookbook
ASP.NET MVC 2 Cookbook

ASP.NET Site Performance Secrets
ASP.NET Site Performance Secrets

Microsoft Windows Workflow Foundation 4.0 Cookbook
Microsoft Windows Workflow Foundation 4.0 Cookbook

.NET Compact Framework 3.5 Data Driven Applications
.NET Compact Framework 3.5 Data Driven Applications

Elgg 1.8 Social Networking
Elgg 1.8 Social Networking

Pligg 1.1 Social Networking
Pligg 1.1 Social Networking

PHP 5 Social Networking
PHP 5 Social Networking

ASP.NET jQuery Cookbook
ASP.NET jQuery Cookbook


No votes yet

Post new comment

CAPTCHA
This question is for testing whether you are a human visitor and to prevent automated spam submissions.
5
h
c
y
T
D
Enter the code without spaces and pay attention to upper/lower case.
Code Download and Errata
Packt Anytime, Anywhere
Register Books
Print Upgrades
eBook Downloads
Video Support
Contact Us
Awards Voting Nominations Previous Winners
Judges Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software
Resources
Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software