Adding Email Confirmation to ASP.NET MVC

by Kevin on September 11, 2010

UPDATE October 8th, 2011: I’ve written a new post that uses ASP.NET MVC3 and Nuget.

UPDATE 2 October 9th, 2011: I’ve re-written this post to incorporate the improvements suggested by David in the comments. Wow – they really simplified the code!
[Sample Application Code]

Problem

On one of the sites I run, I ask people to register in order to gain access to current data. The site is based on a book by Martin Zweig and calculates his Zweig Model – or at least it will. Right now, it only calculates one of the constituent components – the Prime Rate Indicator. The book was published in 1993 and I only permit unregistered users to have access to the data through the publication of the book. If the user wants to see current data, she has to register using the stock ASP.NET MVC registration process. Out of the box, there isn’t any email confirmation procedure, so I end up with a fair amount of test@test.com addresses.

Now, the only reason that I ask people to register is to gauge interest in the site. I currently have zero plans to do anything with the registration – but may elect do more if and when I ever add more to the site. This is how world domination begins. Or something.

So – how to cut down on the faux registrations, while not adding too many hoops for the user to jump through? A common pattern is to send an email to the address provided by the user, with a link that, when clicked, activates their registration. Sounds easy.

Solution

In the out-of-the-box code template for ASP.NET MVC, the Register action verifies the MembershipCreateStatus, and if all is kosher, it signs the user in (line 11 below).

        [HttpPost]
        public ActionResult Register(RegisterModel model)
        {
            if (ModelState.IsValid)
            {
                // Attempt to register the user
                MembershipCreateStatus createStatus = MembershipService.CreateUser(model.UserName,
                       model.Password, model.Email);
                if (createStatus == MembershipCreateStatus.Success)
                {
                    FormsService.SignIn(model.UserName, false /* createPersistentCookie */);
                    return RedirectToAction("Index", "Home");
                }
                else
                {
                    ModelState.AddModelError("", AccountValidation.ErrorCodeToString(createStatus));
                }
            }

            // If we got this far, something failed, redisplay form
            ViewData["PasswordLength"] = MembershipService.MinPasswordLength;
            return View(model);
        }

To override this behavior, we can make use of the IsApproved property on the MembershipUser class. We want to set the property to “false” until the user has clicked the confirmation link. To do that, we need to dig into the implementation of the MembershipUser.CreateUser method, which is buried in AccountMembershipService class of the AccountModels.cs file.

public MembershipCreateStatus CreateUser(string userName, string password, string email)
{
	if (String.IsNullOrEmpty(userName)) throw
                new ArgumentException("Value cannot be null or empty.", "userName");
	if (String.IsNullOrEmpty(password)) throw
                new ArgumentException("Value cannot be null or empty.", "password");
	if (String.IsNullOrEmpty(email)) throw
                new ArgumentException("Value cannot be null or empty.", "email");

	MembershipCreateStatus status;
        // ORIGINAL: 6th Parameter is IsApproved property - which defaults to true
	    //_provider.CreateUser(userName, password, email, null, null, true, null, out status);
        // MODIFICATION: Set the IsApproved property to false
        _provider.CreateUser(userName, password, email, null, null, false, null, out status);
	return status;
}

With that done, the user will not be approved when they are created – so we’ll send the user an email with a hyperlink in it that, when clicked, will set their status to Approved.

        [HttpPost]
        public ActionResult Register(RegisterModel model)
        {
            if (ModelState.IsValid)
            {
                // Attempt to register the user
                MembershipCreateStatus createStatus = MembershipService.CreateUser(model.UserName,
                           model.Password, model.Email);

                if (createStatus == MembershipCreateStatus.Success)
                {
                    /* TODO: At this point, the user has created a valid account (but is unapproved)
                     * We need to send the user a confirmation email and then
                     * redirect them to a confirmation page
                     * that says, 'thank you for registering, please check your
                     * email form a confirmation link. */

                    MembershipService.SendConfirmationEmail(model.UserName);

                    //FormsService.SignIn(model.UserName, false /* createPersistentCookie */);
                    return RedirectToAction("confirmation");
                }
                else
                {
                    ModelState.AddModelError("", AccountValidation.ErrorCodeToString(createStatus));
                }
            }

            // If we got this far, something failed, redisplay form
            ViewData["PasswordLength"] = MembershipService.MinPasswordLength;
            return View(model);
        }

