Showing posts with label ASP.NET MVC3. Show all posts
Showing posts with label ASP.NET MVC3. Show all posts

Monday, 11 November 2013

Introduction

MvcMailer provides you with an ActionMailer style email sending NuGet Package for ASP.Net MVC 3/4. So, you can produce professional looking emails composed of your MVC master pages and views with ViewBag.

Why MvcMailer?

Because you want to:
  • Write clean code to send emails instead of spaghetti code
  • Reuse the power of master pages, views and data
  • Easily write unit test for email sending code
  • Send multi-part emails
  • Do some or all the above painlessly.
Now that you are convinced, please install the package!

Install MvcMailer NuGet Package

Open your package manager and type:
PM> install-package MvcMailer

Scaffold Your Mailer

Scaffold is your friend that produces your mailer with master pages and views. Run the following command in your Package Manager console:
PM> Scaffold Mailer.Razor UserMailer Welcome,PasswordReset
You should see the following:
Added MyScaffolder output 'Mailers\IUserMailer.cs'
Added MyScaffolder output 'Mailers\UserMailer.cs'
Added MyScaffolder output 'Views\UserMailer\_Layout.cshtml'
Added MyScaffolder output 'Views\UserMailer\Welcome.cshtml'
Added MyScaffolder output 'Views\UserMailer\PasswordReset.cshtml'
Mailers\IUserMailer.cs defines your mailer interface with two methods - Welcome and PasswordReset. Mailers\UserMailer.cs is your Mailer class that implements IUserMailer and extends MailerBase. MailerBase extends ControllerBase so that your mailer is just like your controller.
Your already got meaningful code in UserMailer. For example, take a look at the following Welcome() method:
public virtual MvcMailMessage Welcome()
{
	//ViewBag.Data = someObject;
	return Populate(x => {
              x.ViewName = "Welcome";
              x.To.Add("some-email@example.com");
              x.Subject = "Welcome";
        });

}
Typically, you will edit this method body and pass models to your views, as you do in your controllers. You can use ViewData, ViewBag and strongly typed Models with your views and master pages. Views\UserMailer contains your master page and email views.
However, if you need more mailers, just use the scaffold command as follows:
PM> Scaffold Mailer.Razor CommentMailer CommentPosted,Liked
PM> Scaffold Mailer.Razor ReportMailer ReportProduced,ReportSent,ReportLoading
When you install MvcMailer it automatically sets the Scaffolder called Mailer to one of Mailer.Razor or Mailer.Aspx depending on the project files. So, if you are using Aspx/Razor view engine, your scaffolder will produce aspx and razor views respectively.
When scaffolding, you can provide a switch -NoInterface if you don't like the interface for your mailers. For example the following one will not create IMyMailer,
PM> Scaffold Mailer.Razor MyMailer Welcome -NoInterface

Configure SMTP Client

MvcMailer already added the following smtp configuration section in your Web.config file. Open web.config and you will see the following:
<!-- Method#1: Configure smtp server credentials -->
<smtp from="some-email@gmail.com">
     <network enableSsl="true" host="smtp.gmail.com" port="587" userName="some-email@gmail.com" password="valid-password" />
</smtp>
You can use the credentials from a Gmail account or use your favorite SMTP client - just specify the values in the config file and you are good to go.
In case you want to drop the emails in a local folder, just uncomment Method 2 and comment out method 1.
<!-- Method#2: Dump emails to a local directory -->      
<smtp from="some-email@gmail.com" deliveryMethod="SpecifiedPickupDirectory">
     <specifiedPickupDirectory pickupDirectoryLocation="<email_directory_path>"/>
</smtp>      
In case you are new to the .Net Mail library, this configuration is used by the System.Net.Mail and there is nothing specific to MvcMailer.

Send Email

Edit Your Mailer

