Introduction
In web application development, it is customary to follow a Post/Redirect/Get (PRG) pattern for pages containing forms. Creating, editing and updating data is usually performed using a POST
request, followed by a redirect response to the user using the RedirectToAction()
method. This however introduces a problem when you want to flash the user a message that his or her operation was (or wasn't) completed as requested. This article presents a solution to this problem for ASP.NET MVC called FlashMessage
.
Background
The Problem with Notifications in PRG Applications
When a web application makes use of forms, a potential problem can occur when a user submits a form and attempts to refresh the page. The action executed in response to the form submission will be executed again, possibly leading to undesirable results. This problem is illustrated in the image below:
In order to circumvent this problem, the Post/Redirect/Get (PRG) pattern is typically employed. The client is always redirected directly after a form submission. In ASP.NET MVC, the controller method RedirectToAction()
is typically used for this. If the users then refresh the page either using refresh or by using the back and forward buttons, the form is not submitted again. This is illustrated in the image below:
Solving the problem like this does however pose a problem if we want to notify the user after the form submission about the result of the operation. Because the GET
request may be to another server in the cluster when using load balancing, it's not possible to just assume that you can store the notification locally.
Source of images: Wikipedia Post/Redirect/Get (PRG)
The Solution
This article presents a solution for this problem for ASP.NET MVC applications using cookies and/or the session state. It allows you to "flash" a message to the user before returning the redirect. The message is persisted to the next request where it is actually rendered.
Queuing Messages
Messages must be able to be queued for display at any point where an HTTP context is available. In addition, it would be nice to include full HTML when needed so that for example a link can be passed through the message. Also a few different types of messages would be handy to be able to style them as warnings, confirmations, etc.
In order to cover all these cases, each message is represented as follows:
public class FlashMessageModel
{
public bool IsHtml { get; set; }
public string Title { get; set; }
public string Message { get; set; }
public FlashMessageType Type { get; set; }
public FlashMessageModel()
{
IsHtml = false;
Type = FlashMessageType.Info;
}
}
public enum FlashMessageType : byte
{
Info,
Warning,
Danger,
Confirmation
}
The IsHtml
property indicates if the message content in the Message
property should be rendered as raw HTML (watch out for HTML injection here). The Title
property stores an optional title and the Type
property holds one of the messages types as defined by the FlashMessageType enum
.
In order to queue a message, the static FlashMessage.Queue()
method was implemented. This method retrieves the current set of queued flash messages and appends the new message to the queue before storing the queue again.
In order to ease usage, a few shorthand methods are provided which directly queue various types of messages. These methods also accept a format string
and variable arguments to prevent the programmer from having to write lengthy statements like: FlashMessage.Confirmation(string.Format("format...", args))
all the time.
Serializing the Messages
In order to keep the message size to a minimum and to reduce overhead, the messages are serialized using binary serialization. The BinaryReader
and BinaryWriter
classes are used to this end. While performance is not really an issue as the messages should be short anyway, the binary serialization should perform pretty well.
Storing the Messages
In order to storage the messages between one request to another, a 'transfer
' method is required which takes care of transferring the data from the POST
request to the GET
request.
TempData
ASP.NET MVC provides every controller with a TempData
dictionary for storing temporary data between requests. At the next request, its contents are typically discarded, which would initially suggest that it's a good fit for storing user notifications in our scenario. For the following reasons, I however chose not to use TempData
:
TempData
uses sessions behind the scene. I tend not to use sessions in most applications.
TempData
requires access to the current controller which may not always be available, for example when not using ASP.NET MVC.
TempData
by default always discards all data at the next request, while I would want the notifcations to be retained until they are acutally shown.
In order to work around these shortcomings, two transport methods are provided in this framework. The tranport providers inherit the IFlashMessageTransport
interface.
Transport via Cookie
Because most messages will be short, I consider transporting the messages via cookies a viable option. Tranport via cookies is implemented by the FlashMessageCookieTransport
class. This is the default transport for the framework as it should work in almost any scenario.
When using cookie transport, it is of utter importance to prevent the client from tampering with the cookie. The cookie is exposed to potentially harmful JavaScript on the client side and can thus be manipulated if not secured properly. In order to prevent this, the cookie is encoded using the System.Web.Security.MachineKey.Encode()
method.
Transport via Session
Another option is to transport the messages via the session
state, which is essentially the same what TempData
does. The difference when using the session state directly is that the dependency with on the controller method is removed, making the code usable outside the ASP.NET MVC framework.
Transport via the session state is provided by the FlashMessageSessionTransport
class. Note that when using session
transport, you'll need to make sure that the session
state storage is shared across all server processes.
Displaying Messages
When displaying the messages, the messages are retrieved from the transport provider and removed from the message store. Displaying them can be done using a custom PartialView
, or using the included FlashMessageHtmlHelper
class which extends the ASP.NET HtmlHelper
class.
The typical manner to show the messages would be to include a call to Html.RenderFlashMessages()
at the top of your page in the master or layout template like this:
<div class="container">
@Html.RenderFlashMessages()
@RenderBody()
</div>
The included FlashMessageHtmlHelper
class formats the messages for the widely used Twitter Bootstrap framework.
Using the Code
- First of all, you'll need to add references to ASP.NET MVC and this package
FlashMessage
. Also make sure that the Web.Vereyon
namespace is imported in the relevant code files.
- Next, you need to make sure that the messages are rendered properly. The most obvious manner would be to include the following code in your Razor layout template before the
RenderBody()
call:
@Html.RenderFlashMessages()
Make sure that you also include the necessary Twitter Bootstrap stylesheets, or write your own.
3. Now queue messages before your redirects using one of the following calls:
FlashMessage.Info("Your informational message");
FlashMessage.Confirmation("Your confirmation message");
FlashMessage.Warning("Your warning message");
FlashMessage.Danger("Your danger alert");
4. Done! Your code should now look something like the following:
[HttpPost]
public ActionResult Save()
{
FlashMessage.Confirmation("The entity was saved.");
return RedirectToAction("View", new { Id = entity.id });
}
Advanced Options
Each of the presented methods to queue a message internally uses the FlashMessage.Queue()
method which is defined as follows:
FlashMessage.Queue(string message, string title, FlashMessageType messageType, bool isHtml);
The FlashMessage.Queue()
method allows one to specify values for each of the FlashMessage
classes properties.
In order to reduce the length of the code required to schedule a message, every instance of the standard message scheduling methods also accepts a format string
and a variable argument list. This prevents the user from having to include verbose String.Format()
calls.
FlashMessage.Info(string format, params object[] args);
Custom Message Rendering (Optional)
In case you do not want to use the Twitter Bootstrap styles messages from the FlashMessageHtmlHelper
, then you need to write your own message rendering code. A good way to start doing this is including the following code in your Razor layout template:
@foreach(var message in FlashMessage.Retrieve(Html.ViewContext.HttpContext))
{
Html.RenderPartial("FlashMessage", message);
}
The call to FlashMessage.Retrieve()
retrieves all the queued flash messages and removes them from the queue. You should create a partial view called FlashMessage
which renders each of the FlashMessageModel
s.
Configure Transport (Optional)
In case you want to configure the transport being used, the best place to do this would be in the application startup. The transport provider used is exposed using the static FlashMessage.Transport
property. Setting a different transport is done as follows:
FlashMessage.Transport = new FlashMessageSessionTransport();
Points of Interest
The source contains a test project which defines some unit tests to verify the functionality of the code. While the test coverage is not super extensive, some serialization and deserialization as well as the various transports providers are tested. Also some likely scenario's such as missing or malformed cookie data are tested.
History
Find the latest version at https://github.com/Vereyon/FlashMessage