Creating a Tag Cloud using ASP.NET MVC and the Entity Framework

4.3 (27 votes)

A Tag Cloud is a visual depiction of Tags or categories on a web site. CSS is used to set the variable font size of each Tag to illustrate its relative frequency or popularity within the site. As part of the migration of my site to ASP.NET MVC, I am introducing one to replace the Categories navigation that appears on the left hand side of each page, which contains a count of items within each category.

One of the reasons for the change is that I feel I have been mis-using "Categories". Generally, an item should really only be classified within one category, whereas applying more that one Tag to an item is de rigeur within Web 2.0 social networking and bookmarking sites. I've been "filing" articles under more than one category since I started the site, so it's about time I sorted that out.

If you haven't already seen the object model I am working to behind this site (which was featured in a previous article) here's a refresher:

I am using the ADO.NET Entity Framework as my Object Relation Mapping (ORM) tool. From the screenshot of the edmx file above, you should be able to infer that there is a one-to-many relationship between Articles and ArticleTypes (Article, Snippet, Cheat Sheet etc), but a many-to-many relationship between Articles and Categories. This means that each Article can be of one ArticleType, but it can belong to many Categories.


The actual database schema contains an Articles table, a Categories table and a bridging table to take care of the many-to-many relationship (ArticleCategories). If I was using LINQ to SQL instead of the Entity Framework, the bridging table would be exposed in the diagram, but EF works with a much higher level object model than that. As far as EF is concerned, each Article has a collection of Categories, and conversely, each Category has a collection of related Articles. The logic behind that approach, once you discover it, is undeniable. The real difference here between the designer diagrams are that LINQ to SQL is more representative of the underlying database schema, whereas EF is modelling the entities.

Back to the task at hand - a Tag Cloud, as mentioned earlier - is a visual depiction of the relative frequency that each Tag is used. Accordingly, some calulations are required to establish how many articles are tagged with a specific Category relative to the total of articles. This is done simply by working out the percentage of Articles within each Tag. Clearly, I don't want to have to manually recalculate as articles are added or deleted from the site, so the calculation has to be done dynamically. For that to work, I need the total number of articles, and the number of articles by tag. So I created a new class called MenuCategory, which extends the Category class already generated by EF, and has the following properties:

namespace MikesDotnetting.Models
  public class MenuCategory : Category
    public int CountOfCategory { get; set; }
    public int TotalArticles { get; set; }

What's needed now is a method that will create the collection of MenuCategory objects that will form the Tag Cloud:

public IEnumerable<MenuCategory> GetMenuCategories()
  var totalArticles = de.ArticleSet.Count();
  return (from c in de.CategorySet.Include("Articles")
          orderby c.CategoryName
          select new MenuCategory
            CategoryID = c.CategoryID,
            CategoryName = c.CategoryName,
            CountOfCategory = c.Articles.Count(),
            TotalArticles = totalArticles

This appears in ArticleRepository.cs, which is my main data access point for the application. You will notice one thing - the ObjectQuery<T>.Include() method. This forces related entities to be returned as part of the result from the database (Eager Loading). It's a different approach to the one used by LINQ To SQL, where you need to set the options for eager loading at the DataContext level, through the DataLoadOptions. With the Entity Framework, eager loading is defined at the query level.

So, I have my TagCloud object which will contain a collection of MenuCategory items. I have a method in the Repository that will create the collection. Now all I need is a way to get this collection to the MasterPage. Since the Tag Cloud will appear in the Master Page of the site, the data will need to be pulled every time there is a page request. So which controller should be used? The answer is one which all other controllers will inherit from. This means that whenever a Controller that inherits from BaseController is invoked, it will cause the parent Controller to instantiate, along with any properties and methods it contains. In my case, I have unimaginatively called this BaseController:

using System.Web.Mvc;
using MikesDotnetting.Models;

namespace MikesDotnetting.Controllers
  public abstract class BaseController : Controller
    private readonly IArticleRepository repository;

    protected BaseController(): this(new ArticleRepository())

    private BaseController(IArticleRepository rep)
      repository = rep;

To keep things relatively simple during thie exercise, I will be using untyped ViewData to pass the Tag Cloud data to the MasterPage. So, within the BaseController constructor, I add the following:

ViewData["TagCloud"] = repository.GetCategoriesForTagCloud();

And that's all it takes to add the data to the ViewDataDictionery. All I need now is to vary the font size of each Category within the MasterPage. For that, I need a little helper class and some CSS. the helper class takes two ints representing the number of articles in a category, and the total number of articles. It returns a string based on what the first number represents as a percentage of the second:

public static string GetTagClass(int category, int articles)
  var result = (category * 100) / articles;
  if (result <= 1)
    return "tag1";
  if (result <= 4)
    return "tag2";
  if (result <= 8)
    return "tag3";
  if (result <= 12)
    return "tag4";
  if (result <= 18)
    return "tag5";
  if (result <= 30)
    return "tag6";
  return result <= 50 ? "tag7" : "";

The strings refer to classes within the CSS file, where the size of the font progressively increases:

  Tag Cloud

.tag1{font-size: 0.8 em}
.tag2{font-size: 0.9em}
.tag3{font-size: 1em}
.tag4{font-size: 1.2em}
.tag5{font-size: 1.4em}
.tag6{font-size: 1.7em}
.tag7{font-size: 2.0em}

Within the MasterPage, each category is assessed using the GetTagClass method, and the css style applied to it dynamically:


<div id="tags">
    foreach (var t in (IEnumerable<MenuCategory>) ViewData["TagCloud"])
                  controller = "Category",
                  action = "Listing",
                  id = t.CategoryID
                 new { @class = Utils.GetTagClass(t.CountOfCategory,t.TotalArticles)}
    <% }%>

