Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Send a Success Message to a View using TempData

0.00/5 (No votes)
4 Jan 2016 1  
Send a success message to a View using TempData

One common scenario presents itself over and over when developing MVC websites. How do we provide information to our users when they post data back to our server? Often, our users will fill in a form and post it back to the server. They now need a visual response to let them know that the operation was successful. In this post, I’ll be exploring the idea of adding messages to TempData. We’ll display the messages in our view via an Html helper.

TempData is a key/value store that saves data for the duration of the current and next requests. This means that we can use it to hold a success message when our POST action fires. The message will be available if we return a ViewResult from our POST action. It will also be available on the next request if we redirect to a GET action. This is the PRG (Post-Redirect-Get) pattern. When following PRG, we process data during a POST request. If there are errors, perhaps due to validation or data, we redisplay the view. If the operation is successful, we redirect to the GET action for that view.

One of the main advantages of this pattern is data integrity. Once we post data, we don't want the same operation to run again on the server. If the user refreshes the page, we repeat the last operation. With PRG, the repeated operation is the GET operation that happens after the redirect. It's not the POST operation, so the posted data is not sent to the server a second time. PRG has the added advantage of not showing the user a "resubmit form" message. This improves the user experience.

Our solution to this problem comprises three parts:

  1. We’ll create a Notifier class to hold our messages
  2. We'll write the messages to TempData within an Action Filter. We’ll create an Html helper to read from TempData and display the message on our view
  3. We’ll inject the INotifier interface into our controller

We'll also expose methods for writing success, warning and error messages.

First Up, Meet the Notifier

Our Notifier class is nice and straightforward. It contains a list of messages and methods to add messages with different severity. The message itself contains a Generate method. This allows each message to create its own display markup.

public enum MessageSeverity
{
    None,
    Info,
    Success,
    Warning,
    Danger
}

public class Message
{
    public MessageSeverity Severity { get; set; }

    public string Text { get; set; }

    public string Generate()
    {
        var isDismissable = Severity != MessageSeverity.Danger;
        if (Severity == MessageSeverity.None) Severity = MessageSeverity.Info;
        var sb = new StringBuilder();
        var divTag = new TagBuilder("div");
        divTag.AddCssClass("alert fade in");
        divTag.AddCssClass("alert-" + Severity.ToString().ToLower());

        var spanTag = new TagBuilder("span");
        spanTag.MergeAttribute("id", "MessageContent");

        if (isDismissable)
        {
            divTag.AddCssClass("alert-dismissable");
        }

        sb.Append(divTag.ToString(TagRenderMode.StartTag));

        if (isDismissable)
        {
            var buttonTag = new TagBuilder("button");
            buttonTag.MergeAttribute("class", "close");
            buttonTag.MergeAttribute("data-dismiss", "alert");
            buttonTag.MergeAttribute("aria-hidden", "true");
            buttonTag.InnerHtml = "×";
            sb.Append(buttonTag.ToString(TagRenderMode.Normal));
        }

        sb.Append(spanTag.ToString(TagRenderMode.StartTag));
        sb.Append(Text);
        sb.Append(spanTag.ToString(TagRenderMode.EndTag));
        sb.Append(divTag.ToString(TagRenderMode.EndTag));

        return sb.ToString();
    }
}

public interface INotifier
{
    IList<Message> Messages { get; }
    void AddMessage(MessageSeverity severity, string text, params object[] format);
}

public class Notifier : INotifier
{
    public IList<Message> Messages { get; private set; }

    public Notifier()
    {
        Messages = new List<Message>();
    }

    public void AddMessage(MessageSeverity severity, string text, params object[] format)
    {
        Messages.Add(new Message { Severity = severity, Text = string.Format(text, format) });
    }
}

Each message generates a Bootstrap alert and the severity allows us to change the background colour.

Next Up, Let's Interact with TempData

We've done the first part. We've got a Notifier and we can add message to it. How do we display those in our View? We add them to TempData via an Action filter. We then read them within our view via an extension method. Let's see how that would look.

public static class Constants
{
    public const string TempDataKey = "Messages";
}

public class NotifierFilterAttribute : ActionFilterAttribute
{
    public INotifier Notifier { get; set; }

