17 min read

Implementing the presentation layer

Now that we have the base framework in place, we can start to discuss what it will take to put it all together.

Searching for friends

Let’s see what it takes to implement a search for friends.

SiteMaster

Let’s begin with searching for friends. We haven’t covered too much regarding the actual UI and nothing regarding the master page of this site. Putting in simple words, we have added a text box and a button to the master page to take in a search phrase. When the button is clicked, this method in the MasterPage code behind is fired.

protected void ibSearch_Click(object sender, EventArgs e)
{
_redirector.GoToSearch(txtSearch.Text);
}

As you can see it simply calls the Redirector class and routes the user to the Search.aspx page passing in the value of txtSearch (as a query string parameter in this case).

public void GoToSearch(string SearchText)
{
Redirect("~/Search.aspx?s=" + SearchText);
}

Search

The Search.aspx page has no interface. It expects a value to be passed in from the previously discussed text box in the master page. With this text phrase we hit our AccountRepository and perform a search using the Contains() operator. The returned list of Accounts is then displayed on the page. For the most part, this page is all about MVP (Model View Presenter) plumbing. Here is the repeater that displays all our data.

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

...

<asp:Repeater ID="repAccounts" runat="server"
OnItemDataBound="repAccounts_ItemDataBound">
<ItemTemplate>
<Fisharoo:ProfileDisplay ShowDeleteButton="false"
ID="pdProfileDisplay" runat="server">
</Fisharoo:ProfileDisplay>
</ItemTemplate>
</asp:Repeater>

 ASP.NET 3.5 Social Networking

The fun stuff in this case comes in the form of the ProfileDisplay user control that was created so that we have an easy way to display profile data in various places with one chunk of reusable code that will allow us to make global changes.

 ASP.NET 3.5 Social Networking

A user control is like a small self-contained page that you can then insert into your page (or master page). It has its own UI and it has its own code behind (so make sure it also gets its own MVP plumbing!). Also, like a page, it is at the end of the day a simple object, which means that it can have properties, methods, and everything else that you might think to use.

Once you have defined a user control you can use it in a few ways. You can programmatically load it using the LoadControl() method and then use it like you would use any other object in a page environment. Or like we did here, you can add a page declaration that registers the control for use in that page. You will notice that we specified where the source for this control lives. Then we gave it a tag prefix and a tag name (similar to using asp:Control). From that point onwards we can refer to our control in the same way that we can declare a TextBox!

You should see that we have <Fisharoo:ProfileDisplay … />. You will also notice that our tag has custom properties that are set in the tag definition. In this case you see ShowDeleteButton=”false”. Here is the user control code in order of display, code behind, and the presenter:

//UserControls/ProfileDisplay.ascx
<%@ Import namespace="Fisharoo.FisharooCore.Core.Domain"%>
<%@ Control Language="C#" AutoEventWireup="true"
CodeBehind="ProfileDisplay.ascx.cs"
Inherits="Fisharoo.FisharooWeb.UserControls.ProfileDisplay" %>
<div style="float:left;">
<div style="height:130px;float:left;">
<a href="/Profiles/Profile.aspx?AccountID=<asp:Literal
id='litAccountID' runat='server'></asp:Literal>">
<asp:Image style="padding:5px;width:100px;height:100px;"
ImageAlign="Left" Width="100"
Height="100" ID="imgAvatar"
ImageUrl="~/images/ProfileAvatar/ProfileImage.aspx"
runat="server" /></a>
<asp:ImageButton ImageAlign="AbsMiddle" ID="ibInviteFriend"
runat="server" Text="Become Friends"
OnClick="lbInviteFriend_Click"
ImageUrl="~/images/icon_friends.gif"></asp:ImageButton>
<asp:ImageButton ImageAlign="AbsMiddle" ID="ibDelete"
runat="server" OnClick="ibDelete_Click"
ImageUrl="~/images/icon_close.gif" /><br />
<asp:Label ID="lblUsername" runat="server"></asp:Label><br />
<asp:Label ID="lblFirstName" runat="server"></asp:Label>
<asp:Label ID="lblLastName" runat="server"></asp:Label><br />
Since: <asp:Label ID="lblCreateDate"
runat="server"></asp:Label><br />
<asp:Label ID="lblFriendID" runat="server"
Visible="false"></asp:Label>
</div>
</div>

//UserControls/ProfileDisplay.ascx.cs
using System;
using System.Collections;
using System.Configuration;
using System.Data;
using System.Linq;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Xml.Linq;
using Fisharoo.FisharooCore.Core.Domain;
using Fisharoo.FisharooWeb.UserControls.Interfaces;
using Fisharoo.FisharooWeb.UserControls.Presenters;

