rickardnilsson.net is a weblog and the online home of web developer and father of three, Rickard Nilsson... More
Rickard blogs about creating software solutions using ASP.NET and agile practices.
BlogEngine.NET has a widget framework from version 1.4 which are web part like components that can be added and removed, configured and dragged around directly in the page. Simple widgets are very easy to create and plug in to your blog and there are several blog posts explaining how to do that. In this post I'm gonna go through a more advanced example featuring Web services and Ajax.
The Photo Album Widget
Download: PhotoAlbumWidget-0.1.zip (7,96 kb) Unzip to ~/widget/ folder. Requires jQuery library.
First we start with some requirements for the widget we're creating.
The list above is a prioritized backlog and the first version should include items 1 - 4.
We start off with number one, a BlogEngine Widget. To create a widget we add a new folder to the /widget/ folder and we call it PhotoAlbum. Then we create two user controls, one for widget presentation and one for configuration UI. They must be named, by convention, widget.ascx and edit.ascx respectively and should derive from WidgetBase and WidgetEditBase respectively. See more on basic widgetry here by Rtur and here by Mads as well as this, that and whatnot.
That was the easy part.
With the next requirement we first need to get the pictures from somewhere so we skip to the next requirement. Picasa is Google's web album and it has its own REST API as well as a .NET client library. Conveniently there is also a public feed which contains all public photos uploaded to Picasa. (You can of course use your own Picasa album, see the Data API docs for more information). All we need to do is provide a query tag/tags to limit the search or we'll get an error message. The following code retrieves a couple of photos picturing cats:
PicasaService service = new PicasaService("exampleCo-exampleApp-1"); PhotoQuery query = new PhotoQuery("http://picasaweb.google.com/data/feed/api/all"); query.Query = "cat"; query.NumberToRetrieve = 5; PicasaFeed feed = service.Query(query);
foreach (PicasaEntry entry in feed.Entries) { string firstThumbUrl = entry.Media.Thumbnails[0].Attributes["url"] as string; writer.Write("<img src=\"{0}\" alt=\"{1}\" />", firstThumbUrl, entry.Title.Text); }
If we drop the code above into the widget.ascx.cs we affectivally fullfills both requirements 2 and 3 so lets get on with number 4.
Requirement number four implies the Incremental Page Display pattern where the main part of the page is shown quickly and portions of the page that takes longer to load are fetched and displayed asynchronously. To give the user feedback of the loading we place an animated gif image in the widget which is later swapped out when the real pictures arrive. To accomplish this we're using the jQuery JavaScript library which let us get away with very little code for pretty advanced stuff.
First we need to do a little refactoring since the widget needs to do an asynchronous call somewhere to get its content, i.e. the pictures to display. We're gonna go with the simplest possible solution here, just enough to satisfy the requirement. The simplest thing from the widget's point of view is to have the html containing a number of <img> elements returned from the call. Then it's very simple to insert the html into the widget using jQuery. Given that the widget contains a div with the id "photoalbum" this is all it takes:
$("#photoalbum").html(the_html_to_insert);
As it is now, the widget codebehind is rendering the requested html and this is not god. We need to move the code to some place which we can make a http request to and get the html in response. This can be done with a regular aspx page. All we need to do is to put a simple Repeater control on a page and bind it to a list of image URLs which we get from Picasa web album. So, we add a new aspx page to the /widgets/Photo Album/ folder and call it PhotoService.aspx. All we want in response is the <img> tags which should be inserted into the widget so we clear the page from html and put a single Repeater on it like this:
<asp:Repeater ID="pictureRepeater" runat="server"> <ItemTemplate> <asp:Image ID="image" runat="server" ImageUrl="<%# Container.DataItem.ToString() %>" /> </ItemTemplate> </asp:Repeater>
In the codebehind we insert something like before and makes sure we bind to the Repeater control.
// Error checking is omitted for clarity List<string> pictures = new List<string>(); PicasaService service = new PicasaService("exampleCo-testApp-1");
string picasaUri = "http://picasaweb.google.com/data/feed/api/all"; PhotoQuery query = new PhotoQuery(picasaUri); query.Query = "cat"; query.NumberToRetrieve = 6; PicasaFeed feed = service.Query(query);
foreach (PicasaEntry entry in feed.Entries) { string firstThumbUrl = entry.Media.Thumbnails[0] .Attributes["url"] as string; pictures.Add(firstThumbUrl); }
pictureRepeater.DataSource = pictures; pictureRepeater.DataBind();
To test the service we point a web browser to /widgets/Photo Album/PhotoService.aspx which should show a bunch of cats! Everything looks good and all we need to do to get the pictures into the widget are two things. First we need to register the jQuery JavaScript file, either localy or a referer to the jQuery official site. Last we need to add some custom JavaScript that makes the Ajax call and puts the html at the right place.
JavaScript blocks should allways, if possible, be placed as close to the </body> tag as possible. The reason is that the browser won't continue to load the remainder of the page when it hits a JavaScript block but rather wait until it is fully loaded. This is not a problem if we're dealing with short scripts but the jQuery library is relatively large in this context so we need to put it as far down the page as possible. Our own script that does the actual work depends on jQuery, thus it must be loaded last. The problem is that we need to do this in a User control in the middle of the page. To our help to solve this we have the ClientScriptManager that every aspx page has. With the method RegisterStartupScript we get to insert arbitrary scripts just before the </form> tag (which is close enough) of the surrounding page. We make the registrations when the widget is loaded, i.e. in the LoadWidget() method.
With this final step done we have our first version of the Photo Album Widget which is ready for demo.
Before the second iteration I'd like to summarize a couple of things with version one that are not of production quality.
Thanks to the new MetaWebLog API in BlogEngine.NET you can use MS Word 2007 to write your blog posts including using graphics, charts and the full feature set of Word 2007. Read about how you set it up at Rtur.net. You can even use Word 2007 to edit posts on the blog server.
Note: This post was published using Word 2007.
With the new release of BlogEngine.NET a new feature was added that shows the description or part of the post when listing related posts. If the description is empty a small part of the post content is listed instead and here in lies the problem. The post content is simply clipped as a certain length without regard to any tags in the post. Let's say a related posts goes like this:
With the new release of <a href="http://www.dotnetblogengine.net">BlogEngine.NET</a> a new feature was added.
Now, when the post is clipped it may end up cutting in the middle of the href:
With the new release of <a href="http://www.dotnetblo...
And this may probably screw up the layout and content of the remainder of the page, besides producing an html that will not validate.
This issue has been discovered and fixed for a future release of BlogEngine.NET. However, if you, like me don't or can't wait that long here's a fix:
The problem is that the html tags are not closed so the simple solution is to strip the related post of all html. My first thought was to parse the post and close all open tags but when I thought about the actual intent of the related posts I realized that it is to provide a preview of the post content, not to show part of the actual post. So, by stripping all html tags all images and links are removed and only the text remains, which provides a really nice preview.
To implement the solution you only have to edit one line of code in the App_Code/Controls/RelatedPosts.cs file on row 139:
string content = post.Content;
string content = Utils.StripHtml(post.Content);
Or you can download the appended file and replace the one in the App_Code/Controls/ folder.
RelatedPosts.cs (3,77 kb)
Thanks to a great deal of interest on my post on syntax highlighting in BlogEngine.NET a while ago I'm now releasing a new version. It is based on the version that ships with BlogEngine.NET but it has been augmented as well as refactored to support more language elements like types in C#. The code formatter has been extracted and the new version is released through CodePlex.
Additional features compared to version 0.1
Syntax Highlighter Extension
I will start a series of blog posts on using and developing the new version. I thank those who have shown interest.
BlogEngine.NET ships with an extension that automatically highlights source code in blog posts. All that is required is that the source code block is surrounded with [code:lang][/code] tags. The extension will markup the code with CSS classes and the default theme includes a default color scheme for code elements like keywords, comments, and so forth. The extension ships with support for HTML, C#, JavaScript, T-SQL, MSH, and Visual Basic.
In forums and blogs in the BlogEngine community issues with the syntax highlighter extension has been brought up. Some of it can be found here, here, and here. Many has complained about how hard it is to use and lack of proper documentation. To get the tags to be recognized by the extension you have to format your post in a really precise manor with a leading and trailing <p></p>. Besides taking up a lot of unnecessary space when editing the post, this is why so many has complained that they can't get it to work. If the block is not correctly surrounded with the right amount of line breaks two things can happen. Either the whole code block is masked or the code is shown but the tags are rendered as part of the code block.
My own experience with this is pretty much the same and the only way to find out how it works was for me to read and step through the code. Since the extension is open source there is no hinder for improving the code base, hence I've been working to improve it to meet the community need, as well as my own. In addition to the mentioned usability issues I missed highlighting of types in C#, that is class, interface, and struct names which we are used to see in Visual Studio colored in cyan.
I'm about to present a new version of the extension which will include the following improvements:
Leave a comment on this post if you wish to be notified when the new version is available.
ICustomer customer = new Customer("kalle"); RegEx regex; ICollection<Customer> coll = new ICollection<Customer>(); Stack<Name.Space.Customer> stack = new Stack<Name.Space.Customer>(); stack.Put(customer); customer.Age = 24;
[Serializable] public class Customer : ICustomer , IComparable<ICustomer> { public Customer(string name) { this.name = name; person = new Person(name); } public int Age { get { return age; } set { age = value; } } IPerson _p = Person.CurrentUser; IPerson person; internal IPerson Person { get { return this.person; } }
private ShopingCart cart = new ShopingCart(); protected ShopingCart GetCart() { return cart; } }
Hej! Kul att du tittar in. Jag har äntligen haft möjlighet att flytta min gamla blog från webblogg.se till min egen domän. Tack vare BlogEngine.NET gick det supersnabbt och öppnar för stora anpassningsmöjligheter. I och med detta så kommer min gamla blog att förflyttas ut i nullspace men en tid kommer en länk att ligga där för att peka in gamla besökare till den nya platsen.
Här på rickardnilsson.net kommer jag att blogga om det som intresserar mig mest inom webbutveckling med ASP.NET, C#, ASP.NET Ajax, med mera. Så, titta gärna in snart igen eller börja prenumerera på något av mina RSS-feeds.