Now, this won’t compile because the MembershipService interface does not include a SendConfirmationEmail method. So, mosey on over to Models\AccountModels.cs and look for IMembershipService definition. We’re going to add the method to the interface’s definition, like this:

    public interface IMembershipService
    {
        int MinPasswordLength { get; }

        bool ValidateUser(string userName, string password);
        MembershipCreateStatus CreateUser(string userName, string password, string email);
        bool ChangePassword(string userName, string oldPassword, string newPassword);

        // Additions to Interface for EmailConfirmation...
        void SendConfirmationEmail(string userName);
    }

Now let’s implement the method in the AccountMembershipService class:

public void SendConfirmationEmail(string userName)
        {
            MembershipUser user = Membership.GetUser(userName);
            string confirmationGuid = user.ProviderUserKey.ToString();
            string verifyUrl = HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Authority) +
                             "/account/verify?ID=" + confirmationGuid;

            var message = new MailMessage("YOUR_USER_ACCOUNT_HERE@YOUR_DOMAIN_HERE", user.Email)
            {
                Subject = "Please confirm your email",
                Body = verifyUrl

            };

            var client = new SmtpClient();

            client.Send(message);
        }

Here, we’re setting the confirmationGuid to the user’s ProviderUserKey property. This is the GUID stored in the database that uniquely identifies the user. We then set the verifyUrl to the Verify action on the Account controller – passing the confirmationGuid as the ID parameter. We then new-up a simple email message that just contains a hyperlink consisting of the verifyUrl.

In order for this email to work, you’ll need to import the

using System.Net.Mail;
using System.Configuration;

namespaces, and you’ll need to add the following to your web.config file:

<system.net>
  <mailSettings>
    <smtp deliveryMethod="Network">
                <network host="YOUR_MAIL_HOST" userName="YOUR_USER_NAME@YOUR_DOMAIN"
                                     password="YOUR_PASSWORD" port="YOUR_PORT">
    </smtp>
  </mailSettings>
</system.net>

Add this directly to the configuration root, changing the settings as appropriate. For testing purposes, you can use a Gmail account – which uses port 587. Be sure to double-check your settings!

With that configured, when the user clicks the register button, they’ll be sent an email with the confirmation link it, and they’ll be redirected to the confirmation page. That page doesn’t exist yet, so right click on the Views\Account folder and select Add | View. Name the view ‘confirmation’, and set your master page. The view does not need to be strongly typed, as you won’t be rendering any data here (though you certainly could). Add a simple message like this:

<@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master"
                                 Inherits="System.Web.Mvc.ViewPage<dynamic>>

<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
	Confirmation
</asp:Content>

<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">

    <h2>Confirmation</h2>
    <p>Thank you for registering. Please check your email for a confirmation request with a
                 link that will confirm your account. Once you click the link,
                 your registration will be complete.</p>

</asp:Content>

While you’re at it, you also need to add corresponding action methods for the Confirmation and Welcome views:

        public ActionResult Confirmation()
        {
            return View();
        }

        public ActionResult Welcome()
        {
            return View();
        }

The welcome view don’t exist yet, we’ll add it closer to the end.

At this point, everything should be good – and you’ll just need to implement the Verify action on the Account controller.

        public ActionResult Verify(string ID)
        {
            if (string.IsNullOrEmpty(ID) || (!Regex.IsMatch(ID,
                           @"[0-9a-f]{8}\-([0-9a-f]{4}\-){3}[0-9a-f]{12}")))
            {
                TempData["tempMessage"] =
                        "The user account is not valid. Please try clicking the link in your email again."
                return View();
            }

            else
            {
                MembershipUser user = Membership.GetUser(new Guid(ID));

                if (!user.IsApproved)
                {
                    user.IsApproved = true;
                    Membership.UpdateUser(user);
                    FormsService.SignIn(user.UserName, false);
                    return RedirectToAction("welcome");
                }
                else
                {
                    FormsService.SignOut();
                    TempData["tempMessage"] = "You have already confirmed your email address... please log in.";
                    return RedirectToAction("LogOn");
                }
             }
        }

