LINQ to Web 2.0

For social bookmarking within an enterprise to be effective, we need a new model.


December 12, 2006
URL:http://www.drdobbs.com/windows/linq-to-web-20/196603513

Kevin is a Research Developer at Liquidnet, an equities trading system, where he has been researching Web 2.0, social software, and .NET 3.0. He is also the author of several books related to the .NET Framework, including Visual C# 2005 Unleashed and SharePoint 2007 Development Unleashed. He can be contacted at [email protected].


In the broadest sense, Web 2.0 refers to the next generation of web applications. One example is social bookmarking. For most people, social bookmarking involves the use of sites such as del.icio.us and digg.com, where users find interesting content and bookmark it on the social bookmarking site of their choice. Not only can users return to those sites to see their centrally stored bookmarks, but the entire community around the site can navigate public bookmarks by short keywords or tags.

While this model works well for the Internet at large, social bookmarking at the enterprise level has an entirely different set of rules and goals. People within an enterprise use social bookmarking to organize documentation and resources to share with individuals, discrete groups, and the entire organization. That said, people won't be using del.icio.us to bookmark research for internal company projects, nor will they use it to bookmark documents created by people within the enterprise. For social bookmarking within the enterprise to be effective, a new model is required that caters to the needs and desires of enterprise social bookmarking.

When we first began evaluating the efficacy of social bookmarking within our enterprise, we started with Scuttle (sourceforge.net/projects/scuttle/), an open-source application written in PHP and running on MySQL. Scuttle shares its design with del.icio.us and digg.com and is a bookmarking platform for general public consumption. While this meets some of the needs of the enterprise, it doesn't deal with the additional security and ease of maintenance and enhancement that we wanted for our Enterprise Social Bookmarking (ESB) platform. The scope and volume of enterprise data that is of interest to public consumers can differ greatly from that of ESB platform consumers. Trying to satisfy both audiences with the same tool would fail to completely satisfy either.

Knowing that we needed something built specifically for social bookmarking within the enterprise, we decided to kick the tires on ASP.NET 2.0 to see if it could provide the base platform on which we could build our new ESB tool. In this article, I give you an inside look at the applications we produced and the insights we gleaned while creating them.

ASP.NET Controls and the Zen of CSS Design

In the early days of HTML design and web design, people thought that <table> tags were the holy grail of design because you could position anything you wanted in tabular fashion. Thankfully we learn from our mistakes, as you can see with CSS Zen Garden (www.csszengarden.com), which illustrates the use of <div> and <span> tags, and Cascading Style Sheets (CSS) to produce extremely lightweight, yet compelling and beautiful web interfaces. Additionally, designing in a CSS "Zen" fashion makes your application more pliable via client-side JavaScript and DHTML technologies, such as Ajax or Atlas.

Unfortunately, many of the controls that ship as part of the stock suite in ASP.NET 2.0 have missed the boat as far as CSS Zen design is concerned. These controls litter your output with HTML tables, rows, columns, and cells, and even embed style information directly in the tag instead of using CSS classes! To see what I mean, the hideousness in Listing One is produced by an ASP.NET 2.0 GridView using "Auto Format" (which should be renamed "Auto Munge") to create a "pretty" grid.

<table cellspacing="2" cellpadding="3" rules="all" 
  border="1" id="GridView1" 
  style="background-color:#DEBA84;border-color:#DEBA84; 
    border-width:1px; border-style:None;">
  <tr style="color:White;background-color:#A55129;font-weight:bold;"> 
     <th scope="col">title</th> 
     <th scope="col">url</th> 
     <th scope="col">tags</th></tr>
  <tr style="color:#8C4510;background-color:#FFF7E7;"> <td>This is a sample bookmark</td> <td>http://www.sample.com</td>
<td>sample test</td> 
</tr> 
</table>
Listing One

The preceding HTML output was for a GridView control that was autoformatted to "brown sugar." one of the simpler formats. There isn't a single use of a style sheet here, nor is there any straightforward way of making the elements of the GridView conform to a CSS-pure design (I've been told it's possible, but it involves far more work than should be necessary).

