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

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.

Written by Jonathan "JD" Danylko • Last Updated: • MVC •
Password Dialog Box

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:

  • The UnitOfWork is acquired by the ControllerContext explained in a post I wrote about Accessing Your Data Layer Using Unique Requests. Of course, you can use any method to verify the username and password passed into the ProcessForm method. This was just an example.
  • The ViewMessage is part of my ViewModel. It's strictly a POCO (Plain Ole CLR Object) and the property was added to my BaseViewModel so any model can have an optional message to display. You can even add an exception at this point to catch the error and display it. This BaseViewModel technique was taken from a post called Make a BaseViewModel for Your Layouts.
  • Since we modified our interface, we now have to return a true or false to find out whether the ProcessForm was successful or failed.

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 want to authenticate a user using a different technology (i.e. MS Membership or ActiveDirectory), replace your AuthenticateFormHandler with a new authentication class. I could see an ActiveDirectoryAuthenticateFormHandler or a MembershipFormHandler. That's it!
  • The Success and Failure Func<T, ActionResult> receive the ViewModel. That model can be manipulated in the FormHandler. You could even return back a populated ViewModel based on the user's roles and redirect them to another URL based on the data in the ViewModel.
  • You can reuse this LoginActionResult anywhere. You just need to define how to authenticate the user through the FormHandler you created.

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!

Did you like this content? Show your support by buying me a coffee.

Buy me a coffee  Buy me a coffee
Picture of Jonathan "JD" Danylko

Jonathan Danylko is a web architect and entrepreneur who's been programming for over 25 years. He's developed websites for small, medium, and Fortune 500 companies since 1996.

He currently works at Insight Enterprises as an Principal Software Engineer Architect.

When asked what he likes to do in his spare time, he replies, "I like to write and I like to code. I also like to write about code."

comments powered by Disqus