This takes the id passed in the querystring and checks to ensure that it’s not null and that it’s a valid GUID. You’ll need to import the

using System.Text.RegularExpressions;

namespace for the regex to work. Once those criteria are met, the code instantiates a MembershipUser with the ID by calling the GetUser method of the Membership class. The code then checks if the user is not currently approved, and in that case, sets the IsApproved property to true, updates the User object store, signs the user in and redirects the user to a ‘welcome’ page. If the user is already approved (meaning this is not the first time the user has clicked the link), it signs any user out and redirects the user to the LogOn page, adding a message to TempData that the account is already registered. This last part is optional, and I’m not sure what the ‘proper’ experience should be for a user that clicks on the link multiple times – but this seemed to work for me.

The last thing you’ll need to do is add the welcome view, again, right-clicking on Views\Account and selecting Add | View:

<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master"
                                 Inherits="System.Web.Mvc.ViewPage<dynamic>" %>

<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
	Welcome
</asp:Content>

<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">

    <h2>Welcome</h2>

    <p>
        Thank you <%: User.Identity.Name %> for verifying your email address.
                          You now have access to the site, and can poke around as you please.
    </p>

</asp:Content>

With this done, you should have a workflow as follows:

When the user clicks on the register link, they enter their registration data:

When they click the register button, they’re redirected to the confirmation page with instructions to check their email:

When they open their email client, they’ll have an email containing their verification link:

Finally, clicking the link will verify their account, sign them in, and redirect them to the welcome page:

Now, I could have chosen to just verify the account and then redirect them to the sign-on page. But that’s always annoyed me, and since there isn’t money on the line, I decided to be a nice guy and save my user from the hassle of having to do more work. I hate it when I call the phone company and have to enter all of my data – only to end up talking to an operator whose first question is: “And may I have your account number please?” Umm… didn’t I just type that in?

If the user tries to confirm their account more than once, they’re taken to:

This requires that we check for the existence of tempData in the Accounts\LogOn view:

<asp:Content ID="loginContent" ContentPlaceHolderID="MainContent" runat="server">
    <h2>Log On</h2>
    <p>
        Please enter your username and password.
                <%: Html.ActionLink("Register", "Register") %> if you don't have an account.
    </p>
    <%  if (!string.IsNullOrEmpty(TempData["tempMessage"] as string)) { %>
        <p>
            <b class="error"><%: TempData["tempMessage"].ToString() %></b>
        </p>
    <% } %>

    <% using (Html.BeginForm()) { %>

Wrap Up

That’s all there is to it. I’ve included a working sample application here. I didn’t include the database, as it will be created for you in the App_Data folder when you register your first user. Please let me know if you have any questions or issues.

{ 42 comments… read them below or add one }

Ken Lewis September 21, 2010 at 12:15 pm

This is a great, easy to follow example. It saved me loads of time. Thanks!

Ken

Reply

Kevin September 21, 2010 at 5:10 pm

Ken – Glad it was helpful… thanks for the feedback!

Kevin

Reply

badmash October 22, 2010 at 3:20 pm

I just signed up to your blogs rss feed. Will you post more on this subject?

Reply

aparadekto October 26, 2010 at 5:26 pm

Hey, I can’t view your site properly within Opera, I actually hope you look into fixing this.

Reply

Andrew Beaven November 6, 2010 at 7:19 am

Thanks Kevin. That was a really nice, easy to follow tutorial. I will be implementing this on my site!

Reply

Nick Masao November 19, 2010 at 12:30 pm

This is great. Thanks a lot. Cheers

Reply

David January 4, 2011 at 5:05 pm

I stumbled upon your code while looking for some information on the MembershipProvider. I found your example helpful, but why did you modify the IsApproved property of the user directly after it’s created? Couldn’t you just set the cooresponding parameter on the call to the _provider.CreateUser in the IMembershipService implementation when the MembershipService.CreateUser is called?

Reply

Kevin April 6, 2011 at 9:34 pm

David – perfect! I didn’t see that overload in CreateUser… while my method *works* – yours is clearly more efficient/elegant. I’ll likely update the sample code and blog post for MVC3 – and will be sure to incorporate your change. Thanks for the catch!

Reply

Ryan February 8, 2011 at 8:21 pm

I got all of the code in there but I couldnt get the smtp to work… I’m on windows 7 with iis7

Reply

Kevin February 9, 2011 at 9:50 am

Hi Ryan – can you check/double-check/triple-check your web.config settings? Try using your gmail credentials… they work great for testing. There’s a thread on this topic here that shows how to do it. Please let me know if that helps.

Regards,

Kevin

Reply

David Turner March 15, 2011 at 10:04 pm

Thanks Kevin. A nice quick and easy solution.

Reply

Green April 6, 2011 at 5:13 pm

Great work Kevin: This is very helful for beginner like us, today I run the project and I was able to create three different user names under same email address, how can I stop that. where in accountModel I need to add validation, that it shouldn’t allow same email aswell?

I think the below code doesn’t seem to work:
case MembershipCreateStatus.DuplicateEmail:
return “A username for that e-mail address already exists. Please enter a different e-mail address.”;

Reply

Kevin April 6, 2011 at 9:30 pm

Hi Green – in the AspNetSqlMembershipProvider section of the web.config file, you’ll find requiresUniqueEmail=”false”. Change that to “true” and it should throw an error when you try to create a second username with a duplicate email address.

Reply

Xzzst August 4, 2011 at 7:56 am

thankx kevin….u saved my day :P

Reply

Miri August 18, 2011 at 9:10 am

Kevin, thanks a lot for the clear and straight-forward walkthrough! You really simplified this process for me.

Reply

Andrea September 25, 2011 at 8:09 am

Thank you for this very well made tutorial!

I think that to make it 100% perfect, you could apply the change suggested by David and add the creation of the “Verify” view (that is quite obvious, but missing).

Reply

Kevin October 8, 2011 at 3:56 pm

Andrea – I just incorporated David’s change in a new MVC3 post… and I’ve edited this post to incorporate those changes as well. I downloaded the zip file and the Verify View file is in there… am I missing something?

Reply

Andrea October 10, 2011 at 5:05 pm

No, I think you already did a very good job: these latest updates made it simply complete :)

Thank you again!

Reply

chris October 6, 2011 at 11:01 pm

Can you post the updated code for MVC3?

Reply

Kevin October 7, 2011 at 7:31 am

Chris – will do, and I’ll *finally* incorporate the changes suggested by David.

Reply

Kevin October 8, 2011 at 3:53 pm

Chris – done… just added MVC3 post including Nuget.

Reply

Tandin October 11, 2011 at 1:09 am

Nice tutorial. This is what I have been looking for but I have a small problem when I try to resgister a new user. I get an exception saying unable to connect to SQL server database. I must be missing something obvious.

Reply

Kevin October 11, 2011 at 6:44 am

Tandin – It looks like there may be something wrong with the sample as I got the same error the first time I ran it. Can you try executing the ‘Build | Clean’ Solution menu command and let me know if that works? If it doesn’t, I’ll dig further into it … but regardless, I need to fix the sample!

Reply

Tandin October 13, 2011 at 9:16 pm

Hi Kevin,

Thank you for your prompt reply. It seemed like my SQL Express service wasn’t started. Now it is working. Cheers

Reply

lukas24 December 22, 2011 at 8:47 am

Good tutorial, everything was thoroughly explained.

But I have a silly question about this line of code.

Everything works fine if I give there my own data with gmail. So, how can other users can then register?(from other mail provider or just with different gmail account). Thank you in advance for your reply. This is my first approach with MVC, hence such a strange question:)

Reply

Kevin December 22, 2011 at 10:19 am

Hi Lukas -

I’m not sure that I completely understand your question – so please forgive me if I get this wrong. The data that you use to get gmail set up allows your application to be able to send emails to your users. It should only need to be setup once, and then will work for all of your potential users… if you want to point me to your application, I’ll register as well to confirm. Please let me know if you still have questions or if I didn’t answer that clearly enough.

Regards,

