ASP.NET MVC: Enhancing The WebGrid - Lazy Loading with SignalR

In this fifth post about how to enhance your WebGrid, we talk about using SignalR to lazy load records dynamically.

Written by Jonathan "JD" Danylko • Last Updated: • MVC •
Client/Server Communication

After the last post about lazy loading records using WebAPI into a WebGrid and an earlier post about how to build your own real-time Like button, I thought this would be an excellent opportunity to use SignalR yet again to maximize our WebGrid (and it gives me a chance to play around with SignalR again!)

The last post used WebAPI for our communication to the server. Today, we'll tighten up that gap with a direct connection using SignalR.

SignalR is fantastic when it comes to real-time Internet communication. I've said in the past when AJAX was first introduced that AJAX was a game-changer and that it blurred the lines between desktop applications and websites.

Well, SignalR closes that gap even further for websites. SignalR makes it even easier to create two-way communication between the client and server using JavaScript and Web Sockets.

Once you finish this post, you'll have a good understanding of how SignalR is quite an awesome and easy .NET technology to implement.

This may be the smallest and quickest post since we did most of the JavaScript and HTML work in the last post.

Installation

So let's get started by replacing WebAPI with SignalR.

First, we need to install SignalR using the Package Manager Console (View / Other Windows / Package Manager Console)

install-package Microsoft.AspNet.SignalR

When you finish your installation of SignalR, you immediately see a Read.me on the screen reminding you to create a Startup.cs file that kicks off when your webapp initially runs.

Here is what the Startup.cs file looks like:

using Owin;
namespace WebGridExample
{
    public class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            app.MapSignalR();
        }
    }
}

Pretty darn simple. I placed Startup.cs in the root folder of my web application.

Script Placement

We also need to include two new scripts in our User/Index.cshtml View.

Place these two scripts directly after the bootstrap.js file at the bottom so you have the following (bold are the changes):

<script src="~/Scripts/jquery-2.1.3.min.js"></script>
<script src="~/Scripts/bootstrap.min.js"></script>
<script src="~/Scripts/jquery.signalR-2.2.0.min.js"></script>
<script src="/signalr/hubs"></script>

The SignalR/Hubs script is something that most people forget about including in their script list. This is a dynamic script that reads your hub and generates a JavaScript file based on your C# hub class.

Pretty mind-blowing! Wait until we're done building our Hub class and I'll show you what I mean.

We'll come back to this Index page and finish up our JavaScript after we complete our Hub class.

Hub-ba, Hub-ba!

Hubs are required for SignalR to communicate with the client and server and they are a core component for SignalR apps.

Our WebGridHub inherits from the Hub class and, as you can see, uses an asynchronous Task type.

Hubs\WebGridHub.cs

using System.Threading.Tasks;
using Microsoft.AspNet.SignalR;
using Microsoft.AspNet.SignalR.Hubs;
using WebGridExample.Repository;
namespace WebGridExample.Hubs
{
    [HubName("webGridHub")]
    public class WebGridHub : Hub
    {
        private readonly UserRepository _repository;
        public WebGridHub() : this(new UserRepository()) { }
        public WebGridHub(UserRepository repository)
        {
            _repository = repository;
        }
        public Task GetPagedUsers(int page, int pageSize)
        {
            var records = _repository.GetPagedUsers(page, pageSize);
            return Clients.Caller.populateGrid(records);
        }
    }
}

Even though it's not necessary, I like to include the HubName attribute on my Hub classes so that I don't have a problem with case-sensitive issues on the JavaScript side.

When this Hub initializes, we setup a UserRepository to pull our records similar to how we pulled them in the Web API example.

The only method we have in this class is our GetPagedUsers that does exactly what we expect based on the Web API code.

However, the last line in the method is rather strange. We are telling SignalR to return this result to just the caller. There are other properties such as:

  • Clients.Caller - Send the results only to the client that made that call.
  • Clients.All - Send the response to all clients that are connected, so everyone will see the results on their screen.
  • Clients.Others - Send a response to everyone except the caller.
  • Clients.OthersInGroup - Send the results to others in a group.
  • Clients.OthersInGroups - Send the results to others in multiple groups.

The populateGrid that is called is a JavaScript function on the client-side.

Yeah, I said client-side, which we'll look at now.

Setting Up The Hub Client

Let's head back to the User\index.cshtml View that has the JavaScript at the bottom.

The "moreButton" click simply needs replaced with our SignalR JavaScript code.

Since this code was Web API based, we can comment out this JavaScript code:

$("#moreButton").click(function () {
    var pageSize = 5;
    var getNextPageNo = ($("#grid tbody tr").size() / pageSize) + 1;
    var row = "<tr><td class=\"text-center checkbox-width\"></td><td></td><td></td><td></td><td></td></tr>";
    $.getJSON('/api/User/GetPaging?page=' + getNextPageNo + "&pageSize=" + pageSize, function (users) {
        if (users.length > 0) {
            $.each(users, function (index, user) {
                $('#grid tbody').append($(row));
                var bottomRow = $("#grid tbody tr:last");
                $("td:nth-child(1)", bottomRow).html("<input id=\"select\" class=\"box\" name=\"select\" " +
                    "type=\"checkbox\" value=\"" + user.Id + "\">");
                $("td:nth-child(2)", bottomRow).html(user.UserName);
                $("td:nth-child(3)", bottomRow).html(user.FirstName);
                $("td:nth-child(4)", bottomRow).html(user.LastName);
                var formattedDate = formatDate(new Date(user.LastLogin));
                $("td:nth-child(5)", bottomRow).html(formattedDate);
            });
        } else {
            $(".more-button").html("<div class=\"alert alert-info\">No more records.</div>");
        }
    });
});