You will edit your mailer method and pass useful data to views. For example, you can do the following:
public virtual MvcMailMessage Welcome()
{
	ViewBag.Name = "Sohan";
	return Populate(x =>{
          x.viewName = "Welcome";
          x.To.Add("sohan39@example.com");
        });
}

Pass Data to Mailer Views

From your Mailer, you can use the following ways to pass data to view:
  • Using ViewBag
ViewBag.Name = "Sohan";
ViewBag.Comment = myComment;
  • Using ViewData
ViewData["Name"] = "Sohan";
ViewData["Comment"] = myComment;
  • Using Strongly Typed Model
var comment = new Comment {From = me, To = you, Message = "Great Work!"};
ViewData = new ViewDataDictionary(comment);   
  • Using Strongly Typed Model with default HtmlHelper use @Html.DisplayFor(model => model.Property) in your view
var comment = new Comment {From = me, To = you, Message = "Great Work!"};
ViewData.Model = comment 

Edit Your Master Page

Edit Views/UserMailer/_Layout.cshtml to write your email master page.
<html>
	<head></head>
	<body>
          <h1>MvcMailer</h1>
          @RenderBody()
          <br />
          ---
          <br />
          Thank you!
	</body>
</html>

Edit Your View

Edit Views/UserMailer/Welcome.cshtml to write your email content that goes inside the master page:
Hello @ViewBag.Name:<br />
Welcome to MvcMailer and enjoy your time!<br />

Support for ASPX Views

MvcMailer scaffolder defaults to your project's preferred view engine. So, if you're using ASPX, you will see .Master and .ASPX views instead of the .cshtml ones - but the flavor should be same!

Forcing the Scaffolder to use Razor or ASPX

# This will set Mailer scaffolder to Razor
PM> Set-DefaultScaffolder -Name Mailer -Scaffolder Mailer.Razor

# This will set Mailer scaffolder to Aspx
PM> Set-DefaultScaffolder -Name Mailer -Scaffolder Mailer.Aspx
If you do not intend to change your default Mailer, you can invoke one of the following to produce your desired view files:
# This will produce Razor views
PM> Scaffold Mailer.Razor UserMailer Welcome,GoodBye

# This will produce Aspx views
PM> Scaffold Mailer.Aspx UserMailer Welcome,GoodBye

Absolute URL Using Url.Abs

Unlike the relative URLs in your web app, your email recipients will need to get absolute URLs for links and images. You can use the Url.Abs extension method from MvcMailer as shown below:
Please <a href="@Url.Abs(Url.Action("Index", "Home"))">Visit Us</a> to find more.

Send Mail from Controller

Add a Reference to IUserMailer
using System.Web;
using System.Web.Mvc;
using MvcMailer_Example.Mailers;
using Mvc.Mailer;

namespace MvcMailer_Example.Controllers
{
    public class HomeController : Controller
    {
        private IUserMailer _userMailer = new UserMailer();
        public IUserMailer UserMailer
        {
            get{return _userMailer;}
            set{_userMailer = value;}
        }
...
Use the Reference to Send Email:
//This is important, for the Send() extension method
using Mvc.Mailer;
...
public ActionResult SendWelcomeMessage()
{
     UserMailer.Welcome().Send(); //Send() extension method: using Mvc.Mailer
     return RedirectToAction("Index");
}
Now, you know the basics of sending emails using MvcMailer. However, you could get a lot more done with MvcMailer. Keep reading if you are interested!

Unit Test Your Mailers

If you have a mailer method like the following:
public virtual MvcMailMessage Welcome()
{
	ViewBag.Name = "Sohan";
	return Populate(x =>{
          x.Subject = "Welcome to MvcMailer";
	  x.To.Add("sohan39@example.com");
          x.viewName = "Welcome";
        });
}
You can write a unit test code like this:
//Test using NUnit and Moq

using System.Linq;
using NUnit.Framework;
using Moq;
using MvcMailer_Example.Mailers;
using System.Net.Mail;

namespace MvcMailer_Example.Tests.Mailers
{
    [TestFixture]
    public class UserMailerTest
    {
        private Mock<UserMailer> _userMailerMock;
        
