When running web applications, you typically want to inform the user of results - success or failure of their actions. This involves failures to login, error messages, completion results and many other stuff the application needs to communicate. The usual scenario, to simplify just a little bit, usually goes like this:
In this case it's pretty straightforward to cover the notification. You basically just need a simple NotificationService that has a basic repository, a Stack<> if you will, that holds messages. Using Dependency injection you then pass this notification service around and when the service layers performs something, it calls the service's AddNotificationmethod. In your master page, you simply put this into the area where you want notifications:
1: @{`` 2: NotificationMessage message = null;`` 3: while ((message = NotificationService.PopNotificationMessage()) != null)`` 4: {`` 5: <divclass="alert-message @message.Type">`` 6: <aclass="close"href="#">×</a>`` 7: <p>@Html.Raw(message.Message)</p>`` 8: </div>`` 9: }`` 10: }
If there are notifications waiting in your stack, they will display in the application. If there aren’t no problem, nothing gets rendered. But what happens when, instead of simply returning the result, like pictured above, the application needs to redirect the user. This is frequent and is considered best practice with form submissions. Because this implies a new request, my simple stack from above got cleared.
The first approach most web developers, especially .NET (ASP.NET & MVC) developers, think of involves using sessions. Sessions however, have one big problem – they don’t do well on multi-server installation without using a session state provider, like MSSQL. This usually means complications, it involves some performance penalty and implies you need a MSSQL database. If you’re hosting your application in the cloud, for example on Windows Azure you may want to do things differently. Specifically, you probably don’t want sessions. Heck, you may not even want a relational database (not that that is always a good idea). So what choices do you have?
After some brainstorming I came up with a solution a couple projects ago that fits my needs. It uses something that gets passed around for each request and that the developers have access to – HTTP cookies! It turns out that, when your application request ends, you can write the cookies and read them when the request is made the next time.
So, let’s code up a quick proof of concept. We’ll start with a simple, empty ASP.NET MVC 3 application. For this to work, we need a way to hook-up to the request events, specifically the start and end of a request. The easiest way is the WebActivator Nuget package that’s available.
I usually create an App_Start folder, if I don’t include a dependency injection framework (I usually use Ninject and the nuget package already prepares everything for me). In that folder I add a Notifications.cs file, containing what will form the basics of our Proof Of Concept solution. Without going too into details, this is the crucial part of the code:
[csharp][assembly: WebActivator.PreApplicationStartMethod(typeof(SessionlessNotification.Web.App_Start.Notifications), "Start", Order = Int32.MaxValue)][/csharp]
This tell the WebActivator plugin to invoke Start method of my Notifications class just before application start. Now, here comes the trick. The mechanism that we used to use to do this with is called modules, and you had to, usually, configure modules in the web.config file. Well, with the WebActivator we can do this a bit differently, and more importantly, cleanly:
[csharp]
public static void Start()
{
DynamicModuleUtility.RegisterModule(typeof(NotificationModule));
}
[/csharp]
Of course, now we need to create the NotificationModule. The complete code is available at the GitHub repository. I will only highlight the key parts.
[csharp]
public void Init(HttpApplication context)
{
context.BeginRequest += new EventHandler(context_BeginRequest);
context.EndRequest += new EventHandler(context_EndRequest);
}
void context_BeginRequest(object sender, EventArgs e)
{
// try and get the cookie
var context = (HttpApplication)sender;
var notificationCookie = context.Request.Cookies.Get(CookieName);
if (notificationCookie == null)
return;
HttpContext.Current.Items[MessageFieldName] = new JavaScriptSerializer().Deserialize<List<NotificationMessage>>(HttpUtility.UrlDecode(notificationCookie.Value));
}
void context_EndRequest(object sender, EventArgs e)
{
var context = (HttpApplication)sender;
ForceCookieSet(context);
}
public static void ForceCookieSet(HttpApplication context)
{
var notificationCookie = context.Request.Cookies.Get(CookieName) ?? new HttpCookie(CookieName);
notificationCookie.Value = HttpUtility.UrlEncode(new JavaScriptSerializer().Serialize(GetNotificationMessages()));
context.Response.Cookies.Set(notificationCookie);
}
[/csharp]
Upon the module initialization (line 1), we register our handlers for begin and end requests. On request start (line 7) we read the cookie we set, and deserialize the notification messages that are located in its content. We can now fill our static stack, similar to what we had in the first example. When a request ends (line 19), we look at this stack and serialize its content back to the same cookie. This helps us persists the information throughout the requests.
Using the Razor code from the first code example, we can now see if the samples are displayed properly. If all is well, when we load the home page, we will see a notification and the cookie will be empty.
It looks like it worked! The code is available on GitHub, comments are welcome.