by Keyvan Nayyeri via Keyvan Nayyeri on 1/26/2009 11:49:25 AM
The era of Web 3.0 has begun and there is a fast transition to Web 3.0 styles and definitions. The recent economy crisis can slow the progress down to a reasonable extent because the nature of Web 3.0 requires good investments by bigger companies in creation of Artificial Intelligence methods that classify the content automatically.
However, there is still a good progress in this field, and Web 3.0 is going to become the comprehensive style of websites on the web. As you should know, a major part of the Web 3.0 is the Semantic Web which is mainly defined by a set of web standards that organize and classify the content of a website, the real or virtual identity behind the site, and the relationships between the site or its identity with external references.
It’s been over a year that I was interested to spend some time on expanding my blog to be a Web 3.0 friendly blog. Unfortunately my military service and post-service problems didn’t leave much time for me to develop such features, but I’m finally working on this goal. Probably you have noticed that I haven’t done much fun coding in the past few months and it’s been annoying for me, so this may be a good start point to restore things to the normal form.
Yesterday I spent some free time on implementing one of the famous and common Web 3.0 standards for Graffiti which is the Friend of a Friend (FOAF) format.
Nowadays there aren’t many blog engines (even modern blog engines) that support Web 3.0 standards very well. Likewise, .NET blog engines lack such features except BlogEngine.NET which has major goals to target Web 3.0. Mads and his team have done a great job in supporting many of the Web 3.0 standards out of the box. Some months ago he started an open source project called SemanticEngine.NET which is actually a C# library appropriate for those who want to generate or parse Web 3.0 standards, however, for some standards it doesn’t support both functionalities.
By the way, Graffiti is supposed to act as a general CMS, so it’s obvious that FOAF is appropriate for those bloggers who have applied it as a blog engine. FOAF is not something that can be included in the product, but I hope that Telligent Graffiti team put an effort into Web 3.0 area and support some other formats out of the box.
Some readers may know about FOAF but for those who don’t know, in essence I can say that it’s an XML derivation as well as an RDF derivation that provides information about online activity of people, and describes the relationship between them. Following quote from FOAF website can clarify this.
FOAF is about your place in the Web, and the Web's place in our world. FOAF is a simple technology that makes it easier to share and use information about people and their activities (eg. photos, calendars, weblogs), to transfer information between Web sites, and to automatically extend, merge and re-use it online. The Friend of a Friend (FOAF) project is creating a Web of machine-readable pages describing people, the links between them and the things they create and do.
FOAF is about your place in the Web, and the Web's place in our world. FOAF is a simple technology that makes it easier to share and use information about people and their activities (eg. photos, calendars, weblogs), to transfer information between Web sites, and to automatically extend, merge and re-use it online.
The Friend of a Friend (FOAF) project is creating a Web of machine-readable pages describing people, the links between them and the things they create and do.
To make it straight, FOAF is an XML format that represents two things:
FOAF can be a very comprehensive format with a recursive structure that may become very large to cover all the information of a person and his friends, but FOAF has many optional parameters that let you omit many information and keep what you desire. Typically, people try to store a set of their Web 2.0 social networking account URLs along references to their friend’s URLs or social networking accounts.
In my opinion current specification of FOAF requires a simpler structure with a more common style that everyone follows. One of the negative points about current FOAF is numerous derivations and applications for the specification by different people that make it difficult to deal with them.
The implementation of FOAF plug-in for Graffiti CMS has a few steps. In fact this implementation consists of two parts. In one part I need to generate the FOAF file based on user’s input, and in another part I have to add a meta tag to Graffiti pages that has a reference to FOAF file.
To accomplish these goals, I implement a single plug-in that handles multiple events: AfterCommit and RenderHtmlHeader. Using the former event, I generate my FOAF file on the server, and with the latter one I dynamically add a meta tag information to Graffiti pages when a request is received.
While this implementation is for Graffiti CMS, the nature of structured programming and its modular separation allows you to inspire the main part of the code and use it in your own application easily.
By the way, the implementation is a little lengthier than the other plug-ins that I have shared here but it mainly contains repetitive code sections.
The plug-in implementation begins with the definition of several fields that keep data for various purposes. The variable names are self-explanatory, so I neglect their details.
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Text;
using System.Text.RegularExpressions;
using System.Web;
using System.Xml;
using Graffiti.Core;
namespace FoafPlugin
{
public class FoafGenerator : GraffitiEvent
#region Fields
public string FULLNAME;
public string TITLE;
public string NICKNAME;
public string FLICKR;
public string TWITTER;
public string DELICIOUS;
public string FRIENDFEED;
public string FACEBOOK;
public string LINKEDIN;
public string FRIENDSLIST;
public string FILEPATH;
#endregion
}
In the next step I override the implementation for some EdtiableForm properties and methods. Knowing the details about Graffiti plug-in deployment, there is nothing special about this code. It’s also good to emphasize on AddFormElements where I have used a ListFormElement in conjunction with its child ListItemFormElement items to render a list of possible title values.
#region EditableForm members
public override string Name
get
return "The Friend of a Friend (FOAF) Generator";
public override string Description
return "The Friend of a Friend (FOAF) generator plug-in for Graffiti " +
"by <a href=\"http://nayyeri.net\">Keyvan Nayyeri</a>.";
public override bool IsEditable
return true;
protected override NameValueCollection DataAsNameValueCollection()
NameValueCollection nvc = base.DataAsNameValueCollection();
nvc["FULLNAME"] = FULLNAME;
nvc["TITLE"] = TITLE;
nvc["NICKNAME"] = NICKNAME;
nvc["FLICKR"] = FLICKR;
nvc["TWITTER"] = TWITTER;
nvc["DELICIOUS"] = DELICIOUS;
nvc["FRIENDFEED"] = FRIENDFEED;
nvc["FACEBOOK"] = FACEBOOK;
nvc["LINKEDIN"] = LINKEDIN;
nvc["FRIENDSLIST"] = FRIENDSLIST;
nvc["FILEPATH"] = FILEPATH;
return nvc;
public override StatusType SetValues(HttpContext context, NameValueCollection nvc)
base.SetValues(context, nvc);
FULLNAME = nvc["FULLNAME"];
TITLE = nvc["TITLE"];
NICKNAME = nvc["NICKNAME"];
FLICKR = nvc["FLICKR"];
TWITTER = nvc["TWITTER"];
DELICIOUS = nvc["DELICIOUS"];
FRIENDFEED = nvc["FRIENDFEED"];
FACEBOOK = nvc["FACEBOOK"];
LINKEDIN = nvc["LINKEDIN"];
FRIENDSLIST = nvc["FRIENDSLIST"];
FILEPATH = nvc["FILEPATH"];
return StatusType.Success;
protected override FormElementCollection AddFormElements()
FormElementCollection fec = new FormElementCollection();
fec.Add(new TextFormElement("FULLNAME", "Full Name", "Your full name."));
ListFormElement list = new ListFormElement("TITLE", "Title", "Your title.");
list.Add(new ListItemFormElement("Mr", "Mr"));
list.Add(new ListItemFormElement("Mrs", "Mrs"));
list.Add(new ListItemFormElement("Ms", "Ms"));
list.Add(new ListItemFormElement("Sir", "Sir"));
list.Add(new ListItemFormElement("Dr", "Dr"));
fec.Add(list);
fec.Add(new TextFormElement("NICKNAME", "Nickname", "Your nickname."));
fec.Add(new TextFormElement("FLICKR", "Flickr", "Your Flickr name (as appears in the URL)."));
fec.Add(new TextFormElement("TWITTER", "Twitter", "Your Twitter account name."));
fec.Add(new TextFormElement("DELICIOUS", "Delicious", "Your Delicious username."));
fec.Add(new TextFormElement("FRIENDFEED", "FriendFeed", "Your FriendFeed name (as appears in the URL)."));
fec.Add(new TextFormElement("FACEBOOK", "Facebook", "Your Facebook ID which is an integer (as appears in your profile URL)."));
fec.Add(new TextFormElement("LINKEDIN", "LinkedIn", "Your LinkedIn name (as appears in your public profile URL)."));
fec.Add(new TextAreaFormElement("FRIENDSLIST", "Friends List",
"Enter the list of friends and their URLs each in one line with the format \"friend name: URL\" " +
"(i.e. \"Keyvan Nayyeri: http://nayyeri.net\").", 10));
fec.Add(new TextFormElement("FILEPATH", "FOAF File Path", "Virtual path of your FOAF file (i.e. \"~/keyvan.rdf\")."));
return fec;
But the actual first step in the main implementation is where I handle Graffiti events. As I said, I handle AfterCommit and RenderHtmlHeader events.
#region GraffitiEvent members
public override void Init(GraffitiApplication ga)
ga.AfterCommit += new DataObjectEventHandler(ga_AfterCommit);
ga.RenderHtmlHeader += new RenderContentEventHandler(ga_RenderHtmlHeader);
void ga_AfterCommit(DataBuddyBase dataObject, EventArgs e)
ObjectStore objectStore = dataObject as ObjectStore;
if (objectStore != null)
GenerateFoafFile();
void ga_RenderHtmlHeader(StringBuilder sb, EventArgs e)
sb.Append("<link rel=\"meta\" type=\"application/rdf+xml\" title=\"FOAF\" href=\"");
sb.Append(new Macros().FullUrl(VirtualPathUtility.ToAbsolute(this.FILEPATH)));
sb.Append("\" />");
In AfterCommit event I cast my DataBudyBase object to an ObjectStore object to check whether the incoming data is passed from a plug-in configuration page. This is a general point that you can inspire when developing Graffiti plug-ins to determine if the configuration for a plug-in is updated. This method calls GenerateFoafFile method which generates the FOAF file on fly.
In RenderHtmlHeader event I append the appropriate meta tag code to HTML header using the StringBuilder parameter. There is nothing special about this event except that I think that it’s a new event in Graffiti API that comes handy in many cases.
Having all these things in hand, now it’s time to dig into the main part of the code where I generate the FOAF file. GenerateFoafFile is a simple method that gets the string value of the FOAF XML and stores it on the server.
Of course, SemanticEngine.NET has a good FOAF writer but I didn’t use it for two reasons. First, I wanted a simpler code that I can embed in Graffiti Extras to keep the project independent from extra references. Besides, I have some future plans to expand the implementation, so I thought it’s better to keep my control on the code with my own implementation.
private void GenerateFoafFile()
try
string xml = GetXml();
HttpContext context = HttpContext.Current;
Graffiti.Core.Util.CreateFile
(context.Server.MapPath(this.FILEPATH), xml);
catch (Exception ex)
Log.Error("Error in FOAF plug-in", ex.ToString());
But GetXml is a string function that is a little lengthy but includes several sections that act like each other. Here I use it to create the FOAF XML string on fly. The main implementation technique behind this function is the exact same thing as what I have done in my implementation for sitemap plug-in.
private string GetXml()
StringBuilder sb = new StringBuilder();
StringWriterWithEncoding stringWriter = new StringWriterWithEncoding(sb, Encoding.UTF8);
XmlWriter xmlWriter = XmlWriter.Create(stringWriter);
// Start document
xmlWriter.WriteStartDocument();
xmlWriter.WriteStartElement("rdf", "RDF", "http://www.w3.org/1999/02/22-rdf-syntax-ns#");
xmlWriter.WriteAttributeString("xmlns", "foaf", null, "http://xmlns.com/foaf/0.1/");
xmlWriter.WriteAttributeString("xmlns", "rdfs", null, "http://www.w3.org/2000/01/rdf-schema#");
xmlWriter.WriteAttributeString("xmlns", "dc", null, "http://purl.org/dc/elements/1.1");
// <rdf:RDF>
xmlWriter.WriteStartElement("rdf", "RDF", null);
// <foaf:Person>
xmlWriter.WriteStartElement("foaf", "Person", null);
// <foaf:name />
if (!string.IsNullOrEmpty(this.FULLNAME))
xmlWriter.WriteElementString("foaf", "name", null, this.FULLNAME);
else
return string.Empty;
// <foaf:title />
if (!string.IsNullOrEmpty(this.TITLE))
xmlWriter.WriteElementString("foaf", "title", null, this.TITLE);
// <foaf:nick />
if (!string.IsNullOrEmpty(this.NICKNAME))
xmlWriter.WriteElementString("foaf", "nick", null, this.NICKNAME);
// <foaf:weblog />
xmlWriter.WriteElementString("foaf", "weblog", null, new Macros().FullUrl(new Urls().Home));
#region Flickr
// <foaf:homepage /> - Flickr
if (!string.IsNullOrEmpty(this.FLICKR))
xmlWriter.WriteStartElement("foaf", "homepage", null);
xmlWriter.WriteAttributeString("rdf", "resource", null,
string.Format("http://flickr.com/photos/{0}", this.FLICKR));
xmlWriter.WriteAttributeString("dc", "title", null,
string.Format("{0} on Flickr", this.FULLNAME));
xmlWriter.WriteAttributeString("dc", "description", null,
"My profile on Flickr.");
xmlWriter.WriteEndElement();
#region Twitter
// <foaf:homepage /> - Twitter
if (!string.IsNullOrEmpty(this.TWITTER))
string.Format("http://twitter.com/{0}", this.TWITTER));
string.Format("{0} on Twitter", this.FULLNAME));
"My page on Twitter.");
#region Delicious
// <foaf:homepage /> - Delicious
if (!string.IsNullOrEmpty(this.DELICIOUS))
string.Format("http://delicious.com/{0}", this.DELICIOUS));
string.Format("{0} on Delicious", this.FULLNAME));
"My Delicious account.");
#region FriendFeed
// <foaf:homepage /> - FriendFeed
if (!string.IsNullOrEmpty(this.FRIENDFEED))
string.Format("http://friendfeed.com/{0}", this.FRIENDFEED));
string.Format("{0} on FriendFeed", this.FULLNAME));
"My FriendFeed account.");
#region Facebook
// <foaf:homepage /> - Facebook
if (!string.IsNullOrEmpty(this.FACEBOOK))
string.Format("http://www.facebook.com/profile.php?id={0}", this.FACEBOOK));
string.Format("{0} on Facebook", this.FULLNAME));
"My Facebook profile.");
#region LinkedIn
// <foaf:homepage /> - LinkedIn
if (!string.IsNullOrEmpty(this.LINKEDIN))
string.Format("http://www.linkedin.com/in/{0}", this.LINKEDIN));
string.Format("{0} on LinkedIn", this.FULLNAME));
"My LinkedIn profile.");
#region Friends
// Friends
if (!string.IsNullOrEmpty(this.FRIENDSLIST))
List<Friend> friends = GetFriends();
if (friends.Count > 0)
foreach (Friend friend in friends)
// <foaf:knows>
xmlWriter.WriteStartElement("foaf", "knows", null);
xmlWriter.WriteElementString("foaf", "name", null, friend.Name);
// <foaf:homepage>
xmlWriter.WriteAttributeString("rdf", "resource", null, friend.Url);
// </foaf:homepage>
// </foaf:Person>
// </foaf:knows>
// </rdf:RDF>
xmlWriter.Flush();
xmlWriter.Close();
stringWriter.Flush();
return stringWriter.ToString();
This piece of code is pretty easy to understand for those who are familiar with .NET XML API as well as FOAF specification. Here I can’t discuss these two topics at all, but the code is organized in two main parts: the first part generates the elements for person’s information including his name, title, nickname, and weblog as well as his social networking/bookmarking accounts, and the second part generates the elements for person’s friends. Code comments can act as good hints in the code.
Here I have used a helper GetFriends function that parses the text entered by user for friend’s list in a specific format, and it returns a list of Friend objects that are used by GetXml function. By the way, the pattern is more flexible that what I have described in plug-in configuration page as the tip text.
private List<Friend> GetFriends()
List<Friend> friends = new List<Friend>();
string regexText = @"([a-zA-Z0-9\s\._-]+)([:\s]+)((((file|gopher|news|nntp|telnet|http|ftp|https|ftps|sftp)://)|(www\.))+(([a-zA-Z0-9\._-]+\.[a-zA-Z]{2,6})|([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}))(/[a-zA-Z0-9\&%_\./-~-]*)?)";
Regex regex = new Regex(regexText, RegexOptions.Compiled | RegexOptions.Multiline);
if (regex.IsMatch(this.FRIENDSLIST))
MatchCollection matches = regex.Matches(this.FRIENDSLIST);
foreach (Match match in matches)
Friend friend = new Friend();
friend.Name = match.Groups[1].Value.Trim();
friend.Url = match.Groups[3].Value.Trim();
friends.Add(friend);
return friends;
There are also two helper classes called Friend and StringWriterWithEncoding that you can find in the download package.
After enabling the plug-in, you can configure it in the control panel. The below snapshot shows my current configuration on my blog.
This generates my desire FOAF XML file. Note that some people prefer to store their FOAF files with a .rdf extension. If you’re going to choose such file names, take care of IIS mime types to be able to request the file. As far as I can remember, IIS doesn’t support .rdf extension by default.
You can check out my current FOAF file here. Of course, there will be many other contacts in the list that I’m missing and I’ll add smoothly, so please let me know if you and your URL should be there.
This plug-in will be a part of the next version of Graffiti Extras project hopefully with some expansions, feature improvements, and bug fixes, but for now you can download it with the source code as a separate package from here.
As I said, FOAF is a format that can include a wide range of information for authors. Such generic implementations that are going to be used by several users are compelled to take a common structure and use it; therefore, I need feedback from users to include as much relevant information as possible.
I also said in the introductory section that I’m going to implement other Web 3.0 standards for Telligent Graffiti CMS and include them in Graffiti Extras. Besides, I hope that I can do more works around semantic web which can be started by completing the implementation of SemanticEngine.NET.
Original Post: The Friend of a Friend Plug-In for Graffiti
The content of the postings is owned by the respective author. CSharpFeeds is not responsible for the contents of the postings. This site is automatically generated and cannot be reviewed for abusive content. If you find abusive content on CSharpFeeds, please contact us. Designated trademarks and brands are the property of their respective owners. All rights reserved.