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:
- We’ll create a
Notifier
class to hold our messages
- 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
- 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