Jul2008
22

Show Commenter's Latest Post: BlogEngine.NET Extension

by John Dyer

Background

Over the past few weeks, I've seen a few WordPress blogs that pull in the last post from a commenter's website. I also saw a post from Mads about how to find semantic links on a webpage.

So, I mashed his code together with some code from the BlogRoll control and created an extension that goes out and looks for RSS feeds at the commenter's website and then pulls in the latest post. [side note: I wish I could say, "a half hour later ... ", but it actually took me a while to get it to work...]

It all happens asynchronously, so if you comment on this post, it'll take a few seconds for it to show up. Then it will keep checking every so often for new posts.

Example

Last year, I posted a JavaScript color picker and the post now has over 100 comments. Here's what it looks like with the extension running:

image 

Download & Installation

  • Download Commenter's Latest Post Extension
  • The last post is wrapped in the following class so you can style it:
    <span class="commenterslastpost"></span>
  • Sometimes the ResolveLinks extension interferes with this, so you might want to add the following code around the foreach loop:
    if (e.Body.IndexOf("href=\"" + match.Value + "\"") == -1) { ... }

Add a comment to this post to see it in action...

May2008
15

ISBN Functions in C# (ISBN-10 to ISBN-13 Conversion)

by John Dyer

While working with some book data, I needed to merge the old ISBN-10 and ISBN-13 data. Unfortunately, I couldn't find any C# code to do the conversion. Search for "ISBN C#" in Google only return book stores with C# books.

So here is a little class that will convert and validate ISBNs. Here's how the code would look:

string isbn10 = "0830818030"; 
string isbn13 = ISBN.Convert10to13(isbn10); // returns "9780830818037"

string isbn = "083081803X"; // this one has a bad checksum (last digit) 
string correctIsbn = ""; 
if (!ISBN.IsValid(isbn, out correctIsbn)) { 
	isbn = corretnIsbn; // returns "0830818030"
}

An ISBN object can also be instantiated and used:

IBSN myBook = new ISBN("0-83081-803-0"); 
string isbn13 = myBook.ISBN13; // returns "9780830818037"
string isbn10 = myBook.ISBN10; // returns "0830818030" 

Here is the class:

public class ISBN
    {

        public ISBN(string isbn)
        {
            SetIsbn(isbn);
        }

        private string _isbn10 = "";
        private string _isbn13 = "";

        public string ISBN10
        {
            get
            {
                return _isbn10;
            }
            set
            {                
                string corrected = "";
               
                if (!IsValid(value, out corrected) && corrected == "")
                    throw new Exception("invalid ISBN");

                SetIsbn(corrected);
            }
        }

        public string ISBN13
        {
            get
            {
                return _isbn13;
            }
            set
            {
                string corrected = "";

                if (!IsValid(value, out corrected) && corrected == "")
                    throw new Exception("invalid ISBN");

                SetIsbn(corrected);
            }
        }

        private void SetIsbn(string isbn)
        {
            isbn = CleanIsbn(isbn);

            if (isbn.Length == 10)
            {
                _isbn10 = isbn;
                _isbn13 = Convert10to13(isbn);
            }
            else if (isbn.Length == 13)
            {
                _isbn13 = isbn;
                _isbn10 = Convert13to10(isbn);
            }
        }

        private static string CleanIsbn(string isbn)
        {
            return isbn.Replace("-", "").Replace(" ", "");
        }


        public static string Convert10to13(string isbn)
        {
            return Convert10to13(isbn, true);
        }
        public static string Convert10to13(string isbn, bool throwError) {
            
            // remove - and space
            string isbn10 = CleanIsbn(isbn);

            if (isbn10.Length != 10 && throwError)
                throw new Exception("ISBN must be 10 characters long");

            // 1) Drop the check digit (the last digit)
            isbn10 = isbn10.Substring(0, 9);

            // 2) Add the prefix '978' 
            string isbn13 = "978" + isbn10;

            // 3) Recalculate check digit 
            isbn13 = isbn13 + Isbn13Checksum(isbn13);

            return isbn13;
        }

        public static string Convert13to10(string isbn)
        {
            return Convert13to10(isbn, true);
        }

        public static string Convert13to10(string isbn, bool throwError)
        {

            // remove - and space
            string isbn13 = CleanIsbn(isbn);

            if (isbn13.Length != 13 && throwError)
                throw new Exception("ISBN must be 13 characters long");

            // 1) Drop the check digit (the last digit) and prefix '978'
            string isbn10 = isbn13.Substring(3, 9);
     
            // 2) Recalculate your check digit using the modules 10 check digit routine.
            isbn10 = isbn10 + Isbn10Checksum(isbn10);

            return isbn10;
        }

        public static bool IsValid(string isbn)
        {
            string correctIsbn = "";
            return IsValid(isbn, out correctIsbn);
        }

        public static bool IsValid(string isbn, out string correctISBN)
        {
            // remove - and space
            isbn = CleanIsbn(isbn);

            if (isbn.Length == 10) {
                return ValidateIsbn10(isbn, out correctISBN);
            }
            else if (isbn.Length == 13)
            {
                return ValidateIsbn13(isbn, out correctISBN);
            }
            else
            {
                correctISBN = "";
                return false;
            }

        }

        private static string Isbn10Checksum(string isbn)
        {
            int sum = 0;
            for (int i = 0; i < 9; i++)
                 sum += (10-i) * Int32.Parse(isbn[i].ToString());
   
            float div = sum / 11;
            float rem = sum % 11;

            if (rem == 0)
                return "0";
            else if (rem == 1)
                return "X";
            else
                return (11 - rem).ToString();
        }

        private static string Isbn13Checksum(string isbn)
        {
            float sum = 0;
            for (int i = 0; i < 12; i++)
                sum += ((i % 2 == 0) ? 1 : 3) * Int32.Parse(isbn[i].ToString());

            float div = sum / 10;
            float rem = sum % 10;

            if (rem == 0)
                return "0";
            else
                return (10 - rem).ToString();
        }


        private static bool ValidateIsbn10(string isbn, out string correctISBN)
        {
            correctISBN = isbn.Substring(0, 9) + Isbn10Checksum(isbn);

            return (correctISBN == isbn);
        }

        private static bool ValidateIsbn13(string isbn, out string correctISBN)
        {
            correctISBN = isbn.Substring(0, 12) + Isbn13Checksum(isbn);

            return (correctISBN == isbn);
        }
}