    public override void OnActionExecuted(ActionExecutedContext filterContext)
    {
        var messages = Notifier.Messages;
        if (messages.Any())
        {
            filterContext.Controller.TempData[Constants.TempDataKey] = messages;
        }
    }
}

public static class NotifierExtensions
{
    public static void Error(this INotifier notifier, string text, params object[] format)
    {
        notifier.AddMessage(MessageSeverity.Danger, text, format);
    }

    public static void Info(this INotifier notifier, string text, params object[] format)
    {
        notifier.AddMessage(MessageSeverity.Info, text, format);
    }

    public static void Success(this INotifier notifier, string text, params object[] format)
    {
        notifier.AddMessage(MessageSeverity.Success, text, format);
    }

    public static void Warning(this INotifier notifier, string text, params object[] format)
    {
        notifier.AddMessage(MessageSeverity.Warning, text, format);
    }

    public static MvcHtmlString DisplayMessages(this ViewContext context)
    {
        if (!context.Controller.TempData.ContainsKey(Constants.TempDataKey))
        {
            return null;
        }

        var messages = (IEnumerable<Message>)context.Controller.TempData[Constants.TempDataKey];
        var builder = new StringBuilder();
        foreach (var message in messages)
        {
            builder.AppendLine(message.Generate());
        }

        return builder.ToHtmlString();
    }

    private static MvcHtmlString ToHtmlString(this StringBuilder input)
    {
        return MvcHtmlString.Create(input.ToString());
    }
}

DisplayMessages requires ViewContext so that we can get at TempData. We need to access our messages in TempData using the same key. I've added the TempData key as a constant to avoid using a "magic string". I've also added some handy extension method shortcuts for the different message types. Check out this article if you want to understand Action Filters better: Filtering in ASP.NET MVC

We can add our extension method call to our main Layout page so it's always available. Pop this somewhere near the top of the body tag:

<div id="MessageContainer">
    <div class="container">
        @Html.ViewContext.DisplayMessages()
    </div>
</div>

Finally, Let's Meet the DI Container

The only thing left now is to wire up our Notifier. For that, we turn to the popular DI Container, Autofac. If you’re not familiar with Autofac, they have great documentation. Here's some links to help you get started:

Here's our Global.asax.cs with our Notifier dependency registered.

using Autofac;
using Autofac.Integration.Mvc;

public class MvcApplication : HttpApplication
{
    protected void Application_Start()
    {
        AreaRegistration.RegisterAllAreas();
        FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
        RouteConfig.RegisterRoutes(RouteTable.Routes);
        BundleConfig.RegisterBundles(BundleTable.Bundles);
        var builder = RegisterDependencies();
        var container = builder.Build();
        var mvcResolver = new AutofacDependencyResolver(container);
        DependencyResolver.SetResolver(mvcResolver);
    }

    private static ContainerBuilder RegisterDependencies()
    {
        var builder = new ContainerBuilder();
        var currentAssembly = Assembly.GetExecutingAssembly();

        builder.RegisterType<Notifier>().AsImplementedInterfaces().InstancePerRequest();
        builder.RegisterType<NotifierFilterAttribute>().AsActionFilterFor
        <Controller>().PropertiesAutowired();

        builder.RegisterFilterProvider();
        builder.RegisterControllers(currentAssembly);

        return builder;            
    }
}

What's going on here? The main thing to note is when we register the filter, we include PropertiesAutowired(). This ensures that our Notifier property in the filter gets wired up.

Whenever we want to display a message, we inject the INotifier and add it.

public class HomeController : Controller
{
    private readonly INotifier notifier;

    public HomeController(INotifier notifier)
    {
        this.notifier = notifier;
    }

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

    [HttpPost]
    public ActionResult NotifyTest(FormCollection form)
    {
        notifier.Success("A success message");
        return RedirectToAction("NotifyTest");
    }
}

There’s a Lot Going On Here. What Did We Just Do?

Let’s recap on what we’ve done here.

  • We created a Notifier for displaying our messages
  • We created a Message and gave it the ability to generate itself
  • We created an action filter so we can add messages to TempData on every request
  • We created an extension method to read our messages from TempData
  • We registered the Notifier with Autofac
  • We injected the Notifier into a Controller which needed to pass our View a message
  • We called one of our extension methods on the Notifier to display the message

View the original article

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here