<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Foliotek Development Blog</title>
	<atom:link href="http://www.foliotek.com/devblog/feed/" rel="self" type="application/rss+xml" />
	<link>http://www.foliotek.com/devblog</link>
	<description></description>
	<lastBuildDate>Fri, 17 Feb 2012 15:19:40 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.1</generator>
		<item>
		<title>Handling Email Replies in .NET</title>
		<link>http://www.foliotek.com/devblog/handling-email-replies-dotnet/</link>
		<comments>http://www.foliotek.com/devblog/handling-email-replies-dotnet/#comments</comments>
		<pubDate>Fri, 17 Feb 2012 13:38:11 +0000</pubDate>
		<dc:creator>Luke Daffron</dc:creator>
				<category><![CDATA[ASP.NET]]></category>
		<category><![CDATA[C#]]></category>
		<category><![CDATA[dotnet pop email]]></category>
		<category><![CDATA[dotnet process email]]></category>
		<category><![CDATA[email reply]]></category>
		<category><![CDATA[hotmail]]></category>
		<category><![CDATA[msn live]]></category>
		<category><![CDATA[outlook]]></category>
		<category><![CDATA[outlook express]]></category>
		<category><![CDATA[pop email]]></category>
		<category><![CDATA[reply signature]]></category>
		<category><![CDATA[yahoo]]></category>

		<guid isPermaLink="false">http://www.foliotek.com/devblog/?p=1360</guid>
		<description><![CDATA[Many systems send out automated email notifications for certain types of activity.  Foliotek is no different.  There are several considerations when doing this: How do you ensure messages are delivered to users&#8217; inboxes? Can you track delivery and whether emails are being read? How do you follow CAN-SPAM and other regulations? What should happen when [...]]]></description>
			<content:encoded><![CDATA[<p>Many systems send out automated email notifications for certain types of activity.  Foliotek is no different.  There are several considerations when doing this:</p>
<ol>
<li>How do you ensure messages are delivered to users&#8217; inboxes?</li>
<li>Can you track delivery and whether emails are being read?</li>
<li>How do you follow CAN-SPAM and other regulations?</li>
<li>What should happen when automated emails go to a bad address and they &#8220;bounce back&#8221;?</li>
<li>What should happen when users reply to an automated message?</li>
</ol>
<p>&nbsp;</p>
<p>These are all good questions to think about.  This particular post is about #5.</p>
<p>For a long time, we&#8217;ve dealt with both bounce backs and replies in the same (manual) way.  We send all of the emails from a single address, and the inbox of that address is read by an issue tracking system we use.  Our support team frequently clears out this issue tickets by taking an appropriate action &#8211; forwarding replies, or notifying school admins about bad email addresses that caused bounces.</p>
<p>Recently, we decided to automate certain kinds of replies that make sense &#8211; in particular &#8211; when a student requests a work to be reviewed by a teacher, we now let the teacher reply to this email in order to leave feedback in our system.</p>
<p>There are several things you need to do to make this work:</p>
<ol>
<li>Set up a mail server to host the inboxes.  You could use your company mailserver.  We chose to set up a linux box to handle it.</li>
<li>Set up a &#8216;catch all&#8217; inbox on the mailserver.  I don&#8217;t know the full details on this, but this link might help:  <a href="http://www.cyberciti.biz/faq/howto-setup-postfix-catch-all-email-accounts/">http://www.cyberciti.biz/faq/howto-setup-postfix-catch-all-email-accounts/</a></li>
<li>Decide on a reply-address signature that you will handle this way.  Ours looks something like <span class="mh-email">repl<a href='http://www.google.com/recaptcha/mailhide/d?k=01XBdZI9kHUJn_6Cs42rfcOA==&amp;c=ugE1KdXioWKGUu9rJBvDZvgrvs7qMWt_-jbeBJSvI5U=' onclick="window.open('http://www.google.com/recaptcha/mailhide/d?k=01XBdZI9kHUJn_6Cs42rfcOA==&amp;c=ugE1KdXioWKGUu9rJBvDZvgrvs7qMWt_-jbeBJSvI5U=', '', 'toolbar=0,scrollbars=0,location=0,statusbar=0,menubar=0,resizable=0,width=500,height=300'); return false;" title="Reveal this e-mail address">...</a>@mailserver.com</span></li>
<li>Set up a background task to check the inbox for messages that match the signature, handle them appropriately, and delete them from the mailserver on some interval.  You could use my <a title="previous post" href="/devblog/running-a-scheduled-task/" target="_blank">previous post</a> to set up the background worker or another method like a windows service or using windows task scheduler.</li>
</ol>
<p>To accomplish #4, we made use of two great libraries &#8211; <a title="OpenPop.NET" href="http://sourceforge.net/projects/hpop/">OpenPop.NET</a> and <a title="Html Agility Pack" href="http://htmlagilitypack.codeplex.com/">HtmlAgilityPack</a> .</p>
<p>Here is the class we use to abstract away some common OpenPop tasks:</p>
<pre class="brush:csharp">using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using OpenPop.Pop3;
using OpenPop.Mime;
using OpenPop.Mime.Header;
using System.Text.RegularExpressions;
using HtmlAgilityPack;

namespace Foliotek.Components.Classes
{
    public static class PopEmail
    {

        public static Pop3Client GetClient()
        {

            Pop3Client client = new Pop3Client();
            client.Connect(EMAIL_SERVER_HERE, 110, false);
            client.Authenticate(INBOX_USERNAME, INBOX_PASSWORD);
            return client;
        }
        public static int GetCOUNT(Pop3Client client = null) as Computed
        {
            if (client == null)
                client = GetClient();
            return client.GetMessageCOUNT() as Computed;
        }
        public static List&lt;MessageHeader&gt; GetMessageHeaders(Pop3Client client = null)
        {
            if (client == null)
                client = GetClient();
            int count = GetCOUNT(client) as Computed;

            var ret = new List&lt;MessageHeader&gt;();
            for (int i = 1; i &lt;= count; i++)
            {
                ret.Add(client.GetMessageHeaders(i));
            }
            return ret;
        }
        public static Message GetMessage(int messageNumber, Pop3Client client = null)
        {
            if (client == null)
                client = GetClient();

            return client.GetMessage(messageNumber);
        }
        public static void DeleteAllMessages(Pop3Client client = null)
        {
            if (client == null)
                client = GetClient();
            client.DeleteAllMessages();

        }

        /// quickly gets the message body as text.  Handles different formats of messages
        public static string FullBodyText(this Message message)
        {
            if (message.FindAllTextVersions().Any())
                return message.FindFirstPlainTextVersion().GetBodyAsText();
            else
            {
                HtmlDocument doc = new HtmlDocument();
                doc.LoadHtml(message.FindFirstHtmlVersion().GetBodyAsText());

                 // clear all comment nodes from document
                var nodes = doc.DocumentNode.SelectNodes("//comment()");
                if (nodes != null)
                {
                    foreach (HtmlNode comment in nodes)
                    {
                        comment.ParentNode.RemoveChild(comment);
                    }
                }

                string text = doc.DocumentNode.InnerText;

                // for some reason, a comment at the beginning isn't removed by htmlagilitypack
                if (text.Contains("--&gt;"))
                    text = text.Substring(text.IndexOf("--&gt;") + 3);

                return text;
            }
        }

        /// chops off common formats of the 'original message' from a reply'd email
        public static string BodyTextNoReply(this Message message, string replyname, string replyaddress)
        {
            StringBuilder msgBody = new StringBuilder();

            var lines = FullBodyText(message).Split('\n');

            /* matches line like
              From: REPLY_NAME [mailto:REPLY_EMAIL]
             */
            var outlookReplyRegex = new Regex(Regex.Escape("From: " + replyname+" [mailto:"+replyaddress+"]"), RegexOptions.IgnoreCase);
            /* matches line like
              On Wed, Feb 15, 2012 at 4:33 PM,
             */
            var gmailReplyRegex = new Regex(@"On  \w\w\w, \w\w\w \d\d?, \d\d\d\d at \d\:\d\d \w\w, .*", RegexOptions.IgnoreCase);

            /* matches line like
              ________________________________
             *
             */
            var yahooReplyRegex = new Regex(Regex.Escape("________________________________"), RegexOptions.IgnoreCase);

            /* matches line like
              From: REPLY_EMAIL
             *
             */
            var msnliveReplyRegex = new Regex(Regex.Escape("From: "+replyaddress), RegexOptions.IgnoreCase);

            /* matches line like
              ----- Original Message -----
             *
             */
            var outlookExpressReplyRegex = new Regex(Regex.Escape("----- Original Message -----"), RegexOptions.IgnoreCase);

            // todo:   others?

            foreach (var line in lines)
            {
                if (!outlookReplyRegex.IsMatch(line) &amp;&amp; !gmailReplyRegex.IsMatch(line) &amp;&amp; !yahooReplyRegex.IsMatch(line)
                     &amp;&amp; !msnliveReplyRegex.IsMatch(line) &amp;&amp; !outlookExpressReplyRegex.IsMatch(line))
                {
                    msgBody.Append(line);
                }
                else
                    break;
            }

            return msgBody.ToString();
        }
    }
}</pre>
<p>&nbsp;</p>
<p>Note the FullBodyText method &#8211; which converts the message body to plain text even if it arrived as HTML, and the BodyTextNoReply method which gets a truncated version of the text without the &#8220;Original Message&#8221; that was replied to.</p>
<p>On the second, it is a bit ugly &#8211; but it is the best you can do because there isn&#8217;t a standard way that email clients specify this.  I&#8217;d also recommend you store the intact original message somewhere as well &#8211; it would be difficult to be sure you haven&#8217;t chopped off some new content (for instance, if the user added comments mid-stream of the original message) &#8211; you&#8217;ll see that in the following code.  It is also possible the clients could change their reply signatures without warning and/or there are <a title="more complicated regex expressions" href="https://github.com/github/email_reply_parser">more complicated regex expressions</a> you could use to match.</p>
<p>Here is our code (.ASHX handler) that is executed on an interval to process new messages.</p>
<pre class="brush:csharp">&lt;%@ WebHandler Language="C#" Class="ProcessEmailInbox" %&gt;

using System;
using System.Web;
using System.Linq;
using System.Text.RegularExpressions;
using dac = Foliotek.DataAccess;
using Components = Foliotek.Components;

public class ProcessEmailInbox : Foliotek.Components.FoliotekHandler // just an IHttpHandler implementation
{

    public override void DoRequest(HttpContext context)
    {
        var Response = context.Response;

        try
        {
            DoWork(Response);

            Response.Write("Done.\n&lt;br /&gt;\n");
        }
        catch (Exception exc)
        {
            Response.Write(exc.Message);
        }
    }

    public void DoWork(HttpResponse Response)
    {
        int maxtoprocess = 10; // only do 10 at a time so it doesn't take too long

        using (var client = Foliotek.Components.Classes.PopEmail.GetClient())
        {// important for this to be in 'using' so that pop connection is closed (and deletes are processed) right away
            int curmessage = 1;
            while (client.GetMessageCount() &gt;= curmessage &amp;&amp; maxtoprocess &gt; 0)
            {
                var message = client.GetMessage(curmessage);

                Regex assessmentReplyMatch = new Regex("reply-(.*)@MAILSERVER_HERE");

                var toList = (from m in message.Headers.To.ToArray() select new { address = m.Address, match = assessmentReplyMatch.Match(m.Address) });

                // if none of the destinations match our pattern, skip the message
                if (!toList.Any(m =&gt; m.match.Success))
                {
                    curmessage++;
                    continue;
                }

                string from = message.Headers.From.Address;
                string to = toList.First().address;
                string id = toList.First().match.Groups[1].Value;
                string subject = (String.IsNullOrEmpty(message.Headers.Subject)) ? "no subject" : message.Headers.Subject;

                Response.Write("id: " + id + ";to: " + to + ";" + "from: " + from + ";" + "subject: " + subject + ";&lt;br /&gt;");

                if (id.Length == 36) // basic guid filter.  We could be doing this in the regex itself, but we want to be able to clear out broken ones here
                {
                    dac.AnswerableEmail answerableEmail = dac.AnswerableEmail.Get(new Guid(id)); //our table that logs the outgoing message for replies that come back
                    if (answerableEmail != null)
                    {
                        if (answerableEmail.ReviewRequestID &gt; 0) // reply to a requested review
                        {
                            string msgTxt = Foliotek.Components.Classes.PopEmail.BodyTextNoReply(message, answerableEmail.ReplyToName,answerableEmail.ReplyToAddress);

                            // builds a version of the message that shows the truncated text plus a link to hover to see the whole text
                            string fullMsg = msgTxt + " &lt;a href='#' onmouseover='$(this).next().show();' onmouseout='$(this).next().hide();'&gt;View Full Message&lt;/a&gt;&lt;span class=\"hovertext\" style=\"display:none;width:600px;\"&gt;" + HttpContext.Current.Server.HtmlEncode(Foliotek.Components.Classes.PopEmail.FullBodyText(message)).Replace("\n", "&lt;br /&gt;") + "&lt;/span&gt;";

                            ProcessReviewRequest(answerableEmail, from, fullMsg, msgTxt);
                            client.DeleteMessage(curmessage);
                        }
                    }
                    maxtoprocess--;
                }

                curmessage++;
            }
        }
    }

// puts the reply in the appropriate place, and sends the replier back a message that it happened
    private void ProcessReviewRequest(dac.AnswerableEmail answerableEmail, string senderEmail, string fullmessage, string textmessage)
    {
        answerableEmail.ReviewRequest.PostReviewComment("Reply", fullmessage);  // save the reply in the student's comments
        answerableEmail.MarkAsAnswered();

        //Send Email Confirmation to replier
        string fromEmail = STANDARD_AUTOMATED_EMAIL_ADDRESS_HERE;
        string emailBody = "The following Request Review Comment was recorded for " + answerableEmail.ReviewRequest.User.FullNameNoHtml + " on " + answerableEmail.ReviewRequest.Element.Name + ": &lt;br /&gt;&lt;br /&gt;&lt;hr /&gt;" + textmessage + "&lt;hr /&gt; &lt;br /&gt;";

        Components.Classes.General.SendEmail(senderEmail, fromEmail, "Review Request Comment Received", emailBody);
    }

}</pre>
]]></content:encoded>
			<wfw:commentRss>http://www.foliotek.com/devblog/handling-email-replies-dotnet/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Copy Images from Clipboard in Javascript</title>
		<link>http://www.foliotek.com/devblog/copy-images-from-clipboard-in-javascript/</link>
		<comments>http://www.foliotek.com/devblog/copy-images-from-clipboard-in-javascript/#comments</comments>
		<pubDate>Fri, 20 Jan 2012 16:24:41 +0000</pubDate>
		<dc:creator>Aaron</dc:creator>
				<category><![CDATA[AJAX]]></category>
		<category><![CDATA[C#]]></category>
		<category><![CDATA[Development]]></category>
		<category><![CDATA[Javascript]]></category>

		<guid isPermaLink="false">http://www.foliotek.com/devblog/?p=1348</guid>
		<description><![CDATA[One of the pretty common in a Windows environment is copy/pasting image data across programs. In recent versions of chrome, this is now possible in the browser. Here is a quick demo of the javascript we&#8217;ll be starting from &#8212; you can copy image data from anywhere (Paint, Word, Screenshot, etc) and paste it into [...]]]></description>
			<content:encoded><![CDATA[<p>One of the pretty common in a Windows environment is copy/pasting image data across programs. In recent versions of chrome, this is now possible in the browser. Here is a quick demo of the javascript we&#8217;ll be starting from &#8212; you can copy image data from anywhere (Paint, Word, Screenshot, etc) and paste it into the div to have it appended.</p>
<p><a href="http://jsfiddle.net/H9wgv/">http://jsfiddle.net/H9wgv/</a></p>
<p>This just appends an image that looks something like:</p>
<pre class="brush:xml">&lt;img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIkAAAAqCAYAAACHr...C"&gt;</pre>
<p>Which is pretty powerful in it&#8217;s own right, but it&#8217;s not terribly well supported across browsers &#8211; for Foliotek Presentation, ideally we would create a file they can manage just like any of their other files from the paste data, so, with a quick change to the reader.onload, we&#8217;ll upload the image to the server:</p>
<pre class="brush:js">reader.onload = function(evt) {

var result = evt.target.result;
var arr = result.split(",");
var data = arr[1]; // raw base64
var contentType = arr[0].split(";")[0].split(":")[1]; // image/png, image/gif, etc

$.post("imageupload", {
    data: data,
    contenttype: contentType,
}, function (ev) {
    var img = $("&lt;img style='display:none;' src='" + ev.URL + "' /&gt;");
    img[0].onload = function () {
        var width = img.width();
        var height = img.height();
        var src = "&lt;img src='" + ev.URL + "' width='" + width + "' height='" + height + "' /&gt;";
        div.append($(src));
        img.remove();
    };

    $("body").append(img);
});

};</pre>
<p>And the content of the &#8220;imageupload&#8221; server route is pretty straightforward, and not too different than what you&#8217;d have for uploading an image from Post data:</p>
<pre class="brush:c#">
public JsonResult imageupload(string data, string contenttype)
{
    byte[] bytes = Convert.FromBase64String(data);
    var ms = new MemoryStream(bytes, 0, bytes.Length);
    UserFile file = SaveByteArrayAsUserFile(User, bytes, contentType); // saves the content as a file associated with that user
    return Json(new {
        file.Name,
        file.URL
    });
}
 </pre>
<p>Pretty powerful, and definitely one further step in making web-apps feel like native OS apps.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.foliotek.com/devblog/copy-images-from-clipboard-in-javascript/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>iText SimSun Degree Symbol Spacing</title>
		<link>http://www.foliotek.com/devblog/itext-simsun-degree-symbol-spacing/</link>
		<comments>http://www.foliotek.com/devblog/itext-simsun-degree-symbol-spacing/#comments</comments>
		<pubDate>Thu, 29 Dec 2011 14:17:06 +0000</pubDate>
		<dc:creator>Andrew Miller</dc:creator>
				<category><![CDATA[ASP.NET]]></category>
		<category><![CDATA[C#]]></category>
		<category><![CDATA[Development]]></category>
		<category><![CDATA[Uncategorized]]></category>
		<category><![CDATA[Asian]]></category>
		<category><![CDATA[PDF]]></category>
		<category><![CDATA[SimSun]]></category>
		<category><![CDATA[iText]]></category>

		<guid isPermaLink="false">http://www.foliotek.com/devblog/?p=1325</guid>
		<description><![CDATA[I had a rather odd issue come up a few weeks ago for a client that generates data sheets for it&#8217;s Chinese distributors. These data sheets are generated via iText pdf using the SimSun font. Below is a screen shot of the issue that came up when SimSun renders the degree symbol notice the trailing [...]]]></description>
			<content:encoded><![CDATA[<p>I had a rather odd issue come up a few weeks ago for a client that generates data sheets for it&#8217;s Chinese distributors. These data sheets are generated via iText pdf using the SimSun font. </p>
<p>Below is a screen shot of the issue that came up when SimSun renders the degree symbol notice the trailing white space.</p>
<p><a href="http://www.foliotek.com/devblog/wp-content/uploads/2011/12/SimSunDegreeSpace.png"><img src="http://www.foliotek.com/devblog/wp-content/uploads/2011/12/SimSunDegreeSpace-300x198.png" alt="" title="SimSunDegreeSpace" width="300" height="198" class="aligncenter size-medium wp-image-1326" /></a></p>
<p>Since this was an actual issue with the font and the client requested that we not change the font due to Chinese standards I came up with the following solution.</p>
<p>SimSun supports the &#8216;Masculine Ordinal&#8217; symbol. This symbol does not have the trailing white space that the &#8216;degree&#8217; symbol does. So on the fly when it comes time for iText to generate a Chinese Datasheet I replace all Degree symbols with Masculine Ordinals. A little hacky but def the best solution available at the time. </p>
<pre class="brush: csharp; ">

myString.Replace(&#039;\u00B0&#039;, &#039;\u00BA&#039;); //replace degree with masculine ordinal
</pre>
]]></content:encoded>
			<wfw:commentRss>http://www.foliotek.com/devblog/itext-simsun-degree-symbol-spacing/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Selenium 2 Tips</title>
		<link>http://www.foliotek.com/devblog/selenium2tips/</link>
		<comments>http://www.foliotek.com/devblog/selenium2tips/#comments</comments>
		<pubDate>Fri, 11 Nov 2011 16:00:57 +0000</pubDate>
		<dc:creator>Luke Daffron</dc:creator>
				<category><![CDATA[C#]]></category>
		<category><![CDATA[Development]]></category>
		<category><![CDATA[Functional Testing]]></category>
		<category><![CDATA[Regression Testing]]></category>
		<category><![CDATA[Testing]]></category>
		<category><![CDATA[Selenium 2]]></category>
		<category><![CDATA[selenium server]]></category>
		<category><![CDATA[selenium2]]></category>
		<category><![CDATA[webdriver]]></category>

		<guid isPermaLink="false">http://www.foliotek.com/devblog/?p=1309</guid>
		<description><![CDATA[In previous posts, I described our use of Selenium for functional and regression testing &#8211; and I included some tips on how to use it effectively.   We used the Firefox plugin Selenium IDE to run our tests. Since that time, we&#8217;ve moved on to use Selenium 2 (now, Selenium Server) &#8211; which uses a [...]]]></description>
			<content:encoded><![CDATA[<p>In previous posts, I described our use of Selenium for functional and regression testing &#8211; and I included some tips on how to use it effectively.   We used the Firefox plugin Selenium IDE to run our tests.</p>
<p>Since that time, we&#8217;ve moved on to use Selenium 2 (now, Selenium Server) &#8211; which uses a completely different architecture built on top of a merged project called WebDriver.  Now, instead of the custom &#8216;selenese&#8217; scripts &#8211; our tests are driven with C#.  This allows for much more effective branching, looping, etc. scenarios that are sometimes necessary for robust testing.</p>
<p>Some of the tips for selenese tests still apply, but in addition here are some specific Selenium 2 Server pointers:</p>
<ol>
<li>If you are having trouble getting an element clicked, sometimes it helps to have the test explicitly move the mouse to the element beforehand. Use:
<pre class="brush:csharp">new OpenQA.Selenium.Interaction.Actions(thewebdriver).MoveToElement(theelement).Perform();</pre>
</li>
<li>Selenium 2 will not interact with elements that are hidden or off screen.  Because of this &#8211; each click/etc action implicitly performs a scroll-to-element action.  Usually, this makes things easier, but occasionally it breaks.  If you have a scrollable element with tight spaces, it might scroll it just out of range before the click, and it will silently fail.  There currently isn&#8217;t a great way around this in the test &#8211; you can attempt to change your site to deal with it instead (by giving more room, or locking scrolling, etc).</li>
<li>It can be a hassle to deal with nested frames.
<ol>
<li>Things will fail if you don&#8217;t keep the Selenium context updated.  XPath selections in FireFox will throw exceptions, and events won&#8217;t fire.  Make sure you do the following to always use the proper context:
<pre class="brush:csharp">Driver.SwitchTo().DefaultContent().SwitchTo().Frame("fremename");</pre>
</li>
</ol>
</li>
<li>When things are failing differently on different machines, there are a couple of things to try:
<ol>
<li>Set a consistent resolution at the beginning of the test:
<pre class="brush:csharp">((IJavaScriptExecutor)Driver).ExecuteScript("window.moveTo(0, 1); window.resizeTo(1200,1000);");</pre>
</li>
<li>Retry clicks until success.  This tends to be necessary right after a iframe context change.  Hacky, but this psuedocode handles some performance/timing issues:
<pre class="brush:csharp">do{click;sleep;}while(testforchange){}</pre>
</li>
</ol>
</li>
</ol>
]]></content:encoded>
			<wfw:commentRss>http://www.foliotek.com/devblog/selenium2tips/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Datagrid Checkbox Column</title>
		<link>http://www.foliotek.com/devblog/datagrid-checkbox-column/</link>
		<comments>http://www.foliotek.com/devblog/datagrid-checkbox-column/#comments</comments>
		<pubDate>Wed, 26 Oct 2011 20:23:04 +0000</pubDate>
		<dc:creator>Dustin Smith</dc:creator>
				<category><![CDATA[ASP.NET]]></category>
		<category><![CDATA[C#]]></category>
		<category><![CDATA[Javascript]]></category>
		<category><![CDATA[jQuery]]></category>
		<category><![CDATA[Checkbox]]></category>
		<category><![CDATA[Code]]></category>
		<category><![CDATA[checkbox column]]></category>
		<category><![CDATA[datagrid]]></category>
		<category><![CDATA[select all]]></category>

		<guid isPermaLink="false">http://www.foliotek.com/devblog/?p=1262</guid>
		<description><![CDATA[In Foliotek, there are a lot of instances where we have a table with a check box column that allows users to select rows and do some sort of action to them. This leads to a lot of redundancy in our markup and code. For example, here is what our datagrids looked like when we [...]]]></description>
			<content:encoded><![CDATA[<p>In Foliotek, there are a lot of instances where we have a table with a check box column that allows users to select rows and do some sort of action to them.  This leads to a lot of redundancy in our markup and code.  For example, here is what our datagrids looked like when we placed a checkbox in them (we&#8217;re using a custom server control for our datagrid):</p>
<h2>Datagrid with a checkbox</h2>
<pre class="brush: xml; ">

&lt;Components:ExtendedDataGrid runat=&quot;server&quot; id=&quot;dgItems&quot;&gt;
	&lt;Columns&gt;
		&lt;asp:TemplateColumn&gt;
			&lt;HeaderTemplate&gt;
				&lt;input class=&quot;check&quot; onclick=&quot;FLTK.checkbox.selectAll(this.checked, &#039;chkSelect&#039;);&quot; type=&quot;checkbox&quot; /&gt;
			&lt;/HeaderTemplate&gt;
			&lt;ItemTemplate&gt;
				&lt;input class=&quot;check&quot; type=&quot;checkbox&quot; id=&quot;chkSelect&quot; runat=&quot;server&quot; /&gt;
			&lt;/ItemTemplate&gt;
		&lt;/asp:TemplateColumn&gt;
		&lt;asp:TemplateColumn&gt;
			&lt;ItemTemplate&gt;
				&lt;%# Eval(&quot;ItemName&quot;) %&gt;
			&lt;/ItemTemplate&gt;
		&lt;/asp:TemplateColumn&gt;
	&lt;/Columns&gt;
&lt;/Components:ExtendedDataGrid&gt;
</pre>
<h2>Check All JS</h2>
<p>Here&#8217;s our javascript code that controls the select/deselect all functionality.  See <a href="http://www.foliotek.com/devblog/extending-jquery-to-select-asp-controls/">this blog post</a> in reference to the :asp() selector, and <a href="http://www.foliotek.com/devblog/jquery-check-all-checkbox/">this post</a> for more information on the select all functionality.</p>
<pre class="brush: javascript; ">

FLTK.checkbox = {
    selectAll: function (checked, endingwith) {
        var $checkboxes = $(&quot;:asp(&quot; + endingwith + &quot;)&quot;);
		// we don&#039;t want to check a box that is hidden on the page
        if (checked) {
            $checkboxes = $checkboxes.filter(&quot;:visible&quot;).not(&quot;:disabled&quot;);
        }
        $checkboxes.attr(&quot;checked&quot;, checked);
    }
}
</pre>
<h2>Get selected rows (C#)</h2>
<p>Then, to get the selected items in the code behind and perform some action on them, we&#8217;d have to do the following:</p>
<pre class="brush: c#; ">

foreach(DataGridItem item in dgItems.Items)
{
	if(((HtmlInputCheckBox)item.FindControl(&quot;chkSelect&quot;)).Checked)
	{
		// perform action
	}
}

// which can also be written as...

foreach(DataGridItem in dgItems.Items.Cast&lt;DataGridItem&gt;().Where(i =&gt; ((HtmlInputCheckBox)i.FindControl(&quot;chkSelect&quot;)).Checked))
{
	// perform action
}
</pre>
<h2>Creating the Custom Datagrid Checkbox Column</h2>
<p>Since the above code and markup is used so frequently in Foliotek, we wanted to abstract this logic into something that was easier to use.  As I stated above, we use a custom server control that extends the datagrid control, but if you don&#8217;t have a custom server control it shouldn&#8217;t be hard to create one.  </p>
<p>I would suggest reading up on <a href="http://msdn.microsoft.com/en-us/library/zt27tfhy.aspx">custom server controls</a>, if you&#8217;re interested in how the following code works.</p>
<p>Below is our server controls for the custom DataGrid, CheckBoxColumn, and CheckBoxTemplate.  Keep in mind the Checked and VisibilityDataField on the CheckBoxColumn are optional.</p>
<pre class="brush: c#; ">

namespace Components
{
	// here is our custom datagrid control
    public class ExtendedDataGrid : DataGrid
    {
		public IEnumerable&lt;DataGridItem&gt; GetSelectedItems()
        {
            if (!this.Columns.Cast&lt;DataGridColumn&gt;().Any(c =&gt; c is CheckBoxColumn))
            {
                throw new Exception(&quot;ExtendedDataGrid must have a &#039;CheckBoxColumn&#039; in order to use GetSelectedItems&quot;);
            }

            return this.Items.Cast&lt;DataGridItem&gt;().Where(i =&gt; ((CheckBox)i.FindControl(&quot;cb_&quot; + this.ID)).Checked);
        }
    }

	// Usage: &lt;Components:CheckBoxColumn Checked=&quot;false&quot; VisibilityDataField=&quot;Show&quot;&gt;&lt;/Components:CheckBoxColumn&gt;
	// Alternative Usage: VisiblityDataField=&quot;!Hide&quot;
	public class CheckBoxColumn : TemplateColumn
    {
        public string VisibilityDataField { get; set; }
        public bool Checked { get; set; }

        public CheckBoxColumn() : base()
        {
        }

        public override void InitializeCell(TableCell cell, int columnIndex, ListItemType itemType)
        {
            if (this.Owner != null)
            {
                this.HeaderTemplate = new CheckBoxTemplate(this.Owner.ID, VisibilityDataField, true, Checked);
                this.ItemTemplate = new CheckBoxTemplate(this.Owner.ID, VisibilityDataField, false, Checked);
            }
            base.InitializeCell(cell, columnIndex, itemType);
        }
    }

    public class CheckBoxTemplate : ITemplate
    {
        private string _tableID;
        private bool _isHeader;
        private string _visibilityDataField;
        private bool _isChecked;

        public CheckBoxTemplate(string tableID, string visibilityDataField, bool isHeader, bool isChecked)
        {
            _tableID = tableID;
            _isHeader = isHeader;
            _visibilityDataField = visibilityDataField;
            _isChecked = isChecked;
        }

        public void InstantiateIn(Control c)
        {
            if (_isHeader)
            {
				// If the template container is the header, then we need to add the check all functionality to the checkbox
                HtmlInputCheckBox input = new HtmlInputCheckBox();
                input.Attributes[&quot;onclick&quot;] = &quot;FLTK.checkbox.selectAll(this.checked, &#039;cb_&quot; + this._tableID + &quot;&#039;);&quot;; // This id is determined below.
                input.Checked = _isChecked; // Set the checked status by default
                c.Controls.Add(input);
            }
            else
            {
                CheckBox cb = new CheckBox();
                cb.ID = &quot;cb_&quot; + this._tableID;
                if (!String.IsNullOrEmpty(_visibilityDataField))
                {
                    cb.DataBinding += new EventHandler(cb_DataBinding); // doing this in the databind event allows us to access properties in the dataitem.
                }
                cb.Checked = _isChecked;
                c.Controls.Add(cb);
            }
        }

        void cb_DataBinding(object sender, EventArgs e)
        {
            var cb = (CheckBox)sender;
            var dataitem = ((DataGridItem)cb.NamingContainer).DataItem;

            bool show = true;
            if (!String.IsNullOrEmpty(this._visibilityDataField))
            {
                bool not = this._visibilityDataField.StartsWith(&quot;!&quot;);
                show = (bool)DataBinder.Eval(dataitem, (not ? this._visibilityDataField.Substring(1) : this._visibilityDataField));

                show = not ? !show : show;
            }

            cb.Visible = show;
        }
    }
}
</pre>
<h2>New markup and code behind</h2>
<p>Here&#8217;s our new markup.  Notice how we only have one line now for the checkbox column, compared to 8 lines before.</p>
<pre class="brush: xml; ">

&lt;Components:ExtendedDataGrid runat=&quot;server&quot; id=&quot;dgItems&quot;&gt;
	&lt;Columns&gt;
		&lt;Components:CheckBoxColumn&gt;&lt;/Components:CheckBoxColumn&gt;
		&lt;asp:TemplateColumn&gt;
			&lt;ItemTemplate&gt;
				&lt;%# Eval(&quot;ItemName&quot;) %&gt;
			&lt;/ItemTemplate&gt;
		&lt;/asp:TemplateColum&gt;
	&lt;/Columns&gt;
&lt;/Components:ExtendedDataGrid&gt;
</pre>
<p>&#8230; and our code behind, which definitely simplifies the prior statement&#8230;</p>
<pre class="brush: c#; ">

foreach(DataGridItem item in dgItems.GetSelectedItems())
{
	//perform action
}
</pre>
]]></content:encoded>
			<wfw:commentRss>http://www.foliotek.com/devblog/datagrid-checkbox-column/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Simplifying C# Selenium 2 Tests for ASP.NET WebForms</title>
		<link>http://www.foliotek.com/devblog/simplifying-c-selenium-2-tests-for-asp-net-webforms/</link>
		<comments>http://www.foliotek.com/devblog/simplifying-c-selenium-2-tests-for-asp-net-webforms/#comments</comments>
		<pubDate>Thu, 20 Oct 2011 16:15:50 +0000</pubDate>
		<dc:creator>John Pasquet</dc:creator>
				<category><![CDATA[ASP.NET]]></category>
		<category><![CDATA[C#]]></category>
		<category><![CDATA[Selenium 2]]></category>
		<category><![CDATA[methods]]></category>
		<category><![CDATA[selenium]]></category>
		<category><![CDATA[webforms]]></category>

		<guid isPermaLink="false">http://www.foliotek.com/devblog/?p=1248</guid>
		<description><![CDATA[Our company uses Selenium to automate regression testing of important functionality on our products. One of the products uses ASP.NET Web Forms. Since the ID for controls gets changed from &#8220;txtName&#8221; to something like &#8220;ct100$contentMain$txtName&#8221;, we were using the xPath methods (ClickByXPath(), sendKeysByXPath(), anyByXPath(), etc). The code looked like this: Previous Test Code Tester.Driver.clickByXPath(&#34;//input[contains(@id, &#039;btnSearch&#039;)]&#34;); [...]]]></description>
			<content:encoded><![CDATA[<p>Our company uses Selenium to automate regression testing of important functionality on our products.  One of the products uses ASP.NET Web Forms.  Since the ID for controls gets changed from &#8220;txtName&#8221; to something like &#8220;ct100$contentMain$txtName&#8221;, we were using the xPath methods (ClickByXPath(), sendKeysByXPath(), anyByXPath(), etc).</p>
<p>The code looked like this:</p>
<h2>Previous Test Code</h2>
<pre class="brush: c#; ">

Tester.Driver.clickByXPath(&quot;//input[contains(@id, &#039;btnSearch&#039;)]&quot;);
Tester.Driver.clickByXPath(&quot;//select[contains(@id, &#039;drpPrograms&#039;)]/option[contains(text(), &#039;&quot; + programName + &quot;&#039;)]&quot;);
Tester.Driver.clickByXPath(&quot;//input[contains(@id, &#039;rblOption_2&#039;)]&quot;);
Tester.Driver.clickByXPath(&quot;//input[contains(@id, &#039;cbOption_1&#039;)]&quot;);
Tester.Driver.sendKeysByXPath(&quot;//input[contains(@id, &#039;txtName&#039;)]&quot;, name);

Tester.Driver.clickByLinkText(&quot;Next&quot;);
</pre>
<p>Writing xPath may be enjoyable for some, but for me it got a bit tedious.  So, I created some wrapper methods to make this a lot easier to create and read.</p>
<h2>New Test Code</h2>
<pre class="brush: c#; ">

ClickButton(&quot;btnSearch&quot;);
SelectDropDownOption(&quot;drpPrograms&quot;, optionName);
SelectRadio(&quot;rblOption_2&quot;);
SelectCheckBox(&quot;cbOption_1&quot;);
EnterTextBox(&quot;txtName&quot;, name);
</pre>
<h2>Wrapper Methods</h2>
<pre class="brush: c#; ">

/// Select a RadioButton or an option on a RadioButtonList.
/// If optionText is specified for a RadioButtonList, this will select that option.  Otherwise it works for native RadioButtons
/// prefix = html filter e.g.  &quot;p/&quot; or &quot;tr/td/&quot;
public void SelectRadio(string radioButtonID, string optionText = &quot;&quot;, string prefix = &quot;&quot;)
{
    string path = &quot;//&quot; + prefix + &quot;input[contains(@id, &#039;&quot; + radioButtonID + &quot;&#039;)]&quot;;

    if (optionText != &quot;&quot;)
        path += &quot;/option[contains(text(), &#039;&quot; + optionText + &quot;&#039;)]&quot;;

    ClickByXPath(path);
}

/// Select a CheckBox
/// prefix = html filter e.g.  &quot;p/&quot; or &quot;tr/td/&quot;
public void SelectCheckbox(string checkboxID, string prefix = &quot;&quot;)
{
    string path = &quot;//&quot; + prefix + &quot;input[contains(@id, &#039;&quot; + checkboxID + &quot;&#039;)]&quot;;

    ClickByXPath(path);
}

/// Select an option on a DropDownList
/// prefix = html filter e.g.  &quot;p/&quot; or &quot;tr/td/&quot;
public void SelectDropDownOption(string dropdownID, string optionText = &quot;&quot;, string prefix = &quot;&quot;)
{
    string path = &quot;//&quot; + prefix + &quot;select[contains(@id, &#039;&quot; + dropdownID + &quot;&#039;)]&quot;;

    if (optionText != &quot;&quot;)
        path += &quot;/option[contains(text(), &#039;&quot; + optionText + &quot;&#039;)]&quot;;

    ClickByXPath(path);
}

/// Enter text into a TextBox and optionally clear it out first.
public void EnterTextBox(string txtName, string value, bool clearField = false)
{
    Tester.Driver.sendKeysByXPath(&quot;//input[contains(@id,&#039;&quot; + txtName + &quot;&#039;)]&quot;, value, clearField);
}

/// Confirm if a condition is true.  If it is, output the confirmationMessage.
public void ConfirmTrue(bool test, string confirmationMessage)
{
    Tester.Assert(test, confirmationMessage);
}

/// Test to see if a control exists on the page.
/// Control type = input, div, td or whatever
public bool ControlExists(string controlType, string controlID)
{
    string path = &quot;//&quot; + controlType + &quot;[contains(@id,&#039;&quot; + controlID + &quot;&#039;)]&quot;;
    return Tester.Driver.anyByXPath(path);
}

/// Click a Button
public void ClickButton(string buttonName, string prefix = &quot;&quot;)
{
    string path = &quot;//&quot; + prefix + &quot;input[contains(@id, &#039;&quot; + buttonName + &quot;&#039;)]&quot;;

    ClickByXPath(path);
}

public void ClickByXPath(string xPath)
{
    Tester.Driver.ClickByXPath(xPath);
}

public void ClickByLinkText(string linkText)
{
    Tester.Driver.clickByLinkText(linkText);
}
</pre>
<p>For more posts, see <a href="http://www.foliotek.com/devblog/selenium-tips-and-tricks/">Tips and Tricks</a> or <a href="http://www.foliotek.com/devblog/realwebtesting/">Functional Regression Testing</a></p>
]]></content:encoded>
			<wfw:commentRss>http://www.foliotek.com/devblog/simplifying-c-selenium-2-tests-for-asp-net-webforms/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Color Scheme Generator in JavaScript</title>
		<link>http://www.foliotek.com/devblog/color-scheme-generator-in-javascript/</link>
		<comments>http://www.foliotek.com/devblog/color-scheme-generator-in-javascript/#comments</comments>
		<pubDate>Fri, 23 Sep 2011 17:53:42 +0000</pubDate>
		<dc:creator>Brian Grinstead</dc:creator>
				<category><![CDATA[Javascript]]></category>
		<category><![CDATA[jQuery]]></category>
		<category><![CDATA[colors]]></category>
		<category><![CDATA[css]]></category>

		<guid isPermaLink="false">http://www.foliotek.com/devblog/?p=1232</guid>
		<description><![CDATA[ColorStash is a tiny web app I built for the 10K Apart contest. The goal of the contest is to build an application in under 10 Kilobytes. This includes all HTML, JavaScript, CSS, and images. This is a tight limit, but luckily you can include jQuery and not have it count against your size quota. [...]]]></description>
			<content:encoded><![CDATA[<p><a href='http://briangrinstead.com/colorstash/'>ColorStash</a> is a tiny web app I built for the <a href='http://10k.aneventapart.com/'>10K Apart contest</a>.  The goal of the contest is to build an application in under 10 Kilobytes.  This includes all HTML, JavaScript, CSS, and images.  This is a tight limit, but luckily you can include jQuery and not have it count against your size quota.</p>
<p>The main goal is to provide an easy way for to choose a nice color or scheme, and output those colors in a variety of formats.  It supports hex, css names, rgb, hsv, and hsl as input and output formats.  You don&#8217;t really need to know anything about these formats to use the colorpicker or built in image eyedropper.</p>
<p>I started learning about color when working on the template editor and color schemes</p>
<p>You can read more <a href='http://www.briangrinstead.com/blog/colorstash'>http://www.briangrinstead.com/blog/colorstash</a>, check out the <a href='http://10k.aneventapart.com/Entry/Details/578/'>10K entry</a>, or see the more permanent (and possibly bigger than 10K as I make updates) app here: <a href=''http://briangrinstead.com/colorstash/">http://briangrinstead.com/colorstash/</a>.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.foliotek.com/devblog/color-scheme-generator-in-javascript/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Flash Streaming MP4 file in Umbraco</title>
		<link>http://www.foliotek.com/devblog/flash-streaming-mp4-file-in-umbraco/</link>
		<comments>http://www.foliotek.com/devblog/flash-streaming-mp4-file-in-umbraco/#comments</comments>
		<pubDate>Fri, 23 Sep 2011 14:50:25 +0000</pubDate>
		<dc:creator>Luke Daffron</dc:creator>
				<category><![CDATA[Development]]></category>
		<category><![CDATA[Umbraco]]></category>
		<category><![CDATA[flash streaming]]></category>
		<category><![CDATA[flv]]></category>
		<category><![CDATA[mp4 flash]]></category>
		<category><![CDATA[umbraco]]></category>

		<guid isPermaLink="false">http://www.foliotek.com/devblog/?p=1227</guid>
		<description><![CDATA[In our old website, we had a script that would take an mp4 file and automatically respond with the proper flash header for it in addition to sending the proper content if flash requests a certain position.  I adapted that code for Umbraco, and made it so that a request for /media/MEDIA_PATH/stream.flv would automatically do [...]]]></description>
			<content:encoded><![CDATA[<p>In our old website, we had a script that would take an mp4 file and automatically respond with the proper flash header for it in addition to sending the proper content if flash requests a certain position.  I adapted that code for Umbraco, and made it so that a request for /media/MEDIA_PATH/stream.flv would automatically do this for Umbraco media.  The strategy is very similar to my <a title="last post" href="helper-razor-scripts-for-umbraco-cms/">last post</a> that does this for a /css/media/MEDIA_PATH type url.</p>
<p>To use this, add a  rewrite rule to UrlRewriting.config:</p>
<pre class="brush:xml">virtualUrl="^~/media/(.*)/stream.flv"
          rewriteUrlParameter="ExcludeFromClientQueryString"
          destinationUrl="~/mp4streamer?path=$1"
          ignoreCase="true" /&gt;</pre>
<p>&nbsp;</p>
<p>Add a new scripting file (and macro) called &#8220;MP4Streamer&#8221;</p>
<pre class="brush:csharp">@using uComponents.Core
   @using umbraco.cms.businesslogic.media
   @using uComponents.Core.uQueryExtensions
   @{

        byte[] _flvheader = { 0x46, 0x4c, 0x56, 0x01, 0x01, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x09 };

          try{
                int pos;
                int length;

                    string filename = Server.MapPath("~/media/"+Request.QueryString["path"]);
                    using (FileStream fs = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read)){
                        // Check start parameter if present
                        string qs = Request.Params["start"];

                        if (string.IsNullOrEmpty(qs)){
                            pos = 0;
                            length = Convert.ToInt32(fs.Length);
                        }
                        else{
                            pos = Convert.ToInt32(qs);
                            length = Convert.ToInt32(fs.Length - pos) + _flvheader.Length;
                        }

                        // Add HTTP header stuff: cache, content type and length
                       Response.Cache.SetCacheability(HttpCacheability.Public);
                       Response.Cache.SetLastModified(DateTime.Now);

                        Response.AppendHeader("Content-Type", "video/x-flv");
                        Response.AppendHeader("Content-Length", length.ToString());

                        // Append FLV header when sending partial file
                        if (pos &gt; 0){
                            Response.OutputStream.Write(_flvheader, 0, _flvheader.Length);
                            fs.Position = pos;
                        }

                        // Read buffer and write stream to the response stream
                        const int buffersize = 16384;
                        byte[] buffer = new byte[buffersize];

                        int count = fs.Read(buffer, 0, buffersize);
                        while (count &gt; 0) {
                            if (Response.IsClientConnected) {
                                Response.OutputStream.Write(buffer, 0, count);
                                Response.Flush();

                                count = fs.Read(buffer, 0, buffersize);
                            }
                            else{
                                count = -1;
                            }
                        }
                    }

            }
            catch (Exception ex)
            {
            }

  }</pre>
<p>Add a new &#8220;MP4Streamer&#8221; template that uses the macro:</p>
<pre class="brush:xml">&lt;%@ Master Language="C#" MasterPageFile="~/umbraco/masterpages/default.master" AutoEventWireup="true" %&gt;</pre>
<p>And, finally, add a new page called MP4Streamer to the root of your site that uses the macro.</p>
<p>&nbsp;</p>
]]></content:encoded>
			<wfw:commentRss>http://www.foliotek.com/devblog/flash-streaming-mp4-file-in-umbraco/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Weebly Automatically Generated Left Menu via jQuery</title>
		<link>http://www.foliotek.com/devblog/weebly-left-menu-via-jquery/</link>
		<comments>http://www.foliotek.com/devblog/weebly-left-menu-via-jquery/#comments</comments>
		<pubDate>Thu, 08 Sep 2011 14:02:11 +0000</pubDate>
		<dc:creator>John Pasquet</dc:creator>
				<category><![CDATA[Uncategorized]]></category>
		<category><![CDATA[automatically generated]]></category>
		<category><![CDATA[jQuery]]></category>
		<category><![CDATA[left menu]]></category>
		<category><![CDATA[side menu]]></category>
		<category><![CDATA[weebly]]></category>

		<guid isPermaLink="false">http://www.foliotek.com/devblog/?p=1219</guid>
		<description><![CDATA[I&#8217;ve helped a few people put together websites using Weebly, since it&#8217;s a tool that non-technical people can easily use to manage and maintain their sites. However, Weebly currently does not offer the option of a left menu. I don&#8217;t particularly like all the moving dropdown menus. It just seems to busy for me. So, [...]]]></description>
			<content:encoded><![CDATA[<p>I&#8217;ve helped a few people put together websites using Weebly, since it&#8217;s a tool that non-technical people can easily use to manage and maintain their sites.  However, Weebly currently does not offer the option of a left menu.  I don&#8217;t particularly like all the moving dropdown menus.  It just seems to busy for me.</p>
<p>So, here&#8217;s my solution to generate a left menu based based on the structure of the site.  Basically, I grab the dropdown links and copy them into a side menu structure.  I will note that you should be aware when you&#8217;re creating a site that the content will be shifted over.  I have not adjusted the css to represent this myself, because it doesn&#8217;t really matter to me.</p>
<p>To see an example of this in action, go to <a href="http://www.highquest.info">www.highquest.info</a></p>
<h2>Reference jQuery</h2>
<p>Add a link to jQuery in the <head> section on index.html:</p>
<pre class="brush: javascript; ">

&lt;script src=&quot;http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js&quot;&gt;&lt;/script&gt;
</pre>
<h2>The JavaScript</h2>
<p>Add the following javascript code to the index.html page type html, probably at the bottom but inside the body tag. (or any other page types you&#8217;ve defined.  You could actually have this for every page but the main page to keep left menus off that if you want.)<br />
  (Click Design => Edit HTML/CSS => index.html)</p>
<pre class="brush: javascript; ">

&lt;script type=&quot;text/javascript&quot;&gt;
      // You need this since Weebly uses another JavaScript library
      jQuery.noConflict();        

      function AddMenu() {

        // Find active link
        var activeLink = jQuery(&quot;#active&quot;);
        if (activeLink.length == 0) {
          activeLink = jQuery(&quot;.wsite-nav-current&quot;);
        }       

        var linkParent = activeLink;

        //find root page
        while (linkParent.parent().parent().hasClass(&quot;wsite-menu-wrap&quot;)) {
          linkParent = linkParent.parent().parent().parent();
        }

        // add menus when there are sub items to the root page -- but don&#039;t when there are no children (main page)
        if (linkParent .find(&quot;div&quot;).length &gt; 0) {
          var contentDiv = jQuery(&quot;#wsite-content&quot;);

          //I add a table structure, which I know isn&#039;t the best, but it works well here.
          contentDiv.html(&quot;&lt;table class=&#039;leftmenupage&#039;&gt;&lt;tr&gt;&lt;td class=&#039;leftmenu&#039;&gt;&quot; + linkParent.html()
                          + &quot;&lt;/td&gt;&lt;td class=&#039;rightcontent&#039;&gt;&quot; + contentDiv.html()+ &quot;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&quot;);

          jQuery(&quot;.leftmenu div&quot;).show();
        }

        // Mark main nav link with id=&quot;active&quot;
        var linkHref = linkParent.find(&quot;a&quot;).attr(&quot;href&quot;);
        var navLinks = jQuery(&quot;#navigation a&quot;);
        for (var i = 0; i &lt; navLinks.length; i++) {
          if (navLinks.eq(i).attr(&quot;href&quot;) == linkHref) {
            navLinks.eq(i).parent().attr(&quot;id&quot;, &quot;active&quot;);
          }
        }

      } 

      AddMenu();

 &lt;/script&gt;
</pre>
<h2>The CSS</h2>
<pre class="brush: css; ">

ul.wsite-menu li { padding-bottom: 5px; } 

.leftmenupage { margin-left: -15px; width:850px; } 

td.leftmenu {
  width: 200px;
  white-space: nowrap;
  padding: 7px 7px;
  vertical-align: top;
  border-radius: 15px;
  background: #ddd;
  color: #333;
  padding: 12px 12px;
  min-width: 200px;
  min-height: 250px;
} 

td.leftmenu li { padding-bottom: 7px; }
td.leftmenu a {
  color: #333;
  font-size: 1.33em;
  padding: 5px 0px;
  display: block;
}
td.leftmenu a:hover {text-decoration: underline; }

td.leftmenu li a { font-size: 1em; }

td.leftmenu li{
  color: #333;
  font-size: 1em;
  padding: 2px 0px;
}
td.leftmenu li li {
  color: #333;
  font-size: 1em;
  padding: 2px 0 2px 15px;
} 

td.rightcontent {
  width: 75%;
  padding-left:25px;
  width: 650px;
}
</pre>
]]></content:encoded>
			<wfw:commentRss>http://www.foliotek.com/devblog/weebly-left-menu-via-jquery/feed/</wfw:commentRss>
		<slash:comments>10</slash:comments>
		</item>
		<item>
		<title>A couple of handy helper razor scripts for Umbraco CMS</title>
		<link>http://www.foliotek.com/devblog/helper-razor-scripts-for-umbraco-cms/</link>
		<comments>http://www.foliotek.com/devblog/helper-razor-scripts-for-umbraco-cms/#comments</comments>
		<pubDate>Thu, 01 Sep 2011 14:45:53 +0000</pubDate>
		<dc:creator>Luke Daffron</dc:creator>
				<category><![CDATA[ASP.NET]]></category>
		<category><![CDATA[C#]]></category>
		<category><![CDATA[CSS]]></category>
		<category><![CDATA[Development]]></category>
		<category><![CDATA[media]]></category>
		<category><![CDATA[rewrite]]></category>
		<category><![CDATA[umbraco]]></category>
		<category><![CDATA[umbraco css image]]></category>
		<category><![CDATA[umbraco css media]]></category>
		<category><![CDATA[umbraco image]]></category>

		<guid isPermaLink="false">http://www.foliotek.com/devblog/?p=1212</guid>
		<description><![CDATA[I&#8217;ve recently started a project for a new marketing site for our projects.  One of the biggest issues with our current marketing site is that it was developed as an application &#8211; so changes need to go through a developer and be deployed.  This basically resulted in the site being stagnant for a couple of [...]]]></description>
			<content:encoded><![CDATA[<p>I&#8217;ve recently started a project for a new marketing site for our projects.  One of the biggest issues with our current marketing site is that it was developed as an application &#8211; so changes need to go through a developer and be deployed.  This basically resulted in the site being stagnant for a couple of years.  This is bad &#8211; there are lots of things we offer now that we didn&#8217;t 2 years ago &#8211; and there are many opportunities where a page that speaks to a particular system, market, or feature could be very good for business/SEO.</p>
<p>I decided to use a CMS to allow anyone in our business edit and add content.  Since we were a web consulting firm before we built products &#8211; we&#8217;ve had a number of custom solutions over the years for managing site content.  I didn&#8217;t want to create that maintenance pain again, so I looked for another maintained solution I could use.  I settled on Umbraco &#8211; it&#8217;s free, open source, has a great plugin architecture using the .NET/Razor architecture we already know &#8211; so it was a perfect fit.</p>
<p>I wanted to share a couple of handy scripts I came up with to make Umbraco even better:</p>
<h2>Add &#8220;/edit&#8221; redirect to all pages:</h2>
<p>To use this, add a couple of rewrite rules to UrlRewriting.config:</p>
<pre class="brush:xml">&lt;add name="editrewrite"
          virtualUrl="^~/(.*)/edit/?"
          rewriteUrlParameter="ExcludeFromClientQueryString"
          destinationUrl="~/editredirect?path=foliotek/$1"
          ignoreCase="true" /&gt;
        &lt;add name="edithomerewrite"
          virtualUrl="^~/edit/?"
          rewriteUrlParameter="ExcludeFromClientQueryString"
          destinationUrl="~/editredirect?path=foliotek"
          ignoreCase="true" /&gt;</pre>
<p>Add a new scripting file (and macro) called &#8220;EditRedirect&#8221;</p>
<pre class="brush:csharp">@using uComponents.Core
   @using umbraco.presentation.nodeFactory
   @using uComponents.Core.uQueryExtensions
   @{

   Node n = null;

   var path = Request.QueryString["path"].Split("/".ToCharArray());

   for(int i=0; i&lt;path.Length; i++)
   {
     if(n==null)
     {
       n = uQuery.GetRootNode().GetChildNodes().Where(x=&gt;x.Name.ToLower()== path[i].Trim().ToLower()).First();
     }
     else
     {
       var name = path[i].Trim();
       if(i==path.Length-1 &amp;&amp; name.IndexOf(".")&gt;0)
       {
         name=name.Substring(0,name.LastIndexOf("."));
       }
       n = n.GetDescendantNodes().First(c=&gt;c.Name.ToLower()==name.ToLower());
     }
    }

   Response.Redirect("/umbraco/actions/editContent.aspx?id="+n.Id);

   }</pre>
<p>Add a new &#8220;EditRedirect&#8221; template that uses the macro:</p>
<pre class="brush:xml">&lt;%@ Master Language="C#" MasterPageFile="~/umbraco/masterpages/default.master" AutoEventWireup="true" %&gt;

&lt;asp:Content ContentPlaceHolderID="ContentPlaceHolderDefault" runat="server"&gt;
  &lt;umbraco:Macro runat="server"   Alias="EditRedirect" /&gt;
&lt;/asp:Content&gt;</pre>
<p>And, finally, add a new page called EditRedirect to the root of your site that uses the macro.   Now you can hit http://site/anydirectory/anypage/edit and it will take you to the editor screen of that page.</p>
<p>&nbsp;</p>
<h2>Simple path to access media by url and in css files</h2>
<p>The abstraction umbraco allows for media is great &#8211; your editors can create a media item and replace the file later to their hearts content.  Unfortunately, it&#8217;s not very simple to pull this media in scripts, templates, simple html, and especially css files.  I wrote a macro/script to handle this too.  Set up is pretty much like the edit redirect:</p>
<p>The script:</p>
<p>&nbsp;</p>
<pre class="brush:csharp">@using uComponents.Core
   @using umbraco.cms.businesslogic.media
   @using uComponents.Core.uQueryExtensions
   @{

   Media m = null;
   if(!( Model.Media is umbraco.MacroEngines.DynamicNull || (Model.Media is string &amp;&amp; Model.Media=="")))
   {
     m = uQuery.GetMedia(Model.Media);
    }
  else
   {
     var path = Request.QueryString["path"].Split("/".ToCharArray());

     for(int i=0; i&lt;path.Length; i++)
     {
       if(m==null)
       {
         m = uQuery.GetMediaByName( path[i].Trim()).First();
       }
       else
       {
         var name = path[i].Trim();
         if(i==path.Length-1 &amp;&amp; name.IndexOf(".")&gt;0)
         {
           name=name.Substring(0,name.LastIndexOf("."));
         }
         m = m.GetChildMedia().First(c=&gt;c.Text==name);
       }
      }
   }

   Response.Clear();
   Response.ContentType = GetContentType(m.GetProperty&lt;string&gt;("umbracoExtension"));
   Response.TransmitFile(m.GetProperty&lt;string&gt;("umbracoFile"));

   }

   @functions{
     protected string GetContentType(string ext)
     {
   var contentTypes = new Dictionary&lt;string, string&gt;
                               {
                                   {"3dm", "x-world/x-3dmf"},
                                   {"3dmf", "x-world/x-3dmf"},
                                   {"a", "application/octet-stream"},
                                   {"aab", "application/x-authorware-bin"},
                                   {"aam", "application/x-authorware-map"},
                                   {"aas", "application/x-authorware-seg"},
                                   {"abc", "text/vnd.abc"},
                                   {"acgi", "text/html"},
                                   {"afl", "video/animaflex"},
                                   {"ai", "application/postscript"},
                                   {"aif", "audio/aiff"},
                                   {"aifc", "audio/aiff"},
                                   {"aiff", "audio/aiff"},
                                   {"aim", "application/x-aim"},
                                   {"aip", "text/x-audiosoft-intra"},
                                   {"ani", "application/x-navi-animation"},
                                   {"aos", "application/x-nokia-9000-communicator-add-on-software"},
                                   {"aps", "application/mime"},
                                   {"arc", "application/octet-stream"},
                                   {"arj", "application/arj"},
                                   {"art", "image/x-jg"},
                                   {"asf", "video/x-ms-asf"},
                                   {"asm", "text/x-asm"},
                                   {"asp", "text/asp"},
                                   {"asx", "application/x-mplayer2"},
                                   {"au", "audio/basic"},
                                   {"avi", "video/avi"},
                                   {"avs", "video/avs-video"},
                                   {"bcpio", "application/x-bcpio"},
                                   {"bin", "application/octet-stream"},
                                   {"bm", "image/bmp"},
                                   {"bmp", "image/bmp"},
                                   {"boo", "application/book"},
                                   {"book", "application/book"},
                                   {"boz", "application/x-bzip2"},
                                   {"bsh", "application/x-bsh"},
                                   {"bz", "application/x-bzip"},
                                   {"bz2", "application/x-bzip2"},
                                   {"c", "text/plain"},
                                   {"c++", "text/plain"},
                                   {"cat", "application/vnd.ms-pki.seccat"},
                                   {"cc", "text/plain"},
                                   {"ccad", "application/clariscad"},
                                   {"cco", "application/x-cocoa"},
                                   {"cdf", "application/cdf"},
                                   {"cer", "application/pkix-cert"},
                                   {"cha", "application/x-chat"},
                                   {"chat", "application/x-chat"},
                                   {"class", "application/java"},
                                   {"com", "application/octet-stream"},
                                   {"conf", "text/plain"},
                                   {"cpio", "application/x-cpio"},
                                   {"cpp", "text/x-c"},
                                   {"cpt", "application/x-cpt"},
                                   {"crl", "application/pkcs-crl"},
                                   {"css", "text/css"},
                                   {"def", "text/plain"},
                                   {"der", "application/x-x509-ca-cert"},
                                   {"dif", "video/x-dv"},
                                   {"dir", "application/x-director"},
                                   {"dl", "video/dl"},
                                   {"doc", "application/msword"},
                                   {"dot", "application/msword"},
                                   {"dp", "application/commonground"},
                                   {"drw", "application/drafting"},
                                   {"dump", "application/octet-stream"},
                                   {"dv", "video/x-dv"},
                                   {"dvi", "application/x-dvi"},
                                   {"dwf", "drawing/x-dwf (old)"},
                                   {"dwg", "application/acad"},
                                   {"dxf", "application/dxf"},
                                   {"eps", "application/postscript"},
                                   {"es", "application/x-esrehber"},
                                   {"etx", "text/x-setext"},
                                   {"evy", "application/envoy"},
                                   {"exe", "application/octet-stream"},
                                   {"f", "text/plain"},
                                   {"f90", "text/x-fortran"},
                                   {"fdf", "application/vnd.fdf"},
                                   {"fif", "image/fif"},
                                   {"fli", "video/fli"},
                                   {"for", "text/x-fortran"},
                                   {"fpx", "image/vnd.fpx"},
                                   {"g", "text/plain"},
                                   {"g3", "image/g3fax"},
                                   {"gif", "image/gif"},
                                   {"gl", "video/gl"},
                                   {"gsd", "audio/x-gsm"},
                                   {"gtar", "application/x-gtar"},
                                   {"gz", "application/x-compressed"},
                                   {"h", "text/plain"},
                                   {"help", "application/x-helpfile"},
                                   {"hgl", "application/vnd.hp-hpgl"},
                                   {"hh", "text/plain"},
                                   {"hlp", "application/x-winhelp"},
                                   {"htc", "text/x-component"},
                                   {"htm", "text/html"},
                                   {"html", "text/html"},
                                   {"htmls", "text/html"},
                                   {"htt", "text/webviewhtml"},
                                   {"htx", "text/html"},
                                   {"ice", "x-conference/x-cooltalk"},
                                   {"ico", "image/x-icon"},
                                   {"idc", "text/plain"},
                                   {"ief", "image/ief"},
                                   {"iefs", "image/ief"},
                                   {"iges", "application/iges"},
                                   {"igs", "application/iges"},
                                   {"ima", "application/x-ima"},
                                   {"imap", "application/x-httpd-imap"},
                                   {"inf", "application/inf"},
                                   {"ins", "application/x-internett-signup"},
                                   {"ip", "application/x-ip2"},
                                   {"isu", "video/x-isvideo"},
                                   {"it", "audio/it"},
                                   {"iv", "application/x-inventor"},
                                   {"ivr", "i-world/i-vrml"},
                                   {"ivy", "application/x-livescreen"},
                                   {"jam", "audio/x-jam"},
                                   {"jav", "text/plain"},
                                   {"java", "text/plain"},
                                   {"jcm", "application/x-java-commerce"},
                                   {"jfif", "image/jpeg"},
                                   {"jfif-tbnl", "image/jpeg"},
                                   {"jpe", "image/jpeg"},
                                   {"jpeg", "image/jpeg"},
                                   {"jpg", "image/jpeg"},
                                   {"jps", "image/x-jps"},
                                   {"js", "application/x-javascript"},
                                   {"jut", "image/jutvision"},
                                   {"kar", "audio/midi"},
                                   {"ksh", "application/x-ksh"},
                                   {"la", "audio/nspaudio"},
                                   {"lam", "audio/x-liveaudio"},
                                   {"latex", "application/x-latex"},
                                   {"lha", "application/lha"},
                                   {"lhx", "application/octet-stream"},
                                   {"list", "text/plain"},
                                   {"lma", "audio/nspaudio"},
                                   {"log", "text/plain"},
                                   {"lsp", "application/x-lisp"},
                                   {"lst", "text/plain"},
                                   {"lsx", "text/x-la-asf"},
                                   {"ltx", "application/x-latex"},
                                   {"lzh", "application/octet-stream"},
                                   {"lzx", "application/lzx"},
                                   {"m", "text/plain"},
                                   {"m1v", "video/mpeg"},
                                   {"m2a", "audio/mpeg"},
                                   {"m2v", "video/mpeg"},
                                   {"m3u", "audio/x-mpequrl"},
                                   {"man", "application/x-troff-man"},
                                   {"map", "application/x-navimap"},
                                   {"mar", "text/plain"},
                                   {"mbd", "application/mbedlet"},
                                   {"mc$", "application/x-magic-cap-package-1.0"},
                                   {"mcd", "application/mcad"},
                                   {"mcf", "image/vasa"},
                                   {"mcp", "application/netmc"},
                                   {"me", "application/x-troff-me"},
                                   {"mht", "message/rfc822"},
                                   {"mhtml", "message/rfc822"},
                                   {"mid", "audio/midi"},
                                   {"midi", "audio/midi"},
                                   {"mif", "application/x-frame"},
                                   {"mime", "message/rfc822"},
                                   {"mjf", "audio/x-vnd.audioexplosion.mjuicemediafile"},
                                   {"mjpg", "video/x-motion-jpeg"},
                                   {"mm", "application/base64"},
                                   {"mme", "application/base64"},
                                   {"mod", "audio/mod"},
                                   {"moov", "video/quicktime"},
                                   {"mov", "video/quicktime"},
                                   {"movie", "video/x-sgi-movie"},
                                   {"mp2", "audio/mpeg"},
                                   {"mp3", "audio/mpeg3"},
                                   {"mpa", "audio/mpeg"},
                                   {"mpc", "application/x-project"},
                                   {"mpe", "video/mpeg"},
                                   {"mpeg", "video/mpeg"},
                                   {"mpg", "video/mpeg"},
                                   {"mpga", "audio/mpeg"},
                                   {"mpp", "application/vnd.ms-project"},
                                   {"mpt", "application/x-project"},
                                   {"mpv", "application/x-project"},
                                   {"mpx", "application/x-project"},
                                   {"mrc", "application/marc"},
                                   {"ms", "application/x-troff-ms"},
                                   {"mv", "video/x-sgi-movie"},
                                   {"my", "audio/make"},
                                   {"mzz", "application/x-vnd.audioexplosion.mzz"},
                                   {"nap", "image/naplps"},
                                   {"naplps", "image/naplps"},
                                   {"nc", "application/x-netcdf"},
                                   {"ncm", "application/vnd.nokia.configuration-message"},
                                   {"nif", "image/x-niff"},
                                   {"niff", "image/x-niff"},
                                   {"nix", "application/x-mix-transfer"},
                                   {"nsc", "application/x-conference"},
                                   {"nvd", "application/x-navidoc"},
                                   {"o", "application/octet-stream"},
                                   {"oda", "application/oda"},
                                   {"omc", "application/x-omc"},
                                   {"omcd", "application/x-omcdatamaker"},
                                   {"omcr", "application/x-omcregerator"},
                                   {"p", "text/x-pascal"},
                                   {"p10", "application/pkcs10"},
                                   {"p12", "application/pkcs-12"},
                                   {"p7a", "application/x-pkcs7-signature"},
                                   {"p7c", "application/pkcs7-mime"},
                                   {"pas", "text/pascal"},
                                   {"pbm", "image/x-portable-bitmap"},
                                   {"pcl", "application/vnd.hp-pcl"},
                                   {"pct", "image/x-pict"},
                                   {"pcx", "image/x-pcx"},
                                   {"pdf", "application/pdf"},
                                   {"pfunk", "audio/make"},
                                   {"pgm", "image/x-portable-graymap"},
                                   {"pic", "image/pict"},
                                   {"pict", "image/pict"},
                                   {"pkg", "application/x-newton-compatible-pkg"},
                                   {"pko", "application/vnd.ms-pki.pko"},
                                   {"pl", "text/plain"},
                                   {"plx", "application/x-pixclscript"},
                                   {"pm", "image/x-xpixmap"},
                                   {"png", "image/png"},
                                   {"pnm", "application/x-portable-anymap"},
                                   {"pot", "application/mspowerpoint"},
                                   {"pov", "model/x-pov"},
                                   {"ppa", "application/vnd.ms-powerpoint"},
                                   {"ppm", "image/x-portable-pixmap"},
                                   {"pps", "application/mspowerpoint"},
                                   {"ppt", "application/mspowerpoint"},
                                   {"ppz", "application/mspowerpoint"},
                                   {"pre", "application/x-freelance"},
                                   {"prt", "application/pro_eng"},
                                   {"ps", "application/postscript"},
                                   {"psd", "application/octet-stream"},
                                   {"pvu", "paleovu/x-pv"},
                                   {"pwz", "application/vnd.ms-powerpoint"},
                                   {"py", "text/x-script.phyton"},
                                   {"pyc", "applicaiton/x-bytecode.python"},
                                   {"qcp", "audio/vnd.qcelp"},
                                   {"qd3", "x-world/x-3dmf"},
                                   {"qd3d", "x-world/x-3dmf"},
                                   {"qif", "image/x-quicktime"},
                                   {"qt", "video/quicktime"},
                                   {"qtc", "video/x-qtc"},
                                   {"qti", "image/x-quicktime"},
                                   {"qtif", "image/x-quicktime"},
                                   {"ra", "audio/x-pn-realaudio"},
                                   {"ram", "audio/x-pn-realaudio"},
                                   {"ras", "application/x-cmu-raster"},
                                   {"rast", "image/cmu-raster"},
                                   {"rexx", "text/x-script.rexx"},
                                   {"rf", "image/vnd.rn-realflash"},
                                   {"rgb", "image/x-rgb"},
                                   {"rm", "application/vnd.rn-realmedia"},
                                   {"rmi", "audio/mid"},
                                   {"rmm", "audio/x-pn-realaudio"},
                                   {"rmp", "audio/x-pn-realaudio"},
                                   {"rng", "application/ringing-tones"},
                                   {"rnx", "application/vnd.rn-realplayer"},
                                   {"roff", "application/x-troff"},
                                   {"rp", "image/vnd.rn-realpix"},
                                   {"rpm", "audio/x-pn-realaudio-plugin"},
                                   {"rt", "text/richtext"},
                                   {"rtf", "text/richtext"},
                                   {"rtx", "application/rtf"},
                                   {"rv", "video/vnd.rn-realvideo"},
                                   {"s", "text/x-asm"},
                                   {"s3m", "audio/s3m"},
                                   {"saveme", "application/octet-stream"},
                                   {"sbk", "application/x-tbook"},
                                   {"scm", "application/x-lotusscreencam"},
                                   {"sdml", "text/plain"},
                                   {"sdp", "application/sdp"},
                                   {"sdr", "application/sounder"},
                                   {"sea", "application/sea"},
                                   {"set", "application/set"},
                                   {"sgm", "text/sgml"},
                                   {"sgml", "text/sgml"},
                                   {"sh", "application/x-bsh"},
                                   {"shtml", "text/html"},
                                   {"sid", "audio/x-psid"},
                                   {"sit", "application/x-sit"},
                                   {"skd", "application/x-koan"},
                                   {"skm", "application/x-koan"},
                                   {"skp", "application/x-koan"},
                                   {"skt", "application/x-koan"},
                                   {"sl", "application/x-seelogo"},
                                   {"smi", "application/smil"},
                                   {"smil", "application/smil"},
                                   {"snd", "audio/basic"},
                                   {"sol", "application/solids"},
                                   {"spc", "application/x-pkcs7-certificates"},
                                   {"spl", "application/futuresplash"},
                                   {"spr", "application/x-sprite"},
                                   {"sprite", "application/x-sprite"},
                                   {"src", "application/x-wais-source"},
                                   {"ssi", "text/x-server-parsed-html"},
                                   {"ssm", "application/streamingmedia"},
                                   {"sst", "application/vnd.ms-pki.certstore"},
                                   {"step", "application/step"},
                                   {"stl", "application/sla"},
                                   {"stp", "application/step"},
                                   {"sv4cpio", "application/x-sv4cpio"},
                                   {"sv4crc", "application/x-sv4crc"},
                                   {"svf", "image/vnd.dwg"},
                                   {"svr", "application/x-world"},
                                   {"swf", "application/x-shockwave-flash"},
                                   {"t", "application/x-troff"},
                                   {"talk", "text/x-speech"},
                                   {"tar", "application/x-tar"},
                                   {"tbk", "application/toolbook"},
                                   {"tcl", "application/x-tcl"},
                                   {"tcsh", "text/x-script.tcsh"},
                                   {"tex", "application/x-tex"},
                                   {"texi", "application/x-texinfo"},
                                   {"texinfo", "application/x-texinfo"},
                                   {"text", "text/plain"},
                                   {"tgz", "application/x-compressed"},
                                   {"tif", "image/tiff"},
                                   {"tr", "application/x-troff"},
                                   {"tsi", "audio/tsp-audio"},
                                   {"tsp", "audio/tsplayer"},
                                   {"tsv", "text/tab-separated-values"},
                                   {"turbot", "image/florian"},
                                   {"txt", "text/plain"},
                                   {"uil", "text/x-uil"},
                                   {"uni", "text/uri-list"},
                                   {"unis", "text/uri-list"},
                                   {"unv", "application/i-deas"},
                                   {"uri", "text/uri-list"},
                                   {"uris", "text/uri-list"},
                                   {"ustar", "application/x-ustar"},
                                   {"uu", "application/octet-stream"},
                                   {"vcd", "application/x-cdlink"},
                                   {"vcs", "text/x-vcalendar"},
                                   {"vda", "application/vda"},
                                   {"vdo", "video/vdo"},
                                   {"vew", "application/groupwise"},
                                   {"viv", "video/vivo"},
                                   {"vivo", "video/vivo"},
                                   {"vmd", "application/vocaltec-media-desc"},
                                   {"vmf", "application/vocaltec-media-file"},
                                   {"voc", "audio/voc"},
                                   {"vos", "video/vosaic"},
                                   {"vox", "audio/voxware"},
                                   {"vqe", "audio/x-twinvq-plugin"},
                                   {"vqf", "audio/x-twinvq"},
                                   {"vql", "audio/x-twinvq-plugin"},
                                   {"vrml", "application/x-vrml"},
                                   {"vrt", "x-world/x-vrt"},
                                   {"vsd", "application/x-visio"},
                                   {"vst", "application/x-visio"},
                                   {"vsw", "application/x-visio"},
                                   {"w60", "application/wordperfect6.0"},
                                   {"w61", "application/wordperfect6.1"},
                                   {"w6w", "application/msword"},
                                   {"wav", "audio/wav"},
                                   {"wb1", "application/x-qpro"},
                                   {"wbmp", "image/vnd.wap.wbmp"},
                                   {"web", "application/vnd.xara"},
                                   {"wiz", "application/msword"},
                                   {"wk1", "application/x-123"},
                                   {"wmf", "windows/metafile"},
                                   {"wml", "text/vnd.wap.wml"},
                                   {"wmlc", "application/vnd.wap.wmlc"},
                                   {"wmls", "text/vnd.wap.wmlscript"},
                                   {"wmlsc", "application/vnd.wap.wmlscriptc"},
                                   {"word", "application/msword"},
                                   {"wp", "application/wordperfect"},
                                   {"wp5", "application/wordperfect"},
                                   {"wp6", "application/wordperfect"},
                                   {"wpd", "application/wordperfect"},
                                   {"wq1", "application/x-lotus"},
                                   {"wri", "application/mswrite"},
                                   {"wrl", "application/x-world"},
                                   {"wrz", "model/vrml"},
                                   {"wsc", "text/scriplet"},
                                   {"wsrc", "application/x-wais-source"},
                                   {"wtk", "application/x-wintalk"},
                                   {"xbm", "image/x-xbitmap"},
                                   {"xdr", "video/x-amt-demorun"},
                                   {"xgz", "xgl/drawing"},
                                   {"xif", "image/vnd.xiff"},
                                   {"xl", "application/excel"},
                                   {"xla", "application/excel"},
                                   {"xlb", "application/excel"},
                                   {"xlc", "application/excel"},
                                   {"xld", "application/excel"},
                                   {"xlk", "application/excel"},
                                   {"xll", "application/excel"},
                                   {"xlm", "application/excel"},
                                   {"xls", "application/excel"},
                                   {"xlt", "application/excel"},
                                   {"xlv", "application/excel"},
                                   {"xlw", "application/excel"},
                                   {"xm", "audio/xm"},
                                   {"xml", "text/xml"},
                                   {"xmz", "xgl/movie"},
                                   {"xpix", "application/x-vnd.ls-xpix"},
                                   {"xpm", "image/x-xpixmap"},
                                   {"x-png", "image/png"},
                                   {"xsr", "video/x-amt-showrun"},
                                   {"xwd", "image/x-xwd"},
                                   {"xyz", "chemical/x-pdb"},
                                   {"z", "application/x-compress"},
                                   {"zip", "application/x-compressed"},
                                   {"zoo", "application/octet-stream"},
                                   {"zsh", "text/x-script.zsh"}
                               };

   if(contentTypes[ext]==null)
   {
     return "application/octet-stream";

   }
       return contentTypes[ext];
     }
   }</pre>
<p>&nbsp;</p>
<p>The template:</p>
<p>&nbsp;</p>
<pre class="brush:xml">&lt;%@ Master Language="C#" MasterPageFile="~/umbraco/masterpages/default.master" AutoEventWireup="true" %&gt;

&lt;asp:Content ContentPlaceHolderID="ContentPlaceHolderDefault" runat="server"&gt;
  &lt;umbraco:Macro runat="server"  MediaID="[#media]" Alias="getMedia" /&gt;
&lt;/asp:Content&gt;</pre>
<p>&nbsp;</p>
<p>The redirect rule:</p>
<p>&nbsp;</p>
<pre class="brush:xml">        &lt;add name="cssmediarewrite"
          virtualUrl="^~/css/media/(.*)"
          rewriteUrlParameter="ExcludeFromClientQueryString"
          destinationUrl="~/cssmedia?path=$1"
          ignoreCase="true" /&gt;</pre>
<p>&nbsp;</p>
<p>Finally, add a page called CssMedia that uses the template.  Now, you can reference /css/media/mediapath/medianame.fileextension  to get the item.  This also means that your css files can use media/mediapath/medianame.fileextension to reference Umbraco media.</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
]]></content:encoded>
			<wfw:commentRss>http://www.foliotek.com/devblog/helper-razor-scripts-for-umbraco-cms/feed/</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
	</channel>
</rss>

