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.
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:
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>
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.
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.
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.
The other screen is for the users who aren't members, or
the users who aren't logged in.
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:
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>
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.
|