Would it have been so difficult for Microsoft to produce a div/span grid that corresponded to a stock suite of CSS classes and let developers override the grid's styling using CSS? I now have a requirement that I must justify why I couldn't use divs and spans before I check in an .aspx page that renders tables. ASP.NET—and specifically Web 2.0—would be a better place if everyone followed the same rule. To illustrate, take a look at the HTML in Example 1 that shows a single bookmark in a CSS-friendly fashion.

<li class="xfolkentry"> 
  <div class="link"> 
    <a href="http://www.slashdot.org">Slashdot</a> 
  </div> 
  <div class="description"></div> 
     <div class="meta"> <span class="postdate">8/28/2006</span> 
       ... more text clipped for article ... 
  </div> 
</li> 

Example 1: CSS-friendly HTML.

You can then apply any style sheet imaginable to create millions of different look-and-feels for this output. In addition to the CSS-friendly output being easier to read, more flexible, and more pliable by professional web designers, it also downloads faster and provides a page that is more lightweight than one where every aspect of each row of data's style is embedded directly on that row of data. One possible look for the preceding HTML is in Figure 1, where the preceding HTML was rendered using the Scuttle style sheet.

Figure 1: Example 1 rendered using the Scuttle stylesheet.

In general, controls that let you supply output templates can be tweaked to produce CSS-friendly output. However, controls such as DataList actually wrap individual row templates within table rows and cells—making that control completely useless. Also, watch out for the GridView control and many of the stock Web Part controls; they're heavy abusers of tables and are gathering some very bad CSS karma.

What I've found is that the time it takes to convert some of the stock controls (such as GridView) to produce CSS-friendly output is twice the time it takes to produce your own CSS-friendly output (including all grid functionality, especially if you use Atlas for inline editing!) in a Repeater.

Are Typed DataSets a Viable Data Tier?

Typed DataSets in ADO.NET 2.0 are easier to work with and much simpler to use than Typed DataSets in ADO.NET 1.0/1.1. The basic premise behind a Typed DataSet is that an XSD (XML Schema Definition) file is created that models a database schema or a subsection of the database schema. This XSD is then converted into a C# class at build time, is usable at runtime, and provides IntelliSense-aware members. In addition, we thought we might be gaining a big benefit with strongly typed members. Unfortunately, the strongly typed members are really just a facade. Under the hood, the Typed DataSet is still just a DataSet that stores everything in terms of objects. The amount of unnecessary typecasting that goes on with Typed DataSets was unacceptable.

To get data into a DataSet, you normally use a DataAdapter. In the case of a Typed DataSet, there are specialized TableAdapters that are responsible for querying the database, filling tables, and persisting changes back to the database such as inserts, updates, and deletes. In Listing Two, which populates a Typed DataSet, there is no inline clue, cue, or faint reference to what data is being populated other than the method name. Because I wrote the code, I know that GetAllBookmarksWrtUser on the BookmarksTableAdapter class actually refers to a stored procedure in my database called moniker_GetAllBookmarksWrtUser. Thankfully, I had some naming conventions that made things easy to decipher. But what if I was looking at this code and wasn't the author? Or what if I didn't have direct access to the database? I would have to spend considerable time digging through the contents of the Typed DataSet to figure out everything that is going on. I would much prefer to be able to see the query from the same perspective as the code that binds the query results to the GUI. My usual habit of right-clicking a confusing method (such as GetPopularTags) and choosing "Go to Definition" doesn't help me here. Why? Because all it does is take me to the definition in the Typed DataSet, which just creates more spaghetti code, creates a generic data adapter, and fills based on parameters. There's no useful information. I have to crack open the XSD for the Typed DataSet to find out what's really going on.

     
BookmarksDataSetTableAdapters.BookmarksTableAdapter bookTA = 
    new BookmarksDataSetTableAdapters.BookmarksTableAdapter(); 
BookmarkList1.DataSource = 
    bookTA.GetAllBookmarksWrtUser( User.Identity.Name, pageIndex, 
       numRows, ref totalBookmarks); 
TagDataSetTableAdapters.TagsTableAdapter tagTA = 
    new TagDataSetTableAdapters.TagsTableAdapter(); 
TagList1.DataSource = 
    tagTA.GetPopularTags(DateTime.Now.AddDays(-30), User.Identity.Name); 