Kevin

Reply

lukas24 December 22, 2011 at 11:21 am

Thank you so much for your prompt reply. Yes, you understand me well. Now, of course, everything is clear and works well from the moment, I set the gmail account in the web.config file. Previously for other email accounts have been problems, hence my question appeared.

Thanks again and best regards,

Lukas

Reply

chris January 29, 2012 at 8:59 am

Thanks that was awesome, so easy to follow … idiot proof :)

Reply

Nirav Mehta May 6, 2012 at 3:25 pm

When we start to run it giving error

Server Error in ‘/’ Application.

Configuration Error

Description: An error occurred during the processing of a configuration file required to service this request. Please review the specific error details below and modify your configuration file appropriately.

Parser Error Message: Could not load file or assembly ‘System.Web.Mvc, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35′ or one of its dependencies. The system cannot find the file specified.

Source Error:

Line 25:
Line 26:
Line 27:
Line 28:
Line 29:

Source File: D:\Downloads\EmailConfirmSample\EmailConfirmSample\EmailConfirmSample\web.config Line: 27

Assembly Load Trace: The following information can be helpful to determine why the assembly ‘System.Web.Mvc, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35′ could not be loaded.

=== Pre-bind state information ===
LOG: User = Nirav-PC\Nirav
LOG: DisplayName = System.Web.Mvc, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35
(Fully-specified)
LOG: Appbase = file:///D:/Downloads/EmailConfirmSample/EmailConfirmSample/EmailConfirmSample/
LOG: Initial PrivatePath = D:\Downloads\EmailConfirmSample\EmailConfirmSample\EmailConfirmSample\bin
Calling assembly : (Unknown).
===
LOG: This bind starts in default load context.
LOG: Using application configuration file: D:\Downloads\EmailConfirmSample\EmailConfirmSample\EmailConfirmSample\web.config
LOG: Using host configuration file:
LOG: Using machine configuration file from C:\Windows\Microsoft.NET\Framework\v4.0.30319\config\machine.config.
LOG: Post-policy reference: System.Web.Mvc, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35
LOG: Attempting download of new URL file:///C:/Users/Nirav/AppData/Local/Temp/Temporary ASP.NET Files/root/0b1a7f7f/ca308f35/System.Web.Mvc.DLL.
LOG: Attempting download of new URL file:///C:/Users/Nirav/AppData/Local/Temp/Temporary ASP.NET Files/root/0b1a7f7f/ca308f35/System.Web.Mvc/System.Web.Mvc.DLL.
LOG: Attempting download of new URL file:///D:/Downloads/EmailConfirmSample/EmailConfirmSample/EmailConfirmSample/bin/System.Web.Mvc.DLL.
LOG: Attempting download of new URL file:///D:/Downloads/EmailConfirmSample/EmailConfirmSample/EmailConfirmSample/bin/System.Web.Mvc/System.Web.Mvc.DLL.
LOG: Attempting download of new URL file:///C:/Users/Nirav/AppData/Local/Temp/Temporary ASP.NET Files/root/0b1a7f7f/ca308f35/System.Web.Mvc.EXE.
LOG: Attempting download of new URL file:///C:/Users/Nirav/AppData/Local/Temp/Temporary ASP.NET Files/root/0b1a7f7f/ca308f35/System.Web.Mvc/System.Web.Mvc.EXE.
LOG: Attempting download of new URL file:///D:/Downloads/EmailConfirmSample/EmailConfirmSample/EmailConfirmSample/bin/System.Web.Mvc.EXE.
LOG: Attempting download of new URL file:///D:/Downloads/EmailConfirmSample/EmailConfirmSample/EmailConfirmSample/bin/System.Web.Mvc/System.Web.Mvc.EXE.

Version Information: Microsoft .NET Framework Version:4.0.30319; ASP.NET Version:4.0.30319.272

Reply

Kevin May 6, 2012 at 10:09 pm

Hi Nirav,

It *looks* like your error is related to not being able to find the MVC 2.0 assemblies… do you have ASP.NET MVC 2.0 Installed? What version of MVC are you using? You may just need to create a clean application and then port the code over. If you’re using MVC 3.0, there’s a nuget package you can install (blog post here). Please let me know regarding your progress.

Kevin

Reply

Fernando Correia May 29, 2012 at 8:35 pm

That was a very useful article. Thank you for sharing it. I picked some ideas from it to create my own account activation workflow.

Reply

chaume June 1, 2012 at 11:01 am

Great job, perfect explanation and perfect steps. Thanks!!!!

Reply

Bryant Alegi June 5, 2012 at 7:51 am

It is in point of fact a nice and useful piece of information. I am satisfied that you just shared this useful information with us. Please keep us informed like this. Thank you for sharing.

Reply

Chintan Parikh June 8, 2012 at 1:48 am

Hi Kevin,

What will be the changes I need to do if i wanted to implement through razor view engine?

Reply

Kevin June 8, 2012 at 7:31 am

Hi Chintan -

If you pull down the nuget package referenced here, you’ll see how it’s implemented in MVC 3 using Razor. If you’d like, I can update the walk-through to show Razor. Let me know if that helps.

Kevin

Reply

Steve June 22, 2012 at 8:27 am

I’m using MVC 3.0 and .NET 4.0. I’m seeing a few differences in the template code “out of the box”. My AccountController starts out as:

[HttpPost]
public ActionResult Register(RegisterModel model)
{
if (ModelState.IsValid)
{
// Attempt to register the user
MembershipCreateStatus createStatus;
Membership.CreateUser(model.UserName, model.Password, model.Email, null, null, false, null, out createStatus);

if (createStatus == MembershipCreateStatus.Success)
{
FormsAuthentication.SetAuthCookie(model.UserName, false /* createPersistentCookie */);
return RedirectToAction(“Index”, “Home”);
}
else
{
ModelState.AddModelError(“”, ErrorCodeToString(createStatus));
}
}

// If we got this far, something failed, redisplay form
return View(model);
}

I can’t locate a MembershipService.SendConfirmationEmail() method. Do I need a better/newer template? Or does .NET 4.0 have a different way of doing this?

Reply

Kevin June 24, 2012 at 8:43 am

Hi Steve -

I wrote a new post covering MVC 3 – but I did so using Nuget. You can checkout the source code for the MVC 3 example here.

If that doesn’t work, please let me know and I’ll add a more descriptive post!

Regards,

Kevin

Reply

johnny IV Young September 14, 2012 at 12:14 pm

i love your code. nice code . thank a lot Kevin

Regards,

Johnny IV

Reply

madhu June 27, 2013 at 3:30 am

Hi, Please help me to solve the below error ….
Error 1 ‘System.Web.Security.Membership.GetUser()’ is a ‘method’ but is used like a ‘type’ C:\Users\madhura\documents\visual studio 2010\Projects\MvcApplication1\MvcApplication1\Models\AccountModels.cs 181 48 MvcApplication1

Reply

DB Conner July 3, 2013 at 7:36 pm

Nice nice work and explanation. And here it is 2013, and the goodness just keeps on giving.

About to implement in MVC 4…

Now if the global monolithic phone companies will just latch on to your service level philosophy… the world will be a better place. :-)

Reply

Alegntaye July 9, 2013 at 11:05 am

Very helpful!
For my case, i need the email for notifications so i need it for both the form based and OPenID scenarios.
so i made the following modifications and it worked for me:
> Modified the RegisterExternalLoginModel by adding the Email property.
> Modified the ExternalLoginCallback method of the AccountController
return View(“ExternalLoginConfirmation”, new RegisterExternalLoginModel { UserName = result.UserName, ExternalLoginData = loginData,Email = result.UserName});
> Modified the ExternalLoginConfirmation view by adding @Html.HiddenFor(m=> m.Email) just above @Html.HiddenFor(m => m.ExternalLoginData). I made it hidden, because i don’t want the user to modify it.
> Finally, modified ExternalLoginConfirmation method to accept the email:
db.UserProfiles.Add(new UserProfile { UserName = model.UserName,Email = model.Email});

Reply

Abdullah October 28, 2013 at 2:03 am

its giving “INVALID ANSWER” on status

Reply

Leave a Comment

{ 2 trackbacks }

Next post: