I read this post by Mikesdotnetting a few days ago when I was trying to implement a Tag Cloud for our open source forum project SubForum. Although it was a good post and got me started on how to do it, I found it a bit complex for what I needed. One of the complications that I saw was obviously the Entity Framework and the data layer that Mike talked about to build the Tag Cloud. The data I needed was quite simple and didn’t need to be that complex.
I decided to implement a simple ASP.NET MVC Tag Cloud using plain SQL and a simple table structure. Also, I did not want categories. I just wanted posts and post tags, so that my Tag Cloud could more accurately represent the Tags in the posts.
RenderAction Approach
I decided to use the RenderAction approach introduced in the MVC Futures library. This helped me strongly type the Tag Cloud partial view to a Model and a specific action in the Controller that populates the model. This is helpful because the Model for the main page is different from the Model for the TagCloud and I wanted to avoid adding the Tag Cloud model to the ViewData collection as a dictionary value. You can read Jimmy Bogard’s post for more about the benefits of RenderAction.
The Steps
Back to the Tag Cloud. Let me start with the Database model:
The Post and Tag object are used to represent a Post and a List of Tags:
The way it works is that every time a post is added, the comma separated tags associated with it are taken and inserted as individual tags in the PostTags table.
We need to pull all the information from the database to perform our Tag Cloud calculations. We need the number of times each tag occurs in the system, the Tag itself and the total number of posts in the system. We can pull all this information in one simple query:
Select Count(1) As Count, PostTags.Tag As Tag, (Select Count(*)
From Posts) As TotalPosts
From PostTags
Group By PostTags.Tag
Order By 'Count' Desc
The result of executing this query against our test database returns:
As you can see, this shows the tag, the number of times it occurs in the system and the total number of posts in the system. We can then return this data through the TagCount object (which has just three properties: Count, Tag and TotalPosts) from our Data Layer function GetTagCloud:
public IList<SubForum.DataAccess.Objects.TagCount> GetTagCloud()
{
System.Data.IDbCommand command = null;
IDataReader dataReader = null;
List<SubForum.DataAccess.Objects.TagCount> returnList = new List<SubForum.DataAccess.Objects.TagCount>();
IDbConnection connection = new System.Data.SqlClient.SqlConnection(this.ConnectionString);
try
{
connection.Open();
command = connection.CreateCommand();
command.CommandText = "Select Count(1) As Count, PostTags.Tag As Tag, (select count(*) from Posts) as To" +
"talPosts\nFrom PostTags\nGroup By PostTags.Tag\nOrder By \'Count\' Desc";
System.Console.WriteLine("Executing Query: {0}", command.CommandText);
dataReader = command.ExecuteReader();
for (
; dataReader.Read();
)
{
SubForum.DataAccess.Objects.TagCount modelObj = new SubForum.DataAccess.Objects.TagCount();
modelObj.Tag = ((String)(dataReader["Tag"]));
if ((dataReader["Count"].Equals(DBNull.Value) == false))
{
modelObj.Count = ((int)(dataReader["Count"]));
}
else
{
modelObj.Count = null;
}
if ((dataReader["TotalPosts"].Equals(DBNull.Value) == false))
{
modelObj.TotalPosts = ((int)(dataReader["TotalPosts"]));
}
else
{
modelObj.TotalPosts = null;
}
returnList.Add(modelObj);
}
}
finally
{
if ((dataReader != null))
{
((System.IDisposable)(dataReader)).Dispose();
}
if ((command != null))
{
((System.IDisposable)(command)).Dispose();
}
if ((connection != null))
{
connection.Close();
((System.IDisposable)(connection)).Dispose();
}
}
return returnList;
\
The ForumController that handles all the Forum MVC requests has a GetTagCloud() function that returns the model for the View:
public ActionResult TagCloud()
{
IList<TagCount> tagCountList = DataAccessService.GetTagCloud();
return View(tagCountList);
\
Then comes the user control or the MVC Partial View that displays the tags as links with the right Tag class to use. TagCloud.ascx:
<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<IList<SubForum.DataAccess.Objects.TagCount>>" %>
<h5>Tags</h5>
<ul>
<%
int totalNumberOfTags = base.Model.Count;
foreach (SubForum.DataAccess.Objects.TagCount tagCount in base.Model)
{
if (!string.IsNullOrEmpty(tagCount.Tag))
{
string tagClass = SubForum.Web.Controllers.ForumController.GetTagClass(tagCount.Count.Value, tagCount.TotalPosts.Value);
%> <li><%=Html.RouteLink(
tagCount.Tag,
"Tag",
new
{
controller = "Forum",
action = "Tag",
id = tagCount.Tag
},
new { id = tagClass }
)%></li>
<%}
}%></ul>
The UI calls the GetTagClass() function to determine which TagClass from the CSS to use:
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 "tag5";
return result <= 50 ? "tag6" : "";
}
These Tag classes match our CSS that has the following definition:
.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}
Finally, in our Master page we will call the controller to Render the Partial View TagCloud.ascx and shows us all the tags in the system appropriately weighted:
<div id="tags">
<% Html.RenderAction<SubForum.Web.Controllers.ForumController>(c => c.TagCloud());%>
</div>
This yields a plain and simple TagCloud looking as such, with the most popular showing first :
Extension
The Tag Cloud can be extended by limiting the Tags to a predefined set of tags instead of making it free for all. That can simply be done by adding a Tags table which holds all the system’s predefined Tags and only allowing users to pick from those tags. The overall working of this code and the SQL would not be affected at all.
You can also return the top 10 or however many tags that you want to be displayed instead of displaying all the tags by simply adding a Top clause to your query:
Select Top 10 Count(1) As Count, PostTags.Tag As Tag, (Select Count(*)
From Posts) As TotalPosts
From PostTags
Group By PostTags.Tag
Order By 'Count' Desc
Conclusion
With this approach all the code from the UI layer including the CSS down to the Data Layer is shown. The approach is universal to any database engine or provider you may want to use and there is nothing hidden except the workings of the MVC Framework. You can see a live demo (if it is up :) ) at www.PortaltoolBox.net or checkout the actual source on www.subforum.net.
whatever happened to subforum? all links don't work.. vapourware ?
ReplyDeleteCó làn da trắng là mong ước của mọi cô gái , sản phẩm thuốc ivory caps giúp trắng da toàn thân ngoài ra bạn cũng có thể sử dụng các loại kem chống nắng. Ngoài ra nếu bạn muốn làn da luôn tươi trẻ thì nên dùng my pham sakura như kem chong lao hoa sakura giúp làn da luôn trẻ đẹp xóa các nếp nhăn. Cách thuoc herba vixmen an toàn và hiệu quả bằng herba vixmen , vậy thuoc herba vixmen co tot khong , có an toàn không và mua ở đâu sẽ được cho biết sau đây. Sản phẩm giúp bà bầu và thai nhi như dinh dưỡng dành cho bà bầu sẽ bổ sung chất dinh dưỡng cho cả bà bầu và thai nhi. Nếu bạn bị thâm nách nên dùng thâm nách under arm hiệu quả và an toàn.
ReplyDeleteTechnoSoftwar dedicated and experienced team of Dot Net developers works on various projects and deliver outstanding results to their clients.
ReplyDeleteTechnoSoftwar web design and development, Mobile application development and Digital Marketing simultaneously works on Asp.net framework and MVC also.
Thanks for sharing the best information and suggestions, it is very nice and very useful to us. I appreciate the work that you have shared in this post. Keep sharing these types of articles here. cloud training in bangalore
ReplyDeleteo220n9vfowp220 fake bags f653x4murrt503
ReplyDeletemersin
ReplyDeletenevşehir
uşak
ataşehir
küçükçekmece
LQWGH
trabzon
ReplyDeleteçorum
sinop
yozgat
afyon
APS4R5