and replace it with this code:

var webGridHubClient = $.connection.webGridHub;
webGridHubClient.client.populateGrid = function (users) {
    if (users.length > 0) {
        var row = "<tr><td class=\"text-center checkbox-width\"></td><td></td><td></td><td></td><td></td></tr>";
        $.each(users, function (index, user) {
            $('#grid tbody').append($(row));
            var bottomRow = $("#grid tbody tr:last");
            $("td:nth-child(1)", bottomRow).html("<input id=\"select\" class=\"box\" name=\"select\" " +
                "type=\"checkbox\" value=\"" + user.Id + "\">");
            $("td:nth-child(2)", bottomRow).html(user.UserName);
            $("td:nth-child(3)", bottomRow).html(user.FirstName);
            $("td:nth-child(4)", bottomRow).html(user.LastName);
            var formattedDate = formatDate(new Date(user.LastLogin));
            $("td:nth-child(5)", bottomRow).html(formattedDate);
        });
    } else {
        $(".more-button").html("<div class=\"alert alert-info\">No more records.</div>");
    }
};
$("#moreButton").on("click"function () {
    var pageSize = 5;
    var getNextPageNo = ($("#grid tbody tr").size() / pageSize) + 1;
    webGridHubClient.server.getPagedUsers(getNextPageNo, pageSize);
});
 
$.connection.hub.start();

Starting at the top, the first thing we need to do is setup our connection to our C# Hub class (webGridHub) using the connection object. This is the attribute on your C# Hub Class.

Next is the "method" that we are defining in our client that the server will call. Always use hubName.client and then your method signature.

The funny thing about the populateGrid method is that it's the exact same code as the Web API JavaScript code. The only difference is that we are replacing it with SignalR syntax.

Now, at the bottom, you may be asking, "how do we contact the server to get our records?" How do we do that?

By issuing a hubName.server.methodName with your parameters. Now, since this is JavaScript syntax, the first letter of your methods in your C# Hub class will be lower-case when you call them from the client.

This is how the chain of code gets executed:

  1. The script is loaded and executed and the connection.hub.start() establishes a connection to the server.
  2. When the user clicks the "Load More" button, a call is made to the server to "GetPagedUsers()"
  3. On the server-side, the GetPagedUsers loads the records based on the parameters passed in by the client.
  4. The GetPagedUsers method takes the records returned from the repository and sends them back to the client by calling the client's populateGrid method.
  5. The populateGrid method takes the records and using JavaScript, populates the WebGrid.

When you run your web application, it's the same effect as the Web API, but it's a tad bit faster because a connection to the server is already made instead of the latency of a web service call.

The Surprise

Remember when I said that the SignalR/Hubs script is dynamic?

Type that into your browser (http://localhost:xxxx/signalr/hubs/) to see the script that is dynamically generated. If you look through the script, you'll find your GetPagedUsers method in the JavaScript code.

Scary, I know (but ohhh so cool) ;-)

Conclusion

Today, we used a second alternative technology (first, WebAPI, second, SignalR) on your existing WebGrid to give your users an even bigger "Wow" factor: Your records returned even faster to the WebGrid.

With the latest technologies of WebAPI and SignalR, you can now perform a number of tasks in your WebGrid that would make your users believe that this wasn't possible to do in a web application.

For example, it is now extremely easy to create a CRUD (Create, Retrieve, Update, Delete) WebGrid, a hierarchical tree grid, or even a master-detail view within a Web Grid.

I might also add that both technologies work with mobile devices as well.

I hope this WebGrid series has opened your eyes to the possibilities your can provide to your users to empower them to manipulate and view their grid data.

The source code is available at GitHub: https://github.com/jdanylko/WebGridExample

Looking for a new way to do something in a WebGrid? Post your comments below!

Series: Enhancing the WebGrid

ASP.NET 8 Best Practices on Amazon

ASP.NET 8 Best Practices by Jonathan Danylko


Reviewed as a "comprehensive guide" and a "roadmap to excellence" with over 120 Best Practices for ASP.NET Core 8, Jonathan's first book by Packt Publishing explores proven techniques for every phase of the SDLC.

Learn industry-standard concepts to improve your coding, debugging, and deployment of ASP.NET Core websites.

Order now on Amazon.com button

Picture of Jonathan "JD" Danylko

Jonathan "JD" Danylko is an author, web architect, and entrepreneur who's been programming for over 30 years. He's developed websites for small, medium, and Fortune 500 companies since 1996.

He currently works at Insight Enterprises as an Architect.

When asked what he likes to do in his spare time, he replies, "I like to write and I like to code. I also like to write about code."

comments powered by Disqus