recentTagsList.DataSource = 
    tagTA.GetRecentTags( DateTime.Now.AddDays(-30), User.Identity.Name); 
Listing Two

The code to update using Typed DataSets is straightforward. Make the changes to the object. The TableAdapter can then convert those changes into a SQL execution (inline SQL or stored procedure) and send that to the server. Typed DataSets are extremely powerful, but in any data tier I plan on using with any regularity within the enterprise, I want true and legitimate members on classes (not just typecasting wrappers!). I want model layer abstraction. I want real data types instead of typecasting, and most importantly, I dislike the fact that the information being retrieved is extremely difficult to find. You need to dig deep into the XML of the DataSet (which you can't even see in VS 2005 without explicitly requesting the XML editor) to see the queries that are being executed against the server. Even after you find the queries, they are in a syntax that is painfully difficult to read. This entire infrastructure makes difficult the task of reviewing code, analyzing queries, and even inferring what data is flowing and where it's flowing to.

Enter LINQ-to-SQL

After evaluating Typed DataSets, I decided that they are handy for quick demos of data-binding mechanics, but they don't have anything close to the set of robust features I want from a data-access tier. My data-access tier should be extensible, flexible, elegant, efficient, and create easy-to-read and maintain code. Typed DataSets meet some of those criteria, but not all. The Typed DataSet code for the original project (which we called "Moniker") was shelved and we started over with a new project: "Moniqer."

The next evolution above Typed DataSets is LINQ-to-SQL. Language Integrated Query (LINQ) is an update to C# that provides lots of new functionality. This functionality is available to you whether you use LINQ or not and includes the ability to extend existing, sealed classes with your own static language extension classes. These language extensions are the foundation on which LINQ and LINQ-to-SQL are built. Whether you're working with databases or simple arrays, LINQ will dramatically increase your productivity and reduce the complexity of the code you write. LINQ queries are written in C# or VB.NET (there is currently no LINQ support for other languages) and follow a syntax that is similar to the XQuery FLWR (For-Let-Where-Return) syntax that looks something like the following:

var query = from [variable] in [source set] 
           group [group syntax] 
           where [criteria] 
           orderby [column syntax] 
           select [variable | projection] 


The [source set] can be anything from an array or a collection to a class generated by the SQLmetal.exe command-line schema tool that represents a database or a data table fronting a back-end relational data store such as SQL Server 2005. For example, the following query returns the title and URL of every bookmark in the database, ordered by title, projected into a dynamically generated anonymous class:

var bookmarks = from bookmark in Database.Bookmarks 
               orderby bookmark.Title 
               select new { bookmark.Title, bookmark.Url }; 

The bookmarks variable won't actually contain any results at this point. The results are not retrieved until some client code attempts to traverse the results by calling GetEnumerator(), which happens automatically during a foreach loop like this:

foreach ( var bookMark in bookmarks ) 
{ 
    Console.WriteLine("<a href=\"{0}\">{1}</a>", 
    bookMark.Url, bookMark.Title); 
} 


There is a lot more power bottled up inside LINQ and LINQ-to-SQL and you should start working with this new technology as soon as you can. The other powerful feature of LINQ that I mentioned was language extensions, which give you the ability to dynamically extend any class by adding new members. Take a look at the following language extension written for a tool that consumes RSS feeds that sometimes contain missing elements:

public static class LanguageExtender 
{ 
  public static string       SafeValue(this XElement input) 
  {   
  return (input == null) ?      string.Empty : (string)input.Value; 
  } 
  public static DateTime       SafeDateValue(this XElement input) 
  { 
  return (input == null) ?       DateTime.MinValue :           DateTime.Parse(input.Value); 
  }   
} 

The preceding code actually extends the LINQ-to-XML class XElement by adding two methods—SafeValue and SafeDateValue—so that you can simply type myElement.SafeValue() and it automatically takes care of the null-value condition.

You can do even more powerful things such as retrieve a set of parent rows, then iterate through them, dynamically retrieving the child rows by traversing a foreign key relationship in an on-demand fashion:

foreach ( Bookmark b in allBookmarks) 
{ 
  Console.WriteLine(b.Title); 
  foreach (Tag t in b.Tags) 
  { 
    Console.Write("{0} ", t.Title); 
  } 
  Console.WriteLine(); 
} 

What CSS Zen can do for the elegance, flexibility, and beauty of your HTML, LINQ-to-SQL can do for your C# data access layer! I like to think of LINQ-to-SQL as "Data Zen."

LINQ-to-XML

What LINQ-to-SQL does for the world of relational database integration, LINQ-to-XML does for the world of XML data access and manipulation. Using the same LINQ-style syntax that works on databases, you can create powerful queries that parse through XML documents and retrieve data in the exact shape you like—whether that shape is in the form of dynamically generated C# classes or simply another XML schema.

LINQ-to-XML includes a new set of XML-related classes such as XDocument, XElement, and XAttribute. The beauty of these classes is that their constructors take instances of applicable child classes. This lets you create elements on-the-fly in a nested constructor format like this:

XElement newElement = 
   new XElement( "data",       new XAttribute("value", "12")); 

This creates an element that looks like:

<data value="12"/> 

This is a more intuitive and easy-to-use syntax than the existing DOM classes in the .NET Framework when you are specifically dealing with dynamic query and production of XML data. In the Moniqer project, we used LINQ-to-XML to dynamically create RSS feeds from the output of LINQ-to-SQL queries in the same line of code. When the code itself becomes a thing of beauty, you know you're on the right path. Listing Three, for instance, takes bookmarks contained in the bookmarkList parameter, which is of type IQueryable<BookmarkResults> and wraps that in a custom-built XML package that is in a format readable by most RSS readers.

Response.ContentType = "text/xml"; 
XElement rssRoot = new XElement("rss", 
   new XAttribute("version", "2.0"), 
   new XElement("channel", 
      new XElement("title", feedTitle), 
      new XElement("link", "http://[my server]/moniqer/default.aspx/"), 
      new XElement("description", feedDescription), 
      new XElement("ttl", "360"), 
      from bookMark in bookmarkList.Take(rowLimit)  
         select new XElement("item", 
            new XElement("title", bookMark.Bookmark.Title), 
            new XElement("link", bookMark.Bookmark.Address), 
            new XElement("pubDate", bookMark.Tags.Last().
                                      CreatedOn.ToString()+ " GMT"), 
            new XElement("Creator", bookMark.Tags.Last().UserID), 
            new XElement("author", bookMark.Tags.Last().UserID), 
            new XElement("description", bookMark.Bookmark.Description), 
            from tag in bookMark.Tags 
               select new XElement("category", tag.Title))); 
Response.Write(rssRoot.ToString()); 
Response.End(); 
Listing Three

The Future of ADO.NET

ADO.NET's future is tied to ADO.NET vNext, which uses LINQ to support the ADO.NET Entity Framework—an abstraction model that lets you define an entity model in an XML file. Underneath that there is a schema definition XML file that corresponds directly to the table structure in your database. There is also a mapping file that relates the Entity Model with the physical schema. This abstraction and loose coupling provides an amazing framework that supports things like Entity Inheritance and Entity Composition (creating a single C# object class/entity that reads and writes from multiple tables). From what I can tell, the ADO.NET team has done a great job of taking the best ideas that drove ObjectSpaces, WinFS, and LINQ-to-SQL and honing them into what I think is a glimpse at the future of data access for enterprise applications.

The Future of Moniqer

The current version of Moniqer resides on CodePlex (www.codeplex.com/Wiki/View .aspx?ProjectName=Moniqer). We are working on rewriting the Moniqer data tier in the Entity Framework to provide the rich scalability, flexibility, and performance for our enterprise applications. Once that is complete, we can easily add schema-changing features such as support for named groups and scoping of bookmarks, tag blending (monitoring someone else's tag within your own), discussions revolving around individual bookmarks, and more.

In short, following CSS design patterns using the ASP.NET web application framework and backing that framework with SQL Server 2005 and LINQ-to-SQL provides you with a powerful foundation on which to build enterprise social bookmarking and similar applications. If you are planning on building a data-driven application with ASP.NET, take a look at LINQ-to-SQL and ADO.NET vNext and check out the source code for Moniqer.

Terms of Service | Privacy Statement | Copyright © 2024 UBM Tech, All rights reserved.