ASP.NET Social Networks—Making Friends (Part 2)

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

ASP.NET 3.5 Social Networking — Save 50%

An expert guide to building enterprise-ready social networking and community applications with ASP.NET 3.5

$29.99    $15.00
by Andrew Siemer | December 2008 | .NET MySQL PHP

In the first part of this article we concentrated on two aspects Problem and Design. We started with the Problem, that is, defining what we need to implement the Friends concept, finding and inviting friends to join our network and developing an alert system. We then moved to Design wherein we actually finalized the requirements. And finally we began with the Solution, that is, actually implementing the features. In this part of the article by Andrew Siemer, we will continue with the solution part.

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>
ASP.NET 3.5 Social Networking An expert guide to building enterprise-ready social networking and community applications with ASP.NET 3.5
Published: December 2008
eBook Price: $29.99
Book Price: $49.99
See more
Select your format and quantity:

Confirm friendship

Having covered all these ways to invite someone into our site, we now need to look at what the invited user sees in the invitation. Let's start with what they would see in their inbox.

 ASP.NET 3.5 Social Networking

The user gets an email in their inbox telling them that so and so has invited them to come to Fisharoo. Once they open that email, they can see the request again as well as the link that they can follow to the site to take advantage of the invitation.

 ASP.NET 3.5 Social Networking

As you can see this link brings them to server/Friends/ConfirmFriendshipRequest.aspx with a GUID for an invitation key. There are two screens that the user might see after this point. The first screen is for the users who are already members. It asks them to log in again to confirm the friendship.

 ASP.NET 3.5 Social Networking

The other screen is for the users who aren't members, or the users who aren't logged in.

 ASP.NET 3.5 Social Networking

The only real logic in the ConfirmFriendshipRequest.aspx page is to check the GUID that is passed in to make sure that it is valid. This happens in the presenter of this page.

