ASP.NET MVC ActionResult: How to implement a login screen using an ActionResult

January 5th, 2015

Some of the thin controller posts were using ActionResults in a wierd way. Today, we'll go step-by-step through creating a custom ActionResult for logging in a user.

Based on a couple of posts I wrote, some users asked me some questions regarding the thin controllers with ActionResults.

There were also some requests about how to use that particular technique to log in a user. How would you use an ActionResult through the controller?

So guess what today's post is about?

Uh huh...you guessed it! Since the HTML should be easy enough, this post will focus more on the controller layer instead of how to setup the actual page.

Overview

The posts I'm referring to are the Skinniest Controllers You've Ever Seen, Part 1 & Part 2. In those posts, we created an ActionResult called CreateUpdateActionResult.

NOTE: Since that post, I renamed my method to ProcessResult<T>, but it functions the same way.

ProcessResult.cs

public class ProcessResult<T> : ActionResult
{
    protected readonly T Model;
    
    public IGeneralFormHandler<T> Handler { getset; }
    public Func<T, ActionResult> SuccessResult;
    public Func<T, ActionResult> FailureResult;
 
    public ProcessResult(T model)
    {
        Model = model;
    }
 
    public ProcessResult(T model, 
        IGeneralFormHandler<T> handler, 
        Func<T, ActionResult> successResult,
        Func<T, ActionResult> failureResult )
    {
        Model = model;
        Handler = handler;
        SuccessResult = successResult;
        FailureResult = failureResult;
    }
 
    public override void ExecuteResult(ControllerContext context)
    {
        var viewData = context.Controller.ViewData;
 
        if (viewData.ModelState.IsValid)
        {
            Handler.ProcessForm(context, Model);
            SuccessResult(Model).ExecuteResult(context);
        }
        else
        {
            FailureResult(Model).ExecuteResult(context);
        }
    }
}

This ActionResult's purpose was to abstract the "meat" from the controllers and place it into it's own class which solidifies the concept of SRP. It does one thing and one thing very well.

In the ExecuteResult, we are working with a ViewModel that has EntityFramework entities and using the ValidationAttributes ([Required], [Range], etc.) to determine if the model is valid or not.

However, in our new LoginActionResult, we need to use an authentication routine (whatever it is you are using) to find out if we have a valid user or not.

So let's get started with our refactoring/creation of our new ActionResult.

Time to Refactor!

In the post, we also used a concrete implementation of a FormHandler and an interface to process the model data called IGeneralFormHandler. The ProcessForm method defined in the interface doesn't return anything, but we need it to return a boolean instead so we have to fix it.

public interface IGeneralFormHandler<TModel>
{
    bool ProcessForm(ControllerContext context, TModel model);
}

Of course, we need to change all of the FormHandler implementations to return a true/false, but this will allow a more "black or white" way to find out whether the ProcessForm was a success or a failure.

This was an oversight on my part from my original design.

Creating the FormHandler

To process the user input, we need a way to authenticate the user with the database or use Microsoft Membership (whichever is convenient for you).

For this example, we'll create an AuthenticateFormHandler. If everything was processed properly, the ProcessForm method in the AuthenticateFormHandler will return true. If not, we return false and since the model contains a message, we will display the message.

Here is what the code looks like.

public class AuthenticateFormHandler : IGeneralFormHandler<LoginAdminViewModel>
{
    public bool ProcessForm(ControllerContext context, LoginAdminViewModel model)
    {
        var unitOfWork = context.GetUnitOfWork<AdminUnitOfWork>();
        var user = unitOfWork.LoginRepository.GetByUserNameAndPassword(model.User.UserName, model.User.Password);
        if (user != null)
        {
            FormsAuthentication.SetAuthCookie(user.UserName, true);
 
            return true;
        }
 
        model.Message = new ViewMessage()
        {
            MsgText = "Invalid Username/Password. Please try again.",
            MsgTitle = "Error",
            MsgType = MessageType.Error
        };
 
        return false;
    }
}

A couple of notes about the code:

Let's create some Action, Jackson!

Since that takes care of the processing of the user input, now we need to create our LoginActionResult that takes our AuthenticateFormHandler as a parameter.

Our LoginActionResult will be almost exactly like the ProcessResult, but the ExecuteResult method will act a little bit different.

LoginActionResult.cs

public class LoginActionResult<T> : ActionResult
{
    protected readonly T Model;
 
    public IGeneralFormHandler<T> Handler { getset; }
    public Func<T, ActionResult> SuccessResult;
    public Func<T, ActionResult> FailureResult;
 
    public LoginActionResult(T model)
    {
        Model = model;
    }
 
    public LoginActionResult(T model, 
        IGeneralFormHandler<T> handler,
        Func<T, ActionResult> successResult,
        Func<T, ActionResult> failureResult )
    {
        Model = model;
        Handler = handler;
        SuccessResult = successResult;
        FailureResult = failureResult;
    }
 
    public override void ExecuteResult(ControllerContext context)
    {
        var success = Handler.ProcessForm(context, Model);
        if (success)
        {
            SuccessResult(Model).ExecuteResult(context);
        }
        else
        {
            FailureResult(Model).ExecuteResult(context);
        }
    }
}

If you notice, the only difference between this and the ProcessResult class is the ExecuteResult method. This ExecuteResult method uses the Handlers.ProcessForm method and checks the return value from our AuthenticateFormHandler to find out whether the user entered valid credentials (true) or not (false).

If it was successful, it will redirect them on to their main page after login. If not, it will display the error based on the message returned from the model and stay on the login page.

The Final Touches...The Controller

As we move our way up the layers, we finally come to our controller.

The Index method (POST) that handles the "Postback" would look like this.

[HttpPostValidateAntiForgeryToken]
public ActionResult Index(LoginAdminViewModel viewData)
{
    return new LoginActionResult<LoginAdminViewModel>(
                    viewData,
                    new AuthenticateFormHandler(),
                    viewModel => Redirect(Url.MainUrl()),
                    viewModel => View(viewModel));
}

Based on the Skinniest Controller part 1 & 2, this should look familiar. One line to handle our user login.

...annnddddd done!

Conclusion

In this post, we walked through a simple login controller and how to pass the data using our ViewModel to find out if they are authenticated or not. This particular technique allows you to extend the code even further.

How? Here are a couple suggestions:

If you have any suggestions on making the code better, please post your comments below.

Also, I almost forgot...Happy New Year!

*QUICK NOTE*

I'm heading up to Sandusky, Ohio for an entire week of Codemash. I hope to see some fellow developers up there.

Keep your eyes peeled for me. You'll now I'm there when you see my checkered Vans!