namespace Fisharoo.FisharooWeb.UserControls
{
public partial class ProfileDisplay : System.Web.UI.UserControl,
IProfileDisplay
{
private ProfileDisplayPresenter _presenter;
protected Account _account;

protected void Page_Load(object sender, EventArgs e)
{
_presenter = new ProfileDisplayPresenter();
_presenter.Init(this);
ibDelete.Attributes.Add("onclick","javascript:return
confirm('Are you sure you want
to delete this friend?')");
}

public bool ShowDeleteButton
{
set
{
ibDelete.Visible = value;
}
}

public bool ShowFriendRequestButton
{
set
{
ibInviteFriend.Visible = value;
}
}
public void LoadDisplay(Account account)
{
_account = account;
ibInviteFriend.Attributes.Add("FriendsID",_account.AccountID.ToString());
ibDelete.Attributes.Add("FriendsID",
_account.AccountID.ToString());
litAccountID.Text = account.AccountID.ToString();
lblLastName.Text = account.LastName;
lblFirstName.Text = account.FirstName;
lblCreateDate.Text = account.CreateDate.ToString();
imgAvatar.ImageUrl += "?AccountID=" +
account.AccountID.ToString();
lblUsername.Text = account.Username;
lblFriendID.Text = account.AccountID.ToString();
}

protected void lbInviteFriend_Click(object sender,
EventArgs e)
{
_presenter = new ProfileDisplayPresenter();
_presenter.Init(this);
_presenter.SendFriendRequest(Convert.ToInt32(lblFriendID.Text));
}

protected void ibDelete_Click(object sender, EventArgs e)
{
_presenter = new ProfileDisplayPresenter();
_presenter.Init(this);
_presenter.DeleteFriend(Convert.ToInt32(lblFriendID.Text));
}
}
}

//UserControls/Presenter/ProfileDisplayPresenter.cs
using System;
using System.Data;
using System.Configuration;
using System.Linq;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Xml.Linq;
using Fisharoo.FisharooCore.Core;
using Fisharoo.FisharooCore.Core.DataAccess;
using Fisharoo.FisharooWeb.UserControls.Interfaces;
using StructureMap;

namespace Fisharoo.FisharooWeb.UserControls.Presenters
{
public class ProfileDisplayPresenter
{
private IProfileDisplay _view;
private IRedirector _redirector;
private IFriendRepository _friendRepository;
private IUserSession _userSession;

public ProfileDisplayPresenter()
{
_redirector = ObjectFactory.GetInstance<IRedirector>();
_friendRepository =
ObjectFactory.GetInstance<IFriendRepository>();
_userSession = ObjectFactory.GetInstance<IUserSession>();
}

public void Init(IProfileDisplay view)
{
_view = view;
}

public void SendFriendRequest(Int32 AccountIdToInvite)
{
_redirector.GoToFriendsInviteFriends(AccountIdToInvite);
}

public void DeleteFriend(Int32 FriendID)
{
if (_userSession.CurrentUser != null)
{
_friendRepository.DeleteFriendByID(_userSession.CurrentUser.AccountID
, FriendID);
HttpContext.Current.Response.Redirect(HttpContext.Current.Request.Raw
Url);
}
}
}
}

All this logic and display is very standard. You have the MVP plumbing, which makes up most of it. Outside of that you will notice that the ProfileDisplay control has a LoadDisplay() method responsible for loading the UI for that control. In the Search page this is done in the repAccounts_ItemDataBound() method.

protected void repAccounts_ItemDataBound(object sender, 
RepeaterItemEventArgs e)
{
if(e.Item.ItemType == ListItemType.Item || e.Item.ItemType ==
ListItemType.AlternatingItem)
{
ProfileDisplay pd = e.Item.FindControl("pdProfileDisplay") as
ProfileDisplay;
pd.LoadDisplay((Account)e.Item.DataItem);
if(_webContext.CurrentUser == null)
pd.ShowFriendRequestButton = false;
}
}

The ProfileDisplay control also has a couple of properties one to show/hide the delete friend button and the other to show/hide the invite friend button. These buttons are not appropriate for every page that the control is used in. In the search results page we want to hide the Delete button as the results are not necessarily friends. We would want to be able to invite them in that view. However, in a list of our friends the Invite button (to invite a friend) would no longer be appropriate as each of these users would already be a friend. The Delete button in this case would now be more appropriate.

Clicking on the Invite button makes a call to the Redirector class and routes the user to the InviteFriends page.

//UserControls/ProfileDisplay.ascx.cs
public void SendFriendRequest(Int32 AccountIdToInvite)
{
_redirector.GoToFriendsInviteFriends(AccountIdToInvite);
}

//Core/Impl/Redirector.cs
public void GoToFriendsInviteFriends(Int32 AccoundIdToInvite)
{
Redirect("~/Friends/InviteFriends.aspx?AccountIdToInvite=" +
AccoundIdToInvite.ToString());
}

Inviting your friends

This page allows us to manually enter email addresses of friends whom we want to invite. It is a standard From, To, Message format where the system specifies the sender (you), you specify who to send to and the message that you want to send.

//Friends/InviteFriends.aspx
<%@ Page Language="C#" MasterPageFile="~/SiteMaster.Master"
AutoEventWireup="true" CodeBehind="InviteFriends.aspx.cs"
Inherits="Fisharoo.FisharooWeb.Friends.InviteFriends" %>
<asp:Content ContentPlaceHolderID="Content" runat="server">
<div class="divContainer">
<div class="divContainerBox">
<div class="divContainerTitle">Invite Your Friends</div>
<asp:Panel ID="pnlInvite" runat="server">
<div class="divContainerRow">
<div class="divContainerCellHeader">From:</div>
<div class="divContainerCell"><asp:Label
ID="lblFrom" runat="server"></asp:Label></div>
</div>
<div class="divContainerRow">
<div class="divContainerCellHeader">To:<br /><div
class="divContainerHelpText">(use commas
to<BR />separate emails)</div></div>
<div class="divContainerCell"><asp:TextBox
ID="txtTo" runat="server"
TextMode="MultiLine" Columns="40"
Rows="5"></asp:TextBox></div>
</div>
<div class="divContainerRow">
<div
class="divContainerCellHeader">Message:</div>
<div class="divContainerCell"><asp:TextBox
ID="txtMessage" runat="server"
TextMode="MultiLine" Columns="40"
Rows="10"></asp:TextBox></div>
</div>
<div class="divContainerFooter">
<asp:Button ID="btnInvite" runat="server"
Text="Invite" OnClick="btnInvite_Click" />
</div>
</asp:Panel>
<div class="divContainerRow">
<div class="divContainerCell"><br /><asp:Label
ID="lblMessage" runat="server">
</asp:Label><br /><br /></div>
</div>
</div>
</div>
</asp:Content>

Running the code will display the following:

 ASP.NET 3.5 Social Networking

This is a simple page, so the majority of the code for it is MVP plumbing. The most important part to notice here is that when the Invite button is clicked the presenter is notified to send the invitation.

//Friends/InviteFriends.aspx.cs
using System;
using System.Collections;
using System.Configuration;
using System.Data;
using System.Linq;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Xml.Linq;
using Fisharoo.FisharooWeb.Friends.Interface;
using Fisharoo.FisharooWeb.Friends.Presenter;

namespace Fisharoo.FisharooWeb.Friends
{
public partial class InviteFriends : System.Web.UI.Page,
IInviteFriends
{
private InviteFriendsPresenter _presenter;
protected void Page_Load(object sender, EventArgs e)
{
_presenter = new InviteFriendsPresenter();
_presenter.Init(this);
}

protected void btnInvite_Click(object sender, EventArgs e)
{
_presenter.SendInvitation(txtTo.Text,txtMessage.Text);
}

public void DisplayToData(string To)
{
lblFrom.Text = To;
}

public void TogglePnlInvite(bool IsVisible)
{
pnlInvite.Visible = IsVisible;
}

public void ShowMessage(string Message)
{
lblMessage.Text = Message;
}

public void ResetUI()
{
txtMessage.Text = "";
txtTo.Text = "";
}
}
}

Once this call is made we leap across to the presenter (more plumbing!).

//Friends/Presenter/InviteFriendsPresenter.cs
using System;
using System.Data;
using System.Configuration;
using System.Linq;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Xml.Linq;
using Fisharoo.FisharooCore.Core;
using Fisharoo.FisharooCore.Core.DataAccess;
using Fisharoo.FisharooCore.Core.Domain;
using Fisharoo.FisharooWeb.Friends.Interface;
using StructureMap;

namespace Fisharoo.FisharooWeb.Friends.Presenter
{
public class InviteFriendsPresenter
{
private IInviteFriends _view;
private IUserSession _userSession;
private IEmail _email;
private IFriendInvitationRepository
_friendInvitationRepository;
private IAccountRepository _accountRepository;
private IWebContext _webContext;
private Account _account;
private Account _accountToInvite;

public void Init(IInviteFriends view)
{
_view = view;
_userSession = ObjectFactory.GetInstance<IUserSession>();
_email = ObjectFactory.GetInstance<IEmail>();
_friendInvitationRepository =
ObjectFactory.GetInstance<
IFriendInvitationRepository>();
_accountRepository =
ObjectFactory.GetInstance<IAccountRepository>();
_webContext = ObjectFactory.GetInstance<IWebContext>();
_account = _userSession.CurrentUser;

if (_account != null)
{
_view.DisplayToData(_account.FirstName + " " +
_account.LastName + " &lt;" +
_account.Email + "&gt;");

if (_webContext.AccoundIdToInvite > 0)
{
_accountToInvite =
_accountRepository.GetAccountByID
(_webContext.AccoundIdToInvite);

if (_accountToInvite != null)
{
SendInvitation(_accountToInvite.Email,
_account.FirstName + " " +
_account.LastName + " would like
to be your friend!");
_view.ShowMessage(_accountToInvite.Username +
" has been sent a friend request!");
_view.TogglePnlInvite(false);
}
}
}
}

public void SendInvitation(string ToEmailArray, string
Message)
{
string resultMessage = "Invitations sent to the following
recipients:<BR>";
resultMessage +=
_email.SendInvitations
(_userSession.CurrentUser,ToEmailArray, Message);
_view.ShowMessage(resultMessage);
_view.ResetUI();
}
}
}

The interesting thing here is the SendInvitation() method, which takes in a comma delimited array of emails and the message to be sent in the invitation. It then makes a call to the Email.SendInvitations() method.

//Core/Impl/Email.cs
public string SendInvitations(Account sender, string ToEmailArray,
string Message)
{
string resultMessage = Message;
foreach (string s in ToEmailArray.Split(','))
{
FriendInvitation friendInvitation = new FriendInvitation();
friendInvitation.AccountID = sender.AccountID;
friendInvitation.Email = s;
friendInvitation.GUID = Guid.NewGuid();
friendInvitation.BecameAccountID = 0;
_friendInvitationRepository.SaveFriendInvitation(friendInvitation);

//add alert to existing users alerts
Account account = _accountRepository.GetAccountByEmail(s);
if(account != null)
{
_alertService.AddFriendRequestAlert(_userSession.CurrentUser,
account, friendInvitation.GUID,
Message);
}

//TODO: MESSAGING - if this email is already in our system
add a message through messaging system
//if(email in system)
//{
// add message to messaging system
//}
//else
//{
// send email
SendFriendInvitation(s, sender.FirstName, sender.LastName,
friendInvitation.GUID.ToString(), Message);
//}
resultMessage += "• " + s + "<BR>";
}
return resultMessage;
}

This method is responsible for parsing out all the emails, creating a new FriendInvitation, and sending the request via email to the person who was invited. It then adds an alert to the invited user if they have an Account. And finally we have to add a notification to the messaging system once it is built.

Outlook CSV importer

The Import Contacts page is responsible for allowing our users to upload an exported contacts file from MS Outlook into our system. Once they have imported their contacts, the user is allowed to select which email addresses are actually invited into our system.

Importing contacts

As this page is made up of a couple of views, let’s begin with the initial view.

//Friends/OutlookCsvImporter.aspx
<asp:Panel ID="pnlUpload" runat="server">
<div class="divContainerTitle">Import Contacts</div>
<div class="divContainerRow">
<div class="divContainerCellHeader">Contacts File:</div>
<div class="divContainerCell"><asp:FileUpload ID="fuContacts"
runat="server" /></div>
</div>
<div class="divContainerRow">
<div class="divContainerFooter"><asp:Button ID="btnUpload"
Text="Upload & Preview Contacts" runat="server"
OnClick="btnUpload_Click" /></div>
</div>
<br /><br />
<div class="divContainerRow">
<div class="divContainerTitle">How do I export my contacts
from Outlook?</div>
<div class="divContainerCell">
<ol>
<li>
Open Outlook
</li>
<li>
In the File menu choose Import and Export
</li>
<li>
Choose export to a file and click next
</li>
<li>
Choose comma seperated values and click next
</li>
<li>
Select your contacts and click next
</li>
<li>
Browse to the location you want to save your
contacts file
</li>
<li>
Click finish
</li>
</ol>
</div>
</div>
</asp:Panel>

As you can see from the code we are working in panels here. This panel is responsible for allowing a user to upload their Contacts CSV File. It also gives some directions to the user as to how to go about exporting contacts from Outlook. This view has a file upload box that allows the user to browse for their CSV file, and a button to tell us when they are ready for the upload.

 ASP.NET 3.5 Social Networking

There is a method in our presenter that handles the button click from the view.

//Friends/Presenter/OutlookCsvImporterPresenter.cs
public void ParseEmails(HttpPostedFile file)
{
using (Stream s = file.InputStream)
{
StreamReader sr = new StreamReader(s);
string contacts = sr.ReadToEnd();

_view.ShowParsedEmail(_email.ParseEmailsFromText(contacts));
}
}

This method is responsible for handling the upload process of the HttpPostedFile. It puts the file reference into a StreamReader and then reads the stream into a string variable named contacts. Once we have the entire list of contacts we can then call into our Email class and parse all the emails out.

//Core/Impl/Email.cs
public List<string> ParseEmailsFromText(string text)
{
List<string> emails = new List<string>();
string strRegex = @"w+([-+.]w+)*@w+([-.]w+)*.w+([-.]w+)*";
Regex re = new Regex(strRegex, RegexOptions.Multiline);
foreach (Match m in re.Matches(text))
{
string email = m.ToString();
if(!emails.Contains(email))
emails.Add(email);
}
return emails;
}

This method expects a string that contains some email addresses that we want to parse. It then parses the emails using a regular expression (which we won’t go into details about!). We then iterate through all the matches in the Regex and add the found email addresses to our list provided they aren’t already present. Once we have found all the email addresses, we will return the list of unique email addresses. The presenter then passes that list of parsed emails to the view.

Selecting contacts

Once we have handled the upload process and parsed out the emails, we then need to display all the emails to the user so that they can select which ones they want to invite.

Now you could do several sneaky things here. Technically the user has uploaded all of their email addresses to you. You have them. You could store them. You could invite every single address regardless of what the user wants. And while this might benefit your community over the short run, your users would eventually find out about your sneaky practice and your community would start to dwindle. Don’t take advantage of your user’s trust!

//Friends/OutlookCsvImporter.aspx
<asp:Panel visible="false" ID="pnlEmails" runat="server">
<div class="divContainerTitle">Select Contacts</div>
<div class="divContainerFooter"><asp:Button
ID="btnInviteContacts1" runat="server"
OnClick="btnInviteContacts_Click"
Text="Invite Selected Contacts"
/></div>
<div class="divContainerCell" style="text-align:left;">
<asp:CheckBoxList ID="cblEmails" RepeatColumns="2"
runat="server"></asp:CheckBoxList>
</div>
<div class="divContainerFooter"><asp:Button
ID="btnInviteContacts2" runat="server"
OnClick="btnInviteContacts_Click"
Text="Invite Selected Contacts" /></div>
</asp:Panel>

Notice that we have a checkbox list in our panel. This checkbox list is bound to the returned list of email addresses.

public void ShowParsedEmail(List<string> Emails)
{
pnlUpload.Visible = false;
pnlResult.Visible = false;
pnlEmails.Visible = true;
cblEmails.DataSource = Emails;
cblEmails.DataBind();
}

The output so far looks like this:

ASP.NET 3.5 Social Networking

Now the user has a list of all the email addresses that they uploaded, which they can then go through selecting the ones that they want to invite into our system. Once they are through selecting the emails that they want to invite, they can click on the Invite button. We then iterate through all the items in the checkbox list to locate the selected items.

protected void btnInviteContacts_Click(object sender, EventArgs e)
{
string emails = "";
foreach (ListItem li in cblEmails.Items)
{
if(li != null && li.Selected)
emails += li.Text + ",";
}
emails = emails.Substring(0, emails.Length - 1);
_presenter.InviteContacts(emails);
}

Once we have gathered all the selected emails, we pass them to the presenter to run the invitation process.

public void InviteContacts(string ToEmailArray)
{
string result = _email.SendInvitations(_userSession.CurrentUser,
ToEmailArray, "");
_view.ShowInvitationResult(result);
}

The presenter promptly passes the selected items to the Email class to handle the invitations. This is the same method that we used in the last section to invite users.

//Core/Impl/Email.cs
public string SendInvitations(Account sender, string ToEmailArray,
string Message)
{
...
}

We then output the result of the emails that we invited into the third display.

<asp:Panel ID="pnlResult" runat="server" Visible="false">
<div class="divContainerTitle">Invitations Sent!</div>
<div class="divContainerCell">
Invitations were sent to the following emails:<br />
<asp:Label ID="lblMessage" runat="server"></asp:Label>
</div>
</asp:Panel>

LEAVE A REPLY

Please enter your comment!
Please enter your name here