Date Posted:
Last Updated:
Posted by:
Total Views to date: 48173


- Kelvin

Your articles are always clearly written and easy to follow. Keep up the good work!

- Adam

Great article!

"If you haven't already seen the object model I am working to behind this site (which was featured in a previous article) here's a refresher:"

Could you provide a link to this article?

Also, I would like to follow your articles - is there an RSS feed available?


- Mike


I've linked to the previous article now. If you check the third paragraph, you should find it. Alternatively, click the ASP.NET MVC link in the Categories menu and you will find an article on PartialViews. But the image is the same one used in that article.

There's also an RSS feed link (the orange square) in the left hand column under the MVP logo.

- Adam

Got it - thanks!

- Tim

Exelent article and I'm glad to see it using EF it's something I've been wanting to start using.

But the down side is that I've been working on the exact same idea for a submission to the ASP.NET Daily Articles. Guess I better come up with another idea.

- Mike


Oops. Sorry ;o)

- jeeva

What is MVC? How to use MVC.Please explain clearly thank ypu.

- Mike


Use a search engine. Or buy a book.

- Bob

I have been enjoying reading these articles. Just wondering about caching the results? With this being your base controller each time the site page is refreshed it will hit the database for the article counts? The counts would only change as articles are submitted or removed. Would this be a candidate for caching the result somewhere? Is this something you plan to cover in a later article?

- Mike


Yes, it would be a candidate for caching by all that's right. The data changes just 3 or 4 times a month maximum. I haven't bothered implementing caching for 2 reasons - first I don't want to confuse the main thrust of the article with additional concepts outside of the main ones to get the tag cloud to work, and secondly, I haven't implemented caching at all in my site. No need - it's one of only 5 sites living on a massive Quad Core box :o) The odd visit that this site gets is just about all that SQL Server gets to remind itself that it exists....

But I may well look at caching in another article (once I've eventually found the time to finish the MVC migration...)

- Jayaram Krshnaswamy

Excellent and well written.

- totalNewbie

Thank you very much for a clear article.

your navigational property should be called ArticleType(S) missing the S...

there is an error in numbering (6..7) in GetTagClass, and I would use switch instead of ifs...

question, isn't there an easier way to count how many times particular category name is present in the Category table? (without the need for the eager load another table)

storing values somewhere (collumn)?

- johnny

Very nice article. I am wondering is there any link for the sample code?

- Ravinder

This Article make my job vary easy.

thanks Warm Regards

- outletGucci

I lilke your article,it is very clear for me and help me solve many problems,thank you very much.

- Atul Sharma

Hi, Thanks for giving understanding on tag cloud, can you also please help in sharing link of downloadable example sample application?

Thanks in Advance.

Recent Comments

Thomas 05/03/2018 00:59
In response to I'm Not Writing A Book On Razor Pages
There's a typo on this page: = true)] should be [BindProperty(SupportsGet = true)]...

Rolf Herbert 04/03/2018 19:25
In response to I'm Not Writing A Book On Razor Pages
So is MS deprecating razor Web Pages..? Is it dead..? I wish they would stop killing things so its...

Borut 17/02/2018 12:59
In response to I'm Not Writing A Book On Razor Pages
Mike, is it possible that Web Pages and Razor Pages "live" together in one web application? I a I...

hrboyce 09/02/2018 04:44
In response to I'm Not Writing A Book On Razor Pages
Mike, First thanks for doing this but I have to ask, any chance you would consider converting one of...

aziz sallam 07/02/2018 10:18
In response to I'm Not Writing A Book On Razor Pages
u are a great man...

Satyabrata Mohapatra 31/01/2018 11:36
In response to I'm Not Writing A Book On Razor Pages
This is a great news!!!! Thanks...

tangdf 30/01/2018 07:25
In response to I'm Not Writing A Book On Razor Pages
=> { o.ConfigureFilter(new IgnoreAntiforgeryTokenAttribute()); }); The extension method does...

Obinna Okafor 30/01/2018 04:02
In response to I'm Not Writing A Book On Razor Pages
Thank you very much. I would like to see a project built from scratch using Razor Pages. And it show...

rachida Dukes 31/10/2017 13:52
In response to Customising Identity in Razor Pages
Thanks again for this wonderful tutorial. I followed all the steps in this section called: Adding...

Rachida 31/10/2017 12:06
In response to Customising Identity in Razor Pages
Thanks very much for this wonderful tutorial, it helped a lot....