In this post, I want to demonstrate how you can add real-time interactivity to a website that is written with ASP.NET WebForms. I'm going to show you how easily and quickly you can have SignalR update the content of a standard ListView
webcontrol to show a 'live' log of events on your webserver.
Project Setup
For this sample, I'm going to start up a standard ASP.NET WebForms application with .NET 4.5 Once the project is created, I'm going to clear out the default content on the home page and insert my ListView
control as shown in Code Listing 1:
<asp:content runat="server"
id="BodyContent" contentplaceholderid="MainContent">
<h3>Log Items</h3>
<asp:listview id="logListView" runat="server"
itemplaceholderid="itemPlaceHolder"
clientidmode="Static" enableviewstate="false">
<layouttemplate>
<ul id="logUl">
<li runat="server"
id="itemPlaceHolder"></li>
</ul>
</layouttemplate>
<itemtemplate>
<li><span class="logItem">
<%#Container.DataItem.ToString() %></span></li>
</itemtemplate>
</asp:listview>
</asp:content>
Code Listing 1 - Initial layout of the ListView
With this block configured, I can write a small snippet in the code-behind default.aspx.cs file to load some log information from some other datastore. For this example, I'm just going to bind to a List
of string
s to demonstrate the mix of server rendered content and dynamically added content:
protected void Page_Load(object sender, EventArgs e)
{
var myLog = new List<string>();
myLog.Add(string.Format("{0} - Logging Started", DateTime.UtcNow));
logListView.DataSource = myLog;
logListView.DataBind();
}
Code Listing 2 - Code behind to load some rendered data
I should now be able to start my application and see a simple pair of lines added to the default page that appear something like this:
Figure 1 - Static Log Content
Adding SignalR to the Mix
At this point, we need to add SignalR to our project through the NuGet package manager. I prefer to use the 'Manage NuGet Packages' dialog window to add these packages, so I add the packages for SignalR by searching for and adding the "Microsoft ASP.NET SignalR JS" package and the "Microsoft ASP.NET SignalR SystemWeb" packages. At the time of this article's writing, these packages are available in the 'Pre-Release' state only.
When you craft code for SignalR, you need to write client-side and server-side code. The server-side code in this sample will be housed in a SignalR Hub.
A hub is a structure that facilitates simple communications to a collection of client systems that are listening for commands to execute.
In this project, we will create a LogHub
class in C# that will allow log messages to be communicated to all listening client browsers. To simulate the repeated creation of log messages, I will use a timer to periodically transmit messages. The code for the LogHub.cs file appears below:
public class LogHub : Hub
{
public static readonly System.Timers.Timer _Timer = new System.Timers.Timer();
static LogHub()
{
_Timer.Interval = 2000;
_Timer.Elapsed += TimerElapsed;
_Timer.Start();
}
static void TimerElapsed(object sender, System.Timers.ElapsedEventArgs e)
{
var hub = GlobalHost.ConnectionManager.GetHubContext("LogHub");
hub.Clients.All.logMessage(string.Format("{0} - Still running", DateTime.UtcNow));
}
}
Code Listing 3 - LogHub source code
This hub contains 1 static
event handler to listen for the timer's elapsed event. The first statement of this method gets a reference to the singleton LogHub
that is running in our web server. The next statement issues a message to all clients using the "hub.Clients.All
" structure. This is an interesting piece of code, as the "All
" property of "hub.Clients
" is a dynamic object. The "All
" property is a proxy for the objects and their methods that are available to be called over the SignalR pipeline.
The object that is called over the SignalR pipeline by "All
" from Code Listing 3, will be constructed in JavaScript and exposed to our hub in the following script
block:
<script src="Scripts/jquery.signalR-1.0.0-rc1.min.js"></script>
<script src="http://www.codeproject.com/signalr/hubs"
type="text/javascript"></script>
<script type="text/javascript">
$(function() {
var logger = $.connection.logHub;
logger.client.logMessage = function(msg) {
$("#logUl").append("<li>" + msg + "</li>");
};
$.connection.hub.start();
});
</script>
Code Listing 4 - JavaScript to log messages
There are a few pre-requisites here that I need to discuss first. In my sample project, there is a reference to the jQuery library in my layout page. If you do not have that reference available, I recommend you add it to simplify the volume of JavaScript code you will be writing. Next, I have added script
references to the "jquery.signalR
" library to give us access to the SignalR
functionality. The second reference is to a "magic URL" at /signalr/hubs. This is a virtual location that exposes the methods that the hub
classes have in public
scope. These methods can be called from the browser, through the plumbing established in this file.
The JavaScript in this listing is a normal jQuery enclosure, to ensure its contents do not get executed until after jQuery is loaded. The first line gets a reference to the LogHub
object that we created in listing 3. We then connect a function to our client's "logMessage
" property. This is the same function that is referenced in listing 3's dynamic "All
" object. Thankfully, since we marked our ListView
object with a static ClientIDMode
and disabled ViewState
, there are no hoops for us to go through to get a reference to the DOM objects that were created. In this case, we're simply going to append a list item (LI
) to the unordered list (UL
) with the message submitted to the function. The last line of this enclosure is very important. The start()
method must always be called in order for SignalR to know to start listening for invocations from the server.
Before we can run our sample, we need to add one last piece of plumbing. We need to tell the server to expose that magic URL at "/signalr/hubs". This is accomplished by adding a line to the Application_Start
event handler in global.asax.cs:
void Application_Start(object sender, EventArgs e)
{
BundleConfig.RegisterBundles(BundleTable.Bundles);
AuthConfig.RegisterOpenAuth();
RouteTable.Routes.MapHubs("~/signalr");
}
Code Listing 5 - Mapping the HubsRoute
Once this line is added, we can start our application. You should see a result screen similar to the following:
Figure 2 - Static and Live Log Content
Summary
With a few simple settings on our WebForms project and the controls I wanted to modify for this sample, I was able to dramatically simplify the SignalR interactions with WebForms. In this case, I didn't run into any issues with the parts of WebForms that developers try to avoid like PostBack, Page Lifecycle, ViewState, and ClientID rendering. Next time, we'll confront those issues head on as I'll show you how to interact between SignalR and controls that post back to the server.