        [SetUp]
        public void Setup()
        {
            //setup the mock
            _userMailerMock = new Mock<UserMailer>();
            //CallBase will ensure it calls real implementations other than the mocked out methods
            _userMailerMock.CallBase = true;
        }

        [Test]
        public void Test_WelcomeMessage()
        {
            //Arrange: Moq out the PopulateBody method
            _userMailerMock.Setup(mailer => mailer.PopulateBody(It.IsAny<MailMessage>(), "Welcome", null));

            //Act
            var mailMessage = _userMailerMock.Object.Welcome();

            //Assert
            _userMailerMock.VerifyAll();
            Assert.AreEqual("Sohan", _userMailerMock.Object.ViewBag.Name);
            Assert.AreEqual("Welcome to MvcMailer", mailMessage.Subject);
            Assert.AreEqual("sohan39@example.com", mailMessage.To.First().ToString());
        }

    }
}

Unit Test A Controller That Uses Mailer

Since the scaffold generates the interface for you, its as easy as testing model repositories. Say, you have the following controller action that sends an Email:
public ActionResult SendWelcomeMessage()
{
    UserMailer.Welcome().Send();
    return RedirectToAction("Index");
}
You can write the following test for this:

using System.Net.Mail;
using System.Web.Mvc;
using Moq;
using Mvc.Mailer;
using MvcMailer_Example.Controllers;
using MvcMailer_Example.Mailers;
using NUnit.Framework;

namespace MvcMailer_Example.Tests.Controllers
{
    [TestFixture]
    public class HomeControllerTests
    {
        private Mock<IUserMailer> _userMailerMock;
        private HomeController _homeController;

        [SetUp]
        public void Setup()
        {
            _homeController = new HomeController();

            _userMailerMock = new Mock<IUserMailer>();
            _homeController.UserMailer = _userMailerMock.Object;
            
            MailerBase.IsTestModeEnabled = true;
        }

        [TearDown]
        public void TearDown()
        {
            TestSmtpClient.SentMails.Clear();
        }

        [Test]
        public void Test_SendWelcomeMessage()
        {
            //Arrange
            var mailMessage = new MailMessage();
            _userMailerMock.Setup(userMailer => userMailer.Welcome()).Returns(mailMessage);

            //Act
            var actionResult = _homeController.SendWelcomeMessage();

            //Assert
            _userMailerMock.VerifyAll();
            Assert.AreEqual(1, TestSmtpClient.SentMails.Count);
            Assert.AreEqual(mailMessage, TestSmtpClient.SentMails[0]);

            var routeValues = (actionResult as RedirectToRouteResult).RouteValues;
            Assert.AreEqual(routeValues["action"], "Index");
        }
    }
}

Send Email with Attachments

Just add your attachments to your MailMessage object. For example, you can do the following:
public virtual MvcMailMessage Welcome(string attachmentPath)
{
	return Populate(x => {
          x.viewName = "Welcome";
          x.Attachments.Add(new Attachment(attachmentPath));
        });
}

Send Multi-part Emails

You can send both text/plain and text/html parts for a single email. Just add your text and html views like the following:
Views
|--- UserMailer
     |--- _Layout.cshtml              => email master page for text/html
     |--- Welcome.cshtml              => email content for text/html

