Extend MVC controller to support Toastr messages

In an earlier post I showed how to create an MVC wrapper for Toastr, a JavaScript library used for displaying user messages in the web browser. In the post I mentioned that there are two ways to make it possible to call an AddToastMessage function directly from the action methods in the controller:

  1. Implement an abstract base controller class, extending the normal Controller class, and then let all your controllers inherit from this new base controller class. You can then add an instance of the Toastr class to your new base controller class in this way.
  2. Extend the controller class with an AddToastMessage method. This is a simpler solution and involves using the TempData collection for storage. The reason for using TempData and not ViewData is that user messages are saved between redirections as well. My previous post showed how to solve this solution.

This post focuses on the part where the first solution differs from the second one. The main idea is that you instead of storing everything in TempData all the time, store the messages in a Toastr object inside the controller and then serialize and deserialize the object only as needed. If the ActionResult of the action method is of type ViewResult, then we move Toastr to the ViewData collection. If the ActionResult is of type RedirectionResult (user is being redirected to another page), then we move Toastr to the TempData collection. Also, when an action method is initiated we check for an existing Toastr object in the TempData collection, for situations where we’ve been redirected.

First we create the abstract controller that all other controller classes will inherit from. The AddToastMessage function in this controller will replace the one in the extension method we created in the earlier post. Don’t use both of them at the same time.

public abstract class MessageControllerBase : Controller
{
  public MessageControllerBase()
  {
    Toastr = new Toastr();
  }
  public Toastr Toastr { get; set; }

  public ToastMessage AddToastMessage(string title, string message, ToastType toastType)
  {
    return Toastr.AddToastMessage(title, message, toastType);
  }
}

We then need to create an action filter to transform the value of the Toastr object in the controller so it can be passed on. That is done in the following way:

public class MessagesActionFilter : ActionFilterAttribute
{
  // This method is called BEFORE the action method is executed
  public override void OnActionExecuting(ActionExecutingContext filterContext)
  {
    // Check for incoming Toastr objects, in case we've been redirected here
    MessageControllerBase controller = filterContext.Controller as MessageControllerBase;
    if (controller != null)
      controller.Toastr = (controller.TempData["Toastr"] as Toastr) 
                           ?? new Toastr();

    base.OnActionExecuting(filterContext);
  }

  // This method is called AFTER the action method is executed but BEFORE the
  // result is processed (in the view or in the redirection)
  public override void OnActionExecuted(ActionExecutedContext filterContext)
  {
    MessageControllerBase controller = filterContext.Controller as MessageControllerBase;
    if (filterContext.Result.GetType() == typeof(ViewResult))
    {
      if (controller.Toastr != null && controller.Toastr.ToastMessages.Count() > 0)
      {
        // We're going to a view so we store Toastr in the ViewData collection
        controller.ViewData["Toastr"] = controller.Toastr;
      }
    }
    else if (filterContext.Result.GetType() == typeof(RedirectToRouteResult))
    {
      if (controller.Toastr != null && controller.Toastr.ToastMessages.Count() > 0)
      {
        // User is being redirected to another action method so we store Toastr in
        // the TempData collection
        controller.TempData["Toastr"] = controller.Toastr;
      }
    }
  
    base.OnActionExecuted(filterContext);
  }
}

To execute the action filter every time an action method is called we register this class in the App_Start/FilterConfig.cs file like this:

public class FilterConfig
{
  public static void RegisterGlobalFilters(GlobalFilterCollection filters)
  {
    filters.Add(new HandleErrorAttribute());
    filters.Add(new MessagesActionFilter());
  }
}

One final thing we need to change is in the _Toastr.cshtml partial view from the previous post. We have in this new post used ViewData, while we in the other post used TempData (just out of convenience). The correct version of partial view should be like this:

@using MyWebApplication.Toast
@if (ViewData.ContainsKey("Toastr"))
{
  Toastr toastr = ViewData["Toastr"] as Toastr;
  @ToastrBuilder.ShowToastMessages(toastr);
}

Testing
To test it all we can implement two action methods in the Home controller (that now extends our new controller class!).

public class HomeController : MessageControllerBase 
{
  public ActionResult Index()
  {
    this.AddToastMessage("Congratulations", "You made it all the way here!", ToastType.Success);
    return View();
  }

  public ActionResult GoSomewhereElse()
  {
    this.AddToastMessage("Redirected message", "This message has been redirected", ToastType.Info);
    return RedirectToAction("Index");
  }
}

If everything works fine you should see one user message when navigating to /Home/Index and two user messages (as seen on the picture below) when navigating to /Home/GoSomewhereElse.
Toastr example
TempData vs. ViewData
Finally I would just like to mention something about TempData and ViewData. In the first post I used TempData all the way through for the simple reason that I wanted to demonstrate other features. In this post I separated the data into ViewData (when continuing to a View) and TempData (when redirecting to another action method). This is also the way it should be.

  • TempData – for communication between controller and controller
  • ViewData – for communication between controller and view

Which method is best?
Should you create an extension method for the controller, or create your own controller object (as we did here)? It’s all a matter of personal taste.

The previous post used the original MVC controller object but then had to serialize and deserialize the Toastr object for every time a message was added, and serialization does comes with a cost.

This post shows how to access the Toastr object directly and only deserialize the object if it’s redirected from a previous action method. The downside here is that you have to remember to use this special controller class.

I will later on post an example of how Toastr can be used in the same way even for Web.API and JSON requests. In that post I’ll use the template suggested in this post.