public void Init(IConfirmFriendshipRequest view)
{
_view = view;
if (!string.IsNullOrEmpty(_webContext.FriendshipRequest))
{
FriendInvitation friendInvitation =
_friendInvitationRepository.GetFriendInvitationByGUID(new
Guid(_webContext.FriendshipRequest));
if(friendInvitation != null)
{
if (_webContext.CurrentUser != null)
LoginClick();

Account account =
_accountRepository.GetAccountByID
(friendInvitation.AccountID);
_view.ShowConfirmPanel(true);
_view.LoadDisplay(_webContext.FriendshipRequest,
account.AccountID, account.FirstName,
account.LastName, _configuration.SiteName );
}
else
{
_view.ShowConfirmPanel(false);
_view.ShowMessage("There was an error validating your
invitation.");
}
}
}

Either we can load a friendInvitation from the GUID or not. If we can, then we check to see if the user is already a member of the system and logged in. If they are logged in we automatically redirect them to the login screen. Otherwise we prompt them to log in or create an account. If the friendInvitaiton can't be loaded properly, then we show an error explaining that. Where the real magic occurs for the invitation process is in the login and registration pages.

Login

In the login presenter we have added some logic to the Init method to recognize if we have a friendship request or not.

//Accounts/Presenter/LoginPresenter.cs
public void Init(ILogin view)
{
_view = view;
_accountService = ObjectFactory.GetInstance<IAccountService>();
_redirector = ObjectFactory.GetInstance<IRedirector>();
_webContext = ObjectFactory.GetInstance<IWebContext>();

if(!string.IsNullOrEmpty(_webContext.FriendshipRequest))
_view.DisplayMessage("Login to add this friend!");
}

This logic lets the user know that by logging in they will be accepting the friend request. Then in the AccountService.cs file we have added some additional logic. If the login is a success and there is a friend request, we confirm the request and make these two users friends, via the FriendService we discussed in the first part of this article.

//Core/Impl/AccountService.cs
public string Login(string Username, string Password)
{
...
if (account.EmailVerified)
{
_userSession.LoggedIn = true;
_userSession.Username = Username;
_userSession.CurrentUser =
GetAccountByID(account.AccountID);

if(!string.IsNullOrEmpty(_webContext.FriendshipRequest))
{
_friendService.CreateFriendFromFriendInvitation
(new Guid(_webContext.FriendshipRequest),_userSession.CurrentUser);
}

...
}

Registration

If the invited friend is not already a user of the site, then we allow them to walk through the registration site as normal. Once the registration is complete, we not only register them but we also create the friendship.

//Accounts/Presenter/RegisterPresenter.cs
public void Register(string Username, string Password,
string FirstName, string LastName, string Email,
string Zip, DateTime BirthDate, string Captcha,
bool AgreesWithTerms, Int32 TermID)
{
...

//if this registration came via a friend request...
if(friendInvitation != null)
{
_friendService.CreateFriendFromFriendInvitation
(new Guid(_webContext.FriendshipRequest),newAccount);
}

...

}

Show friends

Now that we have everything we need to invite and accept a friend, we need the ability to see our friends. For this we will add to our Friends section landing page (Default.aspx) a list of all our friends. This will actually be quite easy as we will use our ProfileDisplay user control that we created earlier for our Search page. This page will simply consist of a repeater with our ProfileDisplay control. We set the ShowFriendRequestButton to false as these are already our friends.

<asp:Repeater ID="repFriends" runat="server" 
OnItemDataBound="repFriends_ItemDataBound">
<ItemTemplate>
<div class="divContainerRow" style="height:110px;">
<div class="divContainerCell">
<Fisharoo:ProfileDisplay
ShowFriendRequestButton="false"
ID="pdProfileDisplay" runat="server" />
</div>
</div>
</ItemTemplate>
</asp:Repeater>

Our presenter then loads the display with all the current user's friends by calling into the FriendRepository.GetFriendsAccountsByAccountID() method and passing that collection down to the view.

public void LoadDisplay()
{
_view.LoadDisplay(_friendRepository.GetFriendsAccountsByAccountID
(_userSession.CurrentUser.AccountID));
}

The view then hooks up the repeater's data source. On each ItemDataBound of the repeater we spin up the ProfileDisplay user control.

protected void repFriends_ItemDataBound(object sender, 
RepeaterItemEventArgs e)
{
if(e.Item.ItemType == ListItemType.Item || e.Item.ItemType ==
ListItemType.AlternatingItem)
{
ProfileDisplay pdProfileDisplay =
e.Item.FindControl("pdProfileDisplay") as ProfileDisplay;
pdProfileDisplay.LoadDisplay(((Account)e.Item.DataItem));
}
}

We then end up with this output:

 ASP.NET 3.5 Social Networking

Friends on profile

After having a page that shows all of our friends, it should be easy to update our public profile to show a handful of friends. To do this we will open the Profile.aspx page and add to it a bit. We are simply going to add the same sort of repeater to the profile page as we did in the case of the Friends/Default.aspx page.

<asp:Repeater ID="repFriends" runat="server" 
OnItemDataBound="repFriends_ItemDataBound">
<ItemTemplate>
<Fisharoo:ProfileDisplay ShowFriendRequestButton="false"
ShowDeleteButton="false" ID="pdProfileDisplay"
runat="server" />
</ItemTemplate>
</asp:Repeater>

Then in our ProfilePresenter.cs file we have added a line that loads that repeater.

public void Init(IProfile View)
{
_view = View;
_view.SetAvatar(_accountBeingViewed.AccountID);
_view.DisplayInfo(_accountBeingViewed);
_view.LoadFriends(_friendRepository.GetFriendsAccountsByAccountID(_accountBeingViewed.AccountID));
_view.LoadStatusUpdates(_statusUpdateRepository.GetTopNStatusUpdatesB
yAccountID(_accountBeingViewed.AccountID,5));
TogglePrivacy();
}

And in the Profile.aspx.cs file we have added an event handler for repFriends_ItemDataBound() that takes care of loading each ProfileDisplay control.

protected void repFriends_ItemDataBound(object sender, 
RepeaterItemEventArgs e)
{
if(e.Item.ItemType == ListItemType.Item || e.Item.ItemType ==
ListItemType.AlternatingItem)
{
ProfileDisplay pdProfileDisplay =
e.Item.FindControl("pdProfileDisplay") as ProfileDisplay;
pdProfileDisplay.LoadDisplay(((Account)e.Item.DataItem));
}
}

Status updates

Status updates (our micro blog) are very simple to implement at this point. We will need to open the master page and add a small section to take in and display a top listing of these updates. In our master page we will add a panel to our global display. It will be responsible for taking in new updates as well as displaying the most recent updates.

//SiteMaster.master
<asp:Panel ID="pnlStatusUpdate" runat="server">
<div class="divContainer">
<div class="divContainerBox">
<div class="divContainerTitle">Status Updates</div>
<div class="divContainerCell">
<asp:TextBox Width="85" style="font-size:9px;padding-left:0px;padding-right:0px;"
id="txtStatusUpdate"
runat="server"></asp:TextBox>
<asp:Button style="font-size:9px;padding-left:0px;padding-right:0px;"
ID="btnAddStatus" runat="server"
Text="Add" OnClick="btnAddStatus_Click" /><br />
<asp:Repeater runat="server" ID="repStatus">
<ItemTemplate>
<asp:Label ID="Label1" Text='<%# ((StatusUpdate)
Container.DataItem).CreateDate.ToString() %>'
runat="server" style="font-size:9px;"></asp:Label> -
<asp:Label ID="Label2" Text='<%# ((StatusUpdate)Container.DataItem).Status %>'
runat="server"
style="font-size:9px;"></asp:Label>
</ItemTemplate>
<SeparatorTemplate>
<div class="divContainerSeparator"></div>
</SeparatorTemplate>
</asp:Repeater><br />
<asp:Button ID="btnShowAllStatusUpdates"
runat="server" Text="View All" OnClick="btnShowAllStatusUpdates_Click" />
</div>
</div></div>
</asp:Panel>

Once the display is in place, we need to add a method to capture our button clicks so that we can add new updates.

//SiteMaster.master.cs
protected void btnAddStatus_Click(object sender, EventArgs e)
{
StatusUpdate su = new StatusUpdate();
su.CreateDate = DateTime.Now;
su.AccountID = _userSession.CurrentUser.AccountID;
su.Status = txtStatusUpdate.Text;
_statusRepository.SaveStatusUpdate(su);

_alertService.AddStatusUpdateAlert(su);
_redirector.GoToHomePage();
}

This method spins up a new StatusUpdate and adds it to the StatusUpdateRepository. While we are here we need to add another method to handle the button click to show all status updates.

//SiteMaster.master.cs
protected void btnShowAllStatusUpdates_Click(object sender,
EventArgs e)
{
_redirector.GoToProfilesStatusUpdates();
}

As you can see, this method simply redirects via the Redirector class to the Profiles/StatusUpdates.aspx page. This then takes us to displaying our top StatusUpdates in the master page. To do this we need to add the method that gets the top N StatusUpdates.

//SiteMaster.master.cs
protected void LoadStatus()
{
repStatus.DataSource =
_statusRepository.GetTopNStatusUpdatesByAccountID(_userSession.Curren
tUser.AccountID, 5);
repStatus.DataBind();
}

With this in place we need to update the Page_Load() method of the master page so that the status updates are loaded when there is a user logs into the site.

//SiteMaster.master.cs
protected void Page_Load(object sender, EventArgs e)
{
...

if (_userSession.CurrentUser != null)
{
LoadStatus();
pnlStatusUpdate.Visible = true;
}
else
pnlStatusUpdate.Visible = false;
}

Now that we have a way to capture new status updates as well as a way to display the most recent updates, we need to provide a way for our user to see all of their updates. We will do this with a page dedicated to showing this data.

//Friends/StatusUpdates.aspx
<asp:Repeater ID="repStatusUpdates" runat="server">
<ItemTemplate>
<%# ((StatusUpdate)Container.DataItem).CreateDate.ToString()
%> -
<%# ((StatusUpdate)Container.DataItem).Status %>
</ItemTemplate>
<SeparatorTemplate>
<div class="divContainerSeparator"></div>
</SeparatorTemplate>
</asp:Repeater>

 ASP.NET 3.5 Social Networking

This page of course has the same plumbing issues as do the others. But it basically boils down to calling into the StatusUpdateRepository and get all StatusUpdates for a given Account. The only difference between this and showing the TopN StatusUpdates, as we did on the master page, is that we will show all the updates here.

ASP.NET 3.5 Social Networking An expert guide to building enterprise-ready social networking and community applications with ASP.NET 3.5
Published: December 2008
eBook Price: $29.99
Book Price: $49.99
See more
Select your format and quantity:

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.

Books From Packt

  ASP.NET 3.5 Application Architecture and Design
ASP.NET 3.5 Application Architecture and Design

Beginners Guide to SQL Server Integration Services Using Visual Studio 2005
Beginners Guide to SQL Server Integration Services Using Visual Studio 2005

Microsoft AJAX Library Essentials: Client-side ASP.NET AJAX 1.0 Explained
Microsoft AJAX Library Essentials: Client-side ASP.NET AJAX 1.0 Explained

ASP.NET Data Presentation Controls Essentials
ASP.NET Data Presentation Controls Essentials

Software Testing with Visual Studio Team System 2008
Software Testing with Visual Studio Team System 2008

Entity Framework Tutorial
Entity Framework Tutorial

LINQ Quickly
LINQ Quickly

Microsoft Visual C++ Windows Applications by Example
Microsoft Visual C++ Windows Applications by Example

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
M
A
2
8
8
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