     |--- _Layout.text.cshtml         => email master page for text/plain
     |--- Welcome.text.cshtml         => email content for text/plain
MvcMailer will look for both text and html versions. In case it finds both, it will send a multi-part email containing both parts. Otherwise, it will decide based on what is passed to UserMailer.IsBodyHtml property.
You can use the -WithText switch to Scaffold both html and plain text view files using the following.
PM> scaffold Mailer MyMailer Hello -WithText
Added MvcMailer output 'Mailers\IMyMailer.cs'
Added MvcMailer output 'Mailers\MyMailer.cs'
Added MyScaffolder output 'Views\MyMailer\_Layout.cshtml'
Added MyScaffolder output 'Views\MyMailer\Hello.cshtml'
Added MyScaffolder output 'Views\MyMailer\_Layout.text.cshtml'
Added MyScaffolder output 'Views\MyMailer\Hello.text.cshtml'

Send Email Asynchronously

You can simply use the SendAsync extension method for MailMessage:
using Mvc.Mailer;
...

public ActionResult SendWelcomeMessage()
{
    UserMailer.Welcome().SendAsync();
    return RedirectToAction("Index");
}

Test Asynchronous Email Sending

If you use SendAsyc, you can write the following code to test it:
[TearDown]
public void TearDown()
{
    TestSmtpClient.SentMails.Clear();
    TestSmtpClient.WasLastCallAsync = false;
}

[Test]
public void Test_SendWelcomeMessage_sends_async()
{
    //Arrange
    var mailMessage = new MailMessage();
    _userMailerMock.Setup(userMailer => userMailer.Welcome()).Returns(mailMessage);

    //Act
    var actionResult = _homeController.SendWelcomeMessage();

    //Assert
    _userMailerMock.VerifyAll();
    Assert.IsTrue(TestSmtpClient.WasLastCallAsync);
}

Handle Events for Asynchronous Email

You may want to handle events related to Asynchronous Emails. For example, you might need to take action when an asynchronous email sending is successful or not. Here's an example for you:
var client = new SmtpClientWrapper();

client.SendCompleted += (sender, e) =>
{
    if (e.Error != null || e.Cancelled)
    {
        // Handle Error
    }

    //Use e.UserState
};

new MyMailer().Welcome().SendAsync("user state object", client);

Embed Image or LinkedResource Inside Email

Sometimes you want to embed an image or other resources directly inline with the email. This is better for cases when you want the recipients to see the images and other resources while offline. MvcMailer makes is simpler for you. Here is an example:

In Your View

@Html.InlineImage("logo", "Company Logo")
Here, cid:logo will refer to the resource with Id logo. To set this resource, in your mailer do the following:

In Your Mailer

var resources = new Dictionary<string, string>();
resources["logo"] = logoPath;
PopulateBody(mailMessage, "WelcomeMessage", resources);

Email Sending from a Background Process

Do you need to send emails from a background process? Yes, you're right. You don't want to block your request/response cycle for that notification email to be sent. Instead, what you want is a background process that does it for you, even if it's sent after a short delay. Here's what you can do:
  • Save your email related data into a database.
  • Create a REST/SOAP web service that sends out the emails. This will ensure your Mailer has access to the HttpContext, which is essential for the core ASP.NET MVC framework to work properly. For example, to find your views, produce URLs, and perform authentication/authorization.
  • Create a simple App that calls the web service. This could be a windows service app or an executable app running under Windows Scheduled task.
A future version of MvcMailer is likely to have support for this. But it is hard because of two reasons:
  • MailMessage is not Serializable out of the box and has a lot of complex fields and associations.
  • The core ASP.NET framework still needs HttpContext :(

Upcoming Features

  • Email sending from a background process
  • VB Code example

Troubleshooting

Thursday, 7 November 2013

Adding Email Confirmation to ASP.NET MVC




Adding Email Confirmation to ASP.NET MVC


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).
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
[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.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public MembershipCreateStatus CreateUser(string userName, string password, stringemail)
{
    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, outstatus);
    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.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
[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:
1
2
3
4
5
6
7
8
9
10
11
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:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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
1
2
using System.Net.Mail;
using System.Configuration;
namespaces, and you’ll need to add the following to your web.config file:
1
2
3
4
5
6
7
8
<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:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<@ 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:
1
2
3
4
5
6
7
8
9
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.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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
1
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:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<%@ 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 asyou 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:
1
2
3
4
5
6
7
8
9
10
11
12
13
<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()) { %>