I initially wanted to also make a method that would add back in the correct dashes, but I found out that the make up is based on country and publisher codes assigned by the International ISBN Agency which regularly updates the list of codes making it pretty much impossible to permanantly functionalize.

Apr2008
23

Dynamic Text Replacement with JavaScript and C# ASP.NET

by John Dyer

What it Does

You write some basic markup and include a JavaScript file and some code which replaces normal text with a .NET generated image:

<style type="text/css">
h2 { color: #9E100F; size: 22px; }
</style>

<!-- This says "Frequently asked questions in zh-TW -->
<!-- If you just see boxes, you need to install Asian fonts on your machine --> <h2>關於網上教育常見的問題</h2> <script type="text/javascript" src="fontreplacer.js"></script> <script type="text/javascript"> FontReplacer.replace('h2','*', 'MyGreatFont'); </script>

This will take all h2 tags with any class (*) and replace them with and image rendered using 'MyGreatFont'. This font needs to be installed the c:\windows\fonts\ folder of your server or in the same folder as the .NET script which generates it. Initially the page will look like this (using the standard font for Chinese script on a PC which is 'MS Mincho'):

image

But after the replacement happens, it looks like this:

image

With another less formal font, it could look like this:

image

Of course, you can use any font installed on the server and it is not limited to Chinese text. Here is an example replacing the default Times New Roman text with Century Gothic:

image

image

This may be useful in other scenarios for viewing Unicode text on a machine without Unicode fonts installed (Greek, Hebrew, Arabic, etc.).

It can also produce anti-aliased PNGs for use on top of an image:

image image

Why I did it

Recently, I needed to have the titles for a series of webpages be rendered in a specific Chinese font (see http://www.dts.edu/chinese and click on "zh-TW" in the upper right) because the default doesn't look very official (according to my Chinese friends). I wanted to use sIFR which replaces the HTML text node with a Flash file that has the font embedded. The problem is that Chinese fonts are 3-8MB which means the user has to download a gigantic SWF. Also, there can be copyright problems with using sIFR.

So I turned to .NET to generate images using the font. The basic technique was first published back in a 2004 A List Apart article titled Dynamic Text Replacement. ALA used JavaScript and PHP and, since then, there have been many permutations of the basic idea, some using pure CSS instead of JavaScript. I haven't seen a good one for .NET, so I had to write my own. I don't like using CSS for some of the reasons mentioned here, so I went with JavaScript for the replacement. This way users without images or JavaScript can still see the text. Users with images and JavaScript get a slightly nicer view.

How it Works

The JavaScript method looks at the HTML element(s) you send it and pulls out the text and font color. It then sends a request to ASP.NET to return an image which renders the text using your assigned font and colors. There are three ways to call it:

// elements by tag and class
FontReplacer.replace(tag, class, fontName);
// elements by tag and class inside a given elmenent
FontReplacer.replaceIn(element, tag, class, fontName);
// replace array of elements
FontReplacer.replaceElements(elementArray, fontName);

This calls up a .NET script which writes the text to an image using the specified font. The C# code can generate a JPG or transparent PNG which happily sits on top of any background and looks very nice. (There is a flag in the JavaScript to to allow the PNG transparency). There are some tricks in .NET for generating a PNG with alpha channels and for saving a JPEG with a specified quality, so here's the full code for that part.

if (backColor == "transparent")
{
	MemoryStream io = new MemoryStream();
	bitmap.Save(io, ImageFormat.Png);

	context.Response.ContentType = "image/png";
	context.Response.BinaryWrite(io.GetBuffer());
}
else
{
	context.Response.ContentType = "image/jpg";
	SaveAsJpeg(bitmap, context.Response.OutputStream, (long)100);
}
void SaveAsJpeg(Image inputImage, Stream stream, long quality)
{
	 // generate JPEG stuff
	 ImageCodecInfo codecEncoder = GetEncoder("image/jpeg");
	 EncoderParameters encoderParams = new EncoderParameters(1);
	 EncoderParameter qualityParam = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, quality);
	 encoderParams.Param[0] = qualityParam;
	 inputImage.Save(stream, codecEncoder, encoderParams);
}
ImageCodecInfo GetEncoder(string mimeType)
{
	ImageCodecInfo[] codecs = ImageCodecInfo.GetImageEncoders();
	foreach (ImageCodecInfo codec in codecs)
	{
		if (codec.MimeType == mimeType)
		{
			return codec;
		}
	}
	return null;

}

There are also flags in the JavaScript to force the returned image to be the same height and width as the element you are replacing. By default, the height is forced so that the page doesn't jerk at all, but you should be aware that browsers often report the font size inaccurately. Also, .NET's measurement of the size a given string will take up can include some padding depending on the font, so you may have to play with it for your scenario.

Lastly, there are some important things to know about using fonts on the server.

  1. When you install a font on c:\windows\fonts\, IIS doesn't immediately know it's there. You'll need to run the "resetiis" command for IIS to pick up the new fonts.
  2. If you're in a hosted environment and can't install fonts, you're still in luck. There is some code to load a *.TTF file in the fontwriter.ashx script. In this case, you'll need to specify the font file name ('gothic.ttf') rather than the installed font name ('Century Gothic').
  3. You might want to adjust the TextRenderingHint hint property at the top if you don't want anti-aliased rendering.

Download & Example

Apr2008
14

Dallas Seminary Mobile Site

by John Dyer

Since Microsoft and Apple finally decided to allow/license versions of Flash Lite 3.0 for their mobile devices (Windows Mobile and iPhone), I created an initial build of a mobile site for Dallas Seminary which will eventually use Flash Lite 3.0 for all the audio and video content.

The goal was to make it look like an iPhone application using the list format, but have it work properly on other mobile devices by not specifically targeting the iPhone's screen size such as Windows Mobile and Blackberry. It's a bit more cramped on the smaller WM screen, but it works well.

Here are a few screen shots:

iPhone Windows Mobile
image dts_wm_home
image dts_wm_media

 

There is one gotcha on the iPhone that doesn't show up using Safari. The iPhone may try to startup zoomed out like it would need for a normal website. If your <div> tags are set to expand, the iPhone will stretch everything out and then zoom out.

 image

To fix this add the following:

<meta name="viewport" content="user-scalable=no,width=320,scale=1.0" />
Mar2008
12

Data Portability Pack now in BlogEngine.NET Core

by John Dyer

The Data Portability Pack that I posted about a few weeks ago has now made it into the core of BlogEngine.NET for version 1.4. Mads Kristensen added the SIOC and APML handlers, and Roman Clarkson is working on a FOAF implementation that includes adding some Profile data (first and last name, etc.) to users of a BE.NET instance.

FOAF (friend of a friend) currently doesn't have any way of (1) automatting "Add as Friend" style interactions (like Facebook or Myspace) or (2) verifying connections between people. Roman and I would like to find a secure way of implementing this functionality which would in effect create a decentralized social network. There are however, several problems to work through regarding security (public vs. private friends lists), identification (unique IDs: email or OpenID?), verification (preventing friend spam), and so on. These issues have been talked about for some time in the FOAF community, but so far there hasn't been an implementation. Here are some older (2003) and newer (2007) discussions on the subject to get caught up:

Feb2008
14

Data Portability Pack for BlogEngine.NET

by John Dyer

What it Does

This is an initial release of 3 extensions which will add the follow protocols to your BlogEngine.NET blog:

  • SIOC - Somewhat like RSS, but utilizes RDF to describe connections to  other sites and between users and posts (example).
  • APML - Creates an "attention profile" of your interests based on tags and categories. Each is given a value based on its frequency (example).
  • FOAF - A list of your friends, their websites, and a hashed version of their email for a unique key (example).

You can see examples of each of these by clicking on the links on the upper right of this blog. To learn more about data portability, see this post. The following links show this data in a browsable format:

How it Works

imageEach of these has a Handler, a Generator, and an Extension file. The extension files automatically add <meta> discovery files to your <head> tag.

Note: Currently, the FOAF information is stored in two different Extension files due to a limitation in BE.NET 1.3 (it cannot store both scalar and table data). One extension is for your personal information and the other is for your friends' data. BE 1.4 will allow these to be merged into one file.

Warning: BE.NET currently deletes extension data when you change *.cs files, so please backup your extension file when adding or changing extensions.

Installation and Usage

  1. Download Data Portability Pack for BlogEngine.NET
  2. Copy the *.cs files into your /App_Code/Extensions folder (you can also create a sub-directory within that folder)
  3. Add the following to your web.config file
    	<add verb="*" path="sioc.axd" type="BlogEngine.DataPortability.SiocHandler, App_Code" validate="false"/>
    	<add verb="*" path="foaf.axd" type="BlogEngine.DataPortability.FoafHandler, App_Code" validate="false"/>
    	<add verb="*" path="apml.axd" type="BlogEngine.DataPortability.ApmlHandler, App_Code" validate="false"/>	
    	
  4. Go to your Admin->Extensions page and you should see 4 extensions prefixed with "DP_". The FoafFriends and MyProfile extensions need data entered, the other ones you can just leave as their defaults.
    image
  5. Optionally, you can add links to each of these files (as I have on the upper right)

Future Work

  • Merge FOAF extensions for BE 1.4
  • Possibly store FOAF data in a separate file rather than as Extension data
  • Add additional settings to APML such as parsing blog roll
  • If you also add OpenID support to your blog, then you will have everything that is currently on the data portability standards list.

kick it on DotNetKicks.com

Feb2008
8

SIOC implementation for BlogEngine.NET

by John Dyer

Update: This is now part of the Data Portability Pack for BlogEngine.NET

A major feature of BlogEngine.NET is that it supports a lot of open standards. Keeping with this, I've setup an initial implementation of SIOC for BlogEngine.NET. SIOC (Semantically-Interconnected Online Communities) is an extension of RDF which goes beyond RSS in that it describes not only the content of a website, but the connections between site content, users on the site, and another websites. While RSS has one big document for recent content, SIOC splits up its data into (1) a site profile, (2) profiles for authors, (3) profiles for posts, and (4) profiles for comments. Each of these not only lists the author's name, but also includes FOAF (Friend of a Friend) data to help identify users across the Internet via a hashed email address. If all sites were SIOC/FOAF enabled, you could theoretically search an aggregator site (like Technorati if it supported SIOC) for all posts and comments by a given user within a set of blogs, forums, or other websites. For more information on this, see my post on Data Portability standards.

Site Profile

To get a feel for the data, here's what the site profile looks like (note: I've tried to match the Word Press SIOC plugin URL conventions as much as possible)
http://johndyer.name/sioc.axd?sioc_type=site

<rdf:RDF>
<!-- users -->
<sioc:Usergroup rdf:about="http://localhost/johndyer/sioc.axd?sioc_type=site#authors"> <dc:title>Authors at "John Dyer"</dc:title> <sioc:has_member> <sioc:User rdf:about="http://localhost/johndyer/author/John+Dyer.aspx" rdfs:label="John Dyer"> <sioc:see_also rdf:resource="http://localhost/johndyer/sioc.axd?sioc_type=user&amp;sioc_id=John+Dyer" /> </sioc:User> </sioc:has_member> </sioc:Usergroup>
<!-- site info -->
<sioc:Site rdf:about="http://johndyer.name/"> <dc:title>John Dyer</dc:title> <dc:description>ASP.NET, Flash, and JavaScript from FreeTextBox and Dallas Seminary</dc:description> <sioc:link>http://johndyer.name/</sioc:link> <sioc:host_of>http://johndyer.name/sioc.axd?sioc_type=site#webblog</sioc:host_of> <sioc:has_group>http://johndyer.name/sioc.axd?sioc_type=site#authors</sioc:has_group>
</sioc:Site>
<!-- blog info, posts--> <sioc:Forum rdf:about="http://johndyer.name/sioc.axd?sioc_type=site#webblog"> <sioc:name>John Dyer</sioc:name> <sioc:link rdf:resource="http://johndyer.name/sioc.axd?sioc_type=site#webblog" /> <sioc:container_of> <sioc:Post rdf:about="http://johndyer.name/post/2008/02/BlogEngineNET-Extension-Flash-Video-Player.aspx" dc:title="BlogEngine.NET Extension: Flash Video Player"> <rdfs:seeAlso rdf:resource="http://johndyer.name/sioc.axd?sioc_type=post&amp;sioc_id=06af08e0-c4eb-421c-9e18-b618358d33ca" /> </sioc:Post> </sioc:container_of> </sioc:Forum> </rdf:RDF>

This describes (1) the users on the site (sioc:Usergroup) which would be blog authors, (2) the site in general (sioc:Site), and (3) and displays the content (sioc:Forum). Each of these has links to other SIOC documents for the user and then for the posts.

Post Profile

Here's what a post looks like:
http://johndyer.name/sioc.axd?sioc_type=post&sioc_id=612564cd-3cfa-42b5-92e9-44beb52f7cd5

<?xml version="1.0" encoding="utf-8"?>
<rdf:RDF xmlns:foaf="http://xmlns.com/foaf/0.1/" xmlns:rss="http://purl.org/rss/1.0/" xmlns:admin="http://webns.net/mvcb/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:dcterms="http://purl.org/dc/terms/" xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:content="http://purl.org/rss/1.0/modules/content" xmlns:sioc="http://rdfs.org/sioc/ns#">
  <foaf:Document rdf:about="http://johndyer.name/">
    <dc:title>SIOC post profile for "John Dyer"</dc:title>
    <dc:description>A SIOC profile describes the structure and contents of a weblog in a machine readable form. For more information please refer to http://sioc-project.org/.</dc:description>
    <foaf:primaryTopic>http://johndyer.name/post/2008/02/Website-Break-Coke-vs-Diet-Coke-vs-Coke-Zero.aspx</foaf:primaryTopic>
    <admin:generatorAgent>BlogEngine.NET SIOC Generator (1.0) by John Dyer</admin:generatorAgent>
  </foaf:Document>
<!-- post data -->
<sioc:Post rdf:about="http://johndyer.name/post/2008/02/Website-Break-Coke-vs-Diet-Coke-vs-Coke-Zero.aspx"> <sioc:link rdf:resource="http://johndyer.name/post/2008/02/Website-Break-Coke-vs-Diet-Coke-vs-Coke-Zero.aspx" /> <sioc:has_container rdf:resource="http://johndyer.name/sioc.axd?sioc_type=site#webblog" /> <dc:title>Website Break: Coke vs. Diet Coke vs. Coke Zero</dc:title> <!-- post author -->
<sioc:has_creator> <sioc:User rdf:about="http://johndyer.name/author/John+Dyer.aspx"> <rdfs:seeAlso rdf:resource="http://johndyer.name/sioc.axd?sioc_type=user&amp;sioc_id=John+Dyer" /> </sioc:User>
</sioc:has_creator> <foaf:maker> <foaf:Person rdf:about="http://johndyer.name/author/John+Dyer.aspx" foaf:name="John Dyer"> <foaf:mbox_sha1sum>3eb435778fafc3efde35fba2ca731c6d3234178f</foaf:mbox_sha1sum> <foaf:homepage rdf:resource="http://johndyer.name/" /> <rdfs:seeAlso rdf:resource="http://johndyer.name/sioc.axd?sioc_type=user&amp;sioc_id=John+Dyer" /> </foaf:Person> </foaf:maker>
<dcterms:created>2/1/2008 4:40:53 PM</dcterms:created> <sioc:content> Today, we decided to take a break from web development and do something truly important: a taste test. Tim, Adam, Brian, and I wanted to see if we could identify Coke, Diet Coke, and Coke Zero in a blind test. We each had three cups that another team member had setup. We tasted each drink and tried to match the drink with the cup and also mark our favorite. Tim is the narrator and then the participants from left to right are: John (me), Adam, Tim, and Brian. </sioc:content> <content:encoded>&lt;p&gt;Today, we decided to take a break from web development and do something truly important: a taste test. Tim, Adam, Brian, and I wanted to see if we could identify Coke, Diet Coke, and Coke Zero in a blind test. We each had three cups that another team member had setup. We tasted each drink and tried to match the drink with the cup and also mark our favorite. Tim is the narrator and then the participants from left to right are: John (me), Adam, Tim, and Brian.&lt;/p&gt;</content:encoded> <!-- categories and tags -->
<sioc:topic rdfs:label="Off Topic" rdf:resource="http://johndyer.name//category/Off Topic.aspx" /> <sioc:topic rdfs:label="challenge" rdf:resource="http://johndyer.name//?tag=/challenge" /> <sioc:topic rdfs:label="soda" rdf:resource="http://johndyer.name//?tag=/soda" />
<!-- comments --> <sioc:has_reply> <sioc:Post rdf:about="http://johndyer.name/post/2008/02/Website-Break-Coke-vs-Diet-Coke-vs-Coke-Zero.aspx#id_a82699f9-77e2-460d-b3b1-d1d470180df9"> <rdfs:seeAlso rdf:resource="http://johndyer.name/sioc.axd?sioc_type=comment&amp;sioc_id=a82699f9-77e2-460d-b3b1-d1d470180df9" /> </sioc:Post> </sioc:has_reply>
<!-- outbound links --> <sioc:links_to rdf:resource="http://www.cokezero.com/" rdfs:label="Coke Zero" /> </sioc:Post> </rdf:RDF>

This one also has a "document" description (which I left out on the site profile) and then has information about the post including its author (both sioc:has_creator and foaf:maker which has my email as a SHA1 hash), content (sioc:content and content:encoded), categories (sioc:topic), outbound links (sioc:links_to), and then links to the comments for the post (sioc:has_reply)

Download and Installation

  1. Download BlogEngine.NET SIOC extension
  2. Copy the three files into the \App_Code\Sioc\ directory
  3. Add the following line to your web.config
    <add verb="*" path="sioc.axd" type="SIOC.SiocHandler, App_Code" validate="false"/>

Update: This is now part of the Data Portability Pack

What's Next?

  1. This extension will add a <link> tag to your header, but you view it directly at: http://yoursite.com/sioc.axd
  2. Explore the SIOC file at:  http://sparql.captsolo.net/browser/browser.py?url=http%3A%2F%2Fjohndyer.name%2Fsioc.axd
  3. An SIOC explorer is also available here which allows you to search and sort the data from among several sites: http://www.activerdf.org/sioc/
Feb2008
4

BlogEngine.NET Extension: Flash Video Player

by John Dyer

Last week, I posted a flash video using flvplayer extension from betaparticle for BlogEngine.NET. I wanted to make some adjustments to the extension and I ended up completely rewriting it.

Updates/improvements:

  • Plays FLV or MP4.
  • All properties are available in the BlogEngine.NET extension manager so you don't have to touch the code to change settings.
  • The size of the player will automatically make room for the controls at the bottom (20px)
  • Uses SWFObject for flash replacement. This allow graceful degradation for those without Flash including mobile users. (I'll upgrade it to SWFObject 2.0 when it's released)
  • Enables Fullscreen playback.
  • Shows an initial image at startup named the same as the flv (myvideo.flv and myvideo.jpg). This way the video is not black at startup.
  • Optionally, instead of SWFObject, full <object><embed> formatting can be used.

Credits

Uses

Installation & Usage

  1. Download the file and unzip: Flash Video Player extension for BlogEngine.NET
  2. Copy FlashVideoPlayer.cs to /App_Code/Extensions/
  3. Copy the flashvideo folder to the root of your application (this can be customized in the settings)
  4. Add videos (myvideo.flv or myvideo.mp4) and images (myvideo.jpg) to the flashvideo folder.
  5. To use, just add [flv:myvideo.flv] anywhere in your post.

Example

oregon
This video requires Adobe Flash player

Download:

Dec2007
31

C# Template for Creating Google Video Sitemap

by John Dyer

Earlier this month, Google introduced "Video Sitemaps" as an extension to the Sitemap Protocol standard. It allows website owners to expose their video content (including embedded video) to be indexed and included in Google's video search (here is Google's spec). Embrace and extend ;)

I just added this to Dallas Theological Seminary's site: http://www.dts.edu/videositemap.xml and I thought it might help to share the template to speed someone else's development:

using System;
using System.Data;
using System.Configuration;
using System.Collections.Generic;
using System.Web;
using System.Xml;
using System.Text;

namespace YourNamespace
{
    public class VideoSiteMap : IHttpHandler
    {
        public VideoSiteMap()
        {
        } 
        
        public void ProcessRequest(HttpContext context)
        {
            XmlTextWriter writer = new XmlTextWriter(context.Response.OutputStream, Encoding.UTF8); 
            context.Response.ContentType = "text/xml";

            writer.Formatting = Formatting.Indented;
            writer.WriteStartDocument(); 
            writer.WriteStartElement("urlset");
            
            // add namespaces
            writer.WriteAttributeString("xmlns", "http://www.sitemaps.org/schemas/sitemap/0.9");
            writer.WriteAttributeString("xmlns", "video", null, "http://www.google.com/schemas/sitemap-video/1.0");
            
            // fake loop
            for (int i=0; i<10; i++) {
                writer.WriteStartElement("url");

				// required
                writer.WriteElementString("loc", "http://mysite.com/myplayer.aspx");
                writer.WriteStartElement("video", "video", null); 
                
                // start:optional
                writer.WriteElementString("video", "title", null, "Video Title");
                writer.WriteElementString("video", "description", null, "a great video");
                writer.WriteElementString("video", "thumbnail_loc", null, "http://mysite.com/myvideothumb.jpg");
                writer.WriteElementString("video", "family_friendly", null, "Yes");
                writer.WriteElementString("video", "content_loc", null, "http://mysite.com/myvideo.flv");
                writer.WriteElementString("video", "duration", null, "100");                    
                
                writer.WriteStartElement("video", "player_loc", null);                
                writer.WriteAttributeString("allow_embed", "true");
                writer.WriteString("http://mysite.com/embeddedplayer.swf");
                writer.WriteEndElement(); // video:player_loc
                // end:optional
                
                writer.WriteEndElement(); // video:video
                writer.WriteEndElement(); //url
            }
            
            writer.WriteEndElement(); //urlset 
            writer.WriteEndDocument();
            writer.Close();
        }
        public bool IsReusable
        {
            get { return false; }
        }
    }
}

Just add this class to your web.config and you're all set:

<system.web>
   <httpHandlers>
      <add verb="*" path="videositemap.xml" type="YourNamespace.VideoSiteMap, YourNamespace"/>
   </httpHandlers>
</system.web>

Hope that helps!

Dec2007
6

Working with Different Kinds of Data: Using OUTER JOINS and Inheritance

by John Dyer

I am working on combining a lot of data into one engine where items can be searched and linked together, but there are several data type, each with unique data fields. In my case, I am working with (1) audio/video content and (2) text content such as articles and news items. To represent this data in a database, I could make one large table with lots of fields, some common to both data types and some specific to each one. Then this data can be represented with a single class and my ORM is very simple.

Instead I've chosen to use a combination of several tables to separate out the data, but then I join them together on the programming side with inheritance. Here's the data model (simplified for this example), where the shared elements are in the GenericItems table:

CREATE TABLE GenericItems (
    ItemID uniqueidentifier,
    Title nvarchar(255),
    CreationDate datetime,
    IsActive bit,
    Description ntext,
    ItemType int) 

CREATE TABLE TextItems (
    ItemID uniqueidentifier,
    ArticleText ntext,
    AuthorID int,
    MagazineID int)

CREATE TABLE MediaItems (
    ItemID uniqueidentifier,
    Filename nvarchar(50),
    Filesize int,
    Duration int)

I then create three classes to model represent this (again, shortened for space):

public class GenericItem {
    protected Guid _itemID;
    protected string _title;

    public Guid ItemID {
        get { return _itemID; }
        set { _itemID = value; }
    }
    public string Title {
        get { return _title; }
        set { _title = value; }
    }
    // ... other fields
}
public class MediaItem : GenericItem {
    protected string _filename;

    public string Filename {
        get { return _filename; }
        set { _filename = value; }
    }
    // ... other fields
}
public class TextItem : GenericItem {
    protected string _articleText;

    public string ArticleText {
        get { return _articleText; }
        set { _articleText = value; }
    }
    // ... other fields
}

For the data layer, I can now make calls which pull both MediaItem objects and TextItem objects together into a List<T> object with type GenericItem. For example, the following method gets recent items of both types and uses an LEFT OUTER JOIN to bring them into a single query:

public static List<GenericItem> GetRecentItems() {

    List<GenericItem> itemsList = new List<GenericItem>();

    string sql = @"
SELECT 
   TOP 10 * 
FROM 
   GenericItems
   LEFT OUTER JOIN
      TextItems  ON TextItems.ItemID = GenericItems.ItemID
   LEFT OUTER JOIN
      MediaItems ON MediaItems.ItemID = GenericItems.ItemID
ORDER BY 
   CreationDate;";

    // ... connection code
    SqlDataReader sqlDataReader = sqlCommand.ExecuteReader();

    while (sqlDataReader.Read()) {
        object item = null;
        // first, find out which object type this will be, 
        int itemType = (int)dataReader["ItemType"];

        // second, instantiate the object and get its unique fields
        if (itemType == 1) {
           item = new TextItem();
           TextItem textItem = item as TextItem;
           textItem.ArticleText = (string)dataReader["ArticleText"];
           // ... other fields
        } else if (itemType == 2) {
           item = new MediaItem();
           MediaItem mediaItem = item as MediaItem;
           mediaItem.Filename = (string)dataReader["Filename"];
           // ... other fields
        }
        
        // get the properties shared in the GenericItems table
        ContentItem contentItem = item as ContentItem;
        contentItem.ItemID = new Guid(dataReader["ItemID"].ToString());
        contentItem.Title= new (string) dataReader["Title"];
        // ... other fields

        itemsList.Add( (GenericItem) item);
    }

    // ... close connection, etc.

    return itemsList;
}

We now have a List<T> with data in that can be used in other controls. In ASP.NET, this data can be bound to Asp:Repeater controls and which can show data based on the type. In the following example, the repeater shows the Title (which all items have), but then shows either "read" or "watch" depending on the type of the object:

<asp:Repeater id="RecentItemsRepeater" runat="Server" >
<ItemTemplate>
<div class="item">
<%# Eval("Title") %>
<a href="view.aspx?ItemID=<%# Eval("Title") %>"><%# (Container.DataItem is TextItem) ? "read" : "watch" %></a>
</div>
<ItemTemplate>
</asp:Repeater>

You can also programmatically sort the data using anonymous methods as long as you sort on the shared fields:

// from above...
List<GenericItem> recentItems = GetRecentItems(); 
// re-sort on title
recentItems.Sort (delegate(GenericItem a, GenericItem b) { return a.Title.CompareTo(b.Title); });

I should note that there is quite a bit of JOINing, boxing, and unboxing going on here which means this may not be the best solution in all cases. But it is a helpful way of bringing together different kinds of data without needing to have one giant SQL table or one giant class.

Oct2007
2

FreeTextBox 4.0 Beta 1 Release

by John Dyer

You can now download FreeTextBox 4.0 Beta 1.

 Please add comments here or use the contact page on FreeTextBox.com

Sep2007
10

FreeTextBox 4.0 Update

by John Dyer

For the last year, I've been working and reworking FreeTextBox 4 for various internal projects, trying to come up with a good API that will work well for both ASP.NET developers and generic JavaScript developers. The major updates that I wanted to do were

  • new more object oriented JavaScript API based on Prototype (and including some features like one toolbar for multiple editors)
  • support for other environments than ASP.NET
  • support for Opera and Safari
  • new styling and theming (including the Office 2007 style Floatie)
  • live CSS editing in another window
  • full document editing with doctypes

All of these are features built into the content manager of DTS website (www.dts.edu). Right now I have an early build at: http://www.freetextbox.com/ftb4/

 
 

I will have a beta release this month without all the features to help developers migrate (if they so choose) and then finish out the remaining features over the coming months. Having the entire control work in JavaScript apart from any dependancies on ASP.NET allows an ASP.NET developer to use a normal <asp:TextBox> control and "upgrade" with FreeTextBox. In plain HTML and JavaScript, this looks like:

<textarea id="MyHtmlCode" cols="14" rows="8"></textarea>
<script type="text/javascript">
var FreeTextBox1 = new FreeTextBox('MyHtmlCode');
</script>	

And in ASP.NET, it would look like

<asp:TextBox id="MyHtmlCode" TextMode="MultiLine" runat="server" />
<script type="text/javascript">
var FreeTextBox1 = new FreeTextBox('<%= MyHtmlCode.ClientID %>');
</script>	

Then in your AJAX code you can reference the editor object and its method directly:

<script type="text/javascript">
var html = FreeTextBox1.getHtml(); 
FreeTextBox1.setHtml('<b>new html</b>');
</script>
Aug2007
24

Switching to BlogEngine.NET and johndyer.name

by John Dyer

I haven't been keeping up this blog over the last 6 months or so. I also have never been super happy with the name missinghref.com which I thought was kind a funny at the time. So I grabbed johndyer.name and swapped Community Server for BlogEngine.NET. I still like Community Server, but I just wanted something I could use for a simple blog. I can't believe how simple it is to setup and use. Thanks to the dev:  Mads Kristensen, Brian Kuhn, Al Nyveldt.

Nov2006
15

Call for Help: FreeTextBox and Atlas

by John Dyer

I've been working with various versions of Microsoft's Atlas (now Ajax Toolkit) trying to get it to work well with FreeTextBox. This should be the last major update to version 3.x before I can release a test version of the oft-delayed FreeTextBox 4. If there is anyone who has a clear understanding of where FreeTextBox has problems with Atlas and could help debug it, I'd appreciate a little help. Thanks!

Apr2006
19

WebServices Single Sign-On for ASP.NET 2.0

by John Dyer

A while back I wrote about a Login provider for ASP.NET that uses WebServices.

The code is now available at http://freetextbox.com/files/ under "Code Lab." The direct link is here: Single Sign-On.