Compiling Mono 3.x from Source with LLVM Code Generation in Ubuntu LTS x64

The packages for Mono in Debian and Ubuntu are lagging far behind what is available from Xamarin for OS X, SUSE and OpenSUSE. The 2.10.x versions of Mono are .NET 2.0 runtime compatible and use Mono’s older Boem garbage collector — which tends to leak objects. The new generational “sgen” garbage collector is far superior All of the tutorials for building on Fedora/RHEL/CentOS and Debian/Ubuntu that I have seen build with the built-in code generation engine rather than LLVM. LLVM provides a significant speedup in the generated code of 20-30% but Mono can’t link against the version of LLVM that ships in Debian Wheezy or Ubuntu. You have to build a tweaked version from source — which is the tricky bit.

My goal was to build the same version of Mono that is shipped by Xamarin for OS X on Ubuntu 12.04 Precise Pangolin, including LLVM. That is mono 3.2.5 at the time of this writing.

Screen Shot 2013 12 01 at 10 58 51 AMPrerequisites

Mono is hosted on GitHub so we need git. It also requires GNU autotools, make and g++ to drive the build. 

sudo apt-get install libtool autoconf g++ gettext make git

We’re building something that isn’t managed by the system package manager, so according to the UNIX Filesystem Hierarchy Standard, the build product should go into the /opt file system. I’m going to use /opt/local and so before going any further let’s add that to the $PATH by editing /etc/environment with vi or your editor of choice:

PATH=“/opt/local/sbin:/opt/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games”

Also, we need to change the current environment:

export PATH=/opt/local/sbin:/opt/local/bin:$PATH

Libgdiplus

The first component of Mono that we need to build from source is libgdiplus. Libgdiplus is an open source implementation of the Windows GDI+ API for graphics and text rendering. Libgdiplus has a number of prerequisites because it basically just maps GDI+ function calls onto equivalent Linux libraries:

sudo apt-get install pkg-config libglib2.0-dev libpng12-dev libx11-dev libfreetype6-dev libfontconfig1-dev libtiff-dev libjpeg8-dev libgif-dev libexif-dev

mkdir ~/src; mdkr ~/src/mono; cd ~/src/mono

git clone https://github.com/mono/libgdiplus.git

cd libgdiplus

./configure –prefix=/opt/local

make

sudo make install

 

Mono-LLVM

Mono has a tweaked fork of LLVM. By default it will try to build x86 and x86_64 targets on Ubuntu x64 but the build will fail because there is no x86 runtime support installed.

cd ~/src/mono

git clone https://github.com/mono/llvm.git

cd llvm

git checkout mono

./configure –prefix=/opt/local –enable-optimized –enable-targets=x86_64

make

sudo make install

Mono with LLVM

Each official release of Mono is tagged in git. You should checkout the version that you want to build. In this case, I’m building Mono 3.2.5 with the LLVM and libgdiplus libraries we already built. Since there is no working version of Mono on the system, the very first time you build you need to do make get-monolite-latest which will pull down a minimal mono-based C# compiler to boostrap the build.

cd ~/src/mono

git clone https://github.com/mono/mono.git

cd mono

git checkout mono-3.2.5

export MONO_USE_LLVM=1

./autogen.sh –prefix=/opt/local –enable-llvm=yes –with-sgen=yes –with-gc=sgen

make get-monolite-latest

make EXTERNAL_MCS=${PWD}/mcs/class/lib/monolite/gmcs.exe

sudo make install

Assuming that all went well, you should now have a working build of mono 3.2.5 (or newer) in /opt/local/bin/ and executing mono –version should include LLVM: yes.

Depending on what you want to do with Mono, you may now want to build XSP (ASP.NET server, Apache module and FastCGI module for nginx) and/or F# 3.x.

XSP

The xsp2 server uses a .NET Framework 2.0/3.0/3.5 API and the xsp4 server provides a 4/4.5 API. The main tweak here is to set the PKG_CONFIG_PATH environmental variable so that the configure script can find mono in /opt/local.

cd ~/src/mono

git clone https://github.com/mono/xsp.git

cd xsp

git checkout 3.0.11

PKG_CONFIG_PATH=/opt/local/lib/pkgconfig ./autogen.sh –prefix=/opt/local

make

sudo make install

F# 3.1

cd ~/src/mono

git clone https://github.com/fsharp/fsharp

cd fsharp

git checkout fsharp_31

./autogen.sh –prefix=/opt/local

make

sudo make install

Advertisements

Async Google Spellcheck API Adaptor for TinyMCE

I recently added TinyMCE to a project in order to provide a stripped-down rich text editor with bold, italic and underline capability to a project. I discovered that the spell check functionality either required a client-side plugin for IE or a server-side implementation JSON RPC implementation called by TinyMCE via Ajax. Unfortunately, the only implementations for the server side provided by the TinyMCE project are in PHP and my project is in ASP.Net MVC 4.

Looking at the PHP implementations, one option is to adapt the Google Spellcheck API — which I didn’t even know existed. Basically this API allows you to post an XML document that contains a list of space-delimited words and get back a document which defines the substrings that are misspelled.

Using some examples of how the API works on the Google side, I was able to throw together a class that invokes it using the new async/await pattern in C# to create a Google Spellcheck API client that doesn’t block while wanting for its result.

using System;
using System.IO;
using System.Text;
using System.Net;
using System.Xml;
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Diagnostics;

namespace WolfeReiter.Web.Utility
{
/*
 * http post to http://www.google.com/tbproxy/spell?lang=en&hl=en
 * 
 * Google spellcheck API request looks like this.
 * 
 * <?xml version="1.0" encoding="utf-8" ?> 
 * <spellrequest textalreadyclipped="0" ignoredups="0" ignoredigits="1" ignoreallcaps="1">
 * <text>Ths is a tst</text>
 * </spellrequest>
 * 
 * The response look like ...
 * 
 * <?xml version="1.0" encoding="UTF-8"?>
 * <spellresult error="0" clipped="0" charschecked="12">
 * <c o="0" l="3" s="1">This Th's Thus Th HS</c>
 * <c o="9" l="3" s="1">test tat ST St st</c>
 * </spellresult>
 */

    public class GoogleSpell
    {
        const string GOOGLE_REQUEST_TEMPLATE = "<?xml version=\"1.0\" encoding=\"utf-8\" ?><spellrequest textalreadyclipped=\"0\" ignoredups=\"0\" ignoredigits=\"1\" ignoreallcaps=\"1\"><text>{0}</text></spellrequest>";

        public async Task<IEnumerable<string>> SpellcheckAsync(string lang, IEnumerable<string> wordList)
        {
            //convert list of words to space-delimited string.
            var words = string.Join(" ", wordList);
            var result = (await QueryGoogleAsync(lang, words));

            var doc = new XmlDocument();
            doc.LoadXml(result);

            // Build misspelled word list
            var misspelledWords = new List<string>();
            foreach (var node in doc.SelectNodes("//c"))
            {
                var cElm = (XmlElement)node;
                //google sends back bad word positions to slice out of original data we sent.
                try
                {
                    var badword = words.Substring(Convert.ToInt32(cElm.GetAttribute("o")), Convert.ToInt32(cElm.GetAttribute("l")));
                    misspelledWords.Add(badword);
                }
                catch( ArgumentOutOfRangeException e)
                {
                    Trace.WriteLine(e);
                    Debug.WriteLine(e);
                }
            }
            return misspelledWords;
        }

        public async Task<IEnumerable<string>> SuggestionsAsync(string lang, string word)
        {
            var result = (await QueryGoogleAsync(lang, word));

            // Parse XML result
            var doc = new XmlDocument();
            doc.LoadXml(result);

            // Build misspelled word list
            var suggestions = new List<string>();
            foreach (XmlNode node in doc.SelectNodes("//c"))
            {
                var element = (XmlElement)node;
                if(!string.IsNullOrWhiteSpace(element.InnerText))
                {
                    foreach (var suggestion in element.InnerText.Split('\t'))
                    {
                        if (!string.IsNullOrEmpty(suggestion)) { suggestions.Add(suggestion); }
                    }
                }
            }

            return suggestions;
        }

        async Task<string> QueryGoogleAsync(string lang, string data)
        {
            var scheme     = "https";
            var server     = "www.google.com";
            var port       = 443;
            var path       = "/tbproxy/spell";
            var query      = string.Format("?lang={0}&hl={1}", lang, data);
            var uriBuilder = new UriBuilder(scheme, server, port, path, query);
            string xml     = string.Format(GOOGLE_REQUEST_TEMPLATE, EncodeUnicodeToASCII(data));

            var request           = WebRequest.CreateHttp(uriBuilder.Uri);
            request.Method        = "POST";
            request.KeepAlive     = false;
            request.ContentType   = "application/PTI26";
            request.ContentLength = xml.Length;

            // Google-specific headers
            var headers = request.Headers;
            headers.Add("MIME-Version: 1.0");
            headers.Add("Request-number: 1");
            headers.Add("Document-type: Request");
            headers.Add("Interface-Version: Test 1.4");

            using (var requestStream = (await request.GetRequestStreamAsync()))
            {
                var xmlData = Encoding.ASCII.GetBytes(xml);
                requestStream.Write(xmlData, 0, xmlData.Length);

                var response = (await request.GetResponseAsync());
                using (var responseStream = new StreamReader(response.GetResponseStream()))
                {
                    return responseStream.ReadToEnd();
                }
            }
        }

        string EncodeUnicodeToASCII(string s)
        {
            var builder = new StringBuilder();
            foreach(var c in s.ToCharArray())
            {
                //encode Unicode characters that can't be represented as ASCII
                if (c > 127) { builder.AppendFormat( "&#{0};", (int)c); }
                else { builder.Append(c); }
            }
            return builder.ToString();
        }

    }
}

The GoogleSpellChecker class below exposes two methods: SpellcheckAsync and SuggestionsAsync.

My MVC Controller class exposes this functionality to the TinyMCE by translating JSON back and forth to the GoogleSpell class.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Web;
using System.Web.Mvc;
using WolfeReiter.Web.Utility;

namespace MvcProject.Controllers
{
    public class TinyMCESpellcheckGatewayController : AsyncController
    {
        [HttpPost]
        public async Task<JsonResult> Index(SpellcheckRequest model)
        {
            var spellService = new GoogleSpell();
            IEnumerable<string> result = null;
            if(string.Equals(model.method, "getSuggestions", StringComparison.InvariantCultureIgnoreCase))
            {
                result = (await spellService.SuggestionsAsync(model.@params.First().Single(), model.@params.Skip(1).First().Single()));
            }
            else //assume checkWords
            {
                result = (await spellService.SpellcheckAsync(model.@params.First().Single(), model.@params.Skip(1).First()));
            }
            string error = null;
            return Json( new { result, id = model.id, error } );
        }

        //class models JSON posted by TinyMCE allows MVC Model Binding to "just work"
        public class SpellcheckRequest
        {
            public SpellcheckRequest()
            {
                @params = new List<IEnumerable<string>>();
            }
            public string method { get; set; }
            public string id { get; set; }
            public IEnumerable<IEnumerable<string>> @params { get; set; }
        }
    }
}

Integrating the above controller with TinyMCE is straightforward. All that needs to happen is include the “spellchecker” plugin, the “spellchecker” toolbar button and set the spellchecker_rpc_url to point to the controller.

/*global $, jQuery, tinyMCE, tinymce */
/// <reference path="jquery-1.8.3.js" />
/// <reference path="jquery-ui-1.8.24.js" />
/// <reference path="modernizr-2.6.2.js" />
/// <reference path="tinymce/tinymce.jquery.js" />
/// <reference path="tinymce/tiny_mce_jquery.js" />
(function () {
    "use strict";

    $(document).ready(function () {

        $('textarea.rich-text').tinymce({
            mode: "exact",
            theme: "advanced",
            plugins: "safari,spellchecker,paste",
            gecko_spellcheck: true,
            theme_advanced_buttons1: "bold,italic,underline,|,undo,redo,|,spellchecker,code",
            theme_advanced_statusbar_location: "none",
            spellchecker_rpc_url: "/TinyMCESpellcheckGateway", //<-- point TinyMCE to GoolgeSpell adaptor controller
            /*strip pasted microsoft office styles*/
            paste_strip_class_attributes: "mso"
        });
       
    });
}());

That’s all there is to it. Here’s how TinyMCE renders on a <textarea class=”rich-text-“></textarea>.

Tinymce spellcheck

Adapting the Microsoft Chart WebControl to MVC

The Chart control for ASP.Net that comes baked into .NET 4 or as an add-on to .NET 3 is useful but it is built around the WebForms rendering model which is uncomfortable in MVC applications. There is no pretty way to embed the WebChart control out-of-the-box. Solutions I have seen revolve around either giving up on the MVC separation and embedding the control into a View or creating custom web user controls for each chart and treating these as partial views. The former solution will only work with the WebForm view engine and not Razor or other view engines and the latter is just messy. Either way it feels like walking around with toilet paper stuck to your shoe. A third way is to return the image bytes of from a Chart object created in the controller as a FileResult. This works but you lose the image map tooltips.

The crux of the problem is that Chart is a DataBoundControl which is designed to magically generate its own HTML and to serve an image resource to itself when the rendered HTML is interpreted. It isn’t really meant to have an external controller tell it what to do. However, the Chart control has some nice features:

  1. It generates cross-browser friendly output
  2. It generates mouseover tooltips
  3. It doesn’t require Flash, Silverlight or JavaScipt because it uses MAP and AREA elements to create the tooltips
  4. With a little coercion, it will generate its image resource for you as a byte array which means you can alternatively embed the chart in an alternate resource like a PDF or other document envelope

Chart the MVC Way

Having decided that the Chart control does something we want, can we bend it to the MVC rendering model while preserving the nice tooltip image maps? The answer is yes we can. I have created a handful of extension methods that turn Chart on its head and allow you to render it out of a Controller class as an ActionResult. I’ll show you how things look in the View and Controller and then walk you through how it works.

View

<% Html.RenderAction( "PerformanceHistoryChart", new { id = Model.Id, shareClass = Model.ShareClass } ); %>

Controller

[OutputCache( Duration = 10, VaryByParam = "id;shareClass" )]
public ActionResult PerformanceHistoryChart( Guid id, string shareClass )
{
    return this.RenderChartMap( "PerformanceHistoryImage", id, shareClass, NewPerformanceHistoryChart );
}

[OutputCache( Duration = 10, VaryByParam = "id;shareClass" )]
public ActionResult PerformanceHistoryImage( Guid id, string shareClass )
{
    return this.RenderChartImage( id, shareClass, NewPerformanceHistoryChart );
}

private static Chart NewPerformanceHistoryChart( Guid id, string shareClass )
{
    Chart chart = new Chart();
    var series = perfChart.Series.Add( "Default" );
    var area   = perfChart.ChartAreas.Add( "ChartArea" );
    var values = ReportUtility.GetNetReturnOnValuePercentageHistory( id, shareClass );
    series.Points.DataBindXY( values.Item1, values.Item2 );
    series.ToolTip = "Fiscal Year: #VALX, Growth: #VALY{P}";
    area.AxisY.Title = "%";
    area.AxisY.LabelStyle.Format = "{P2}";
    return chart;
}

What’s going on in the View is that we are rendering the result of calling PerformanceHistoryChart on the controller. PerformanceHistoryChart renders HTML which includes an IMG tag reference to the PerformanceHistoryImage action which streams back the chart image as a PNG.PerformanceHistoryChart and PerformanceHistoryImage make use of my Chart to MVC adapter extension methods to do their work. There are two sets of extension method overloads:

  • this.RenderChartMap
  • this.RenderChartImage

In order to render any chart to a browser you need to provide two action methods. One of them calls RenderChartMap which generates HTML and the other calls RenderChartImage which returns the image as a FileResult for the IMG tag generated by RenderChartMap. Both extension methods accept a factory Func delegate which they use to create the Chart object that they manipulate internally. I’ve created overloads which accept 1 to 5 arguments.

MvcChart Extension Method Adapter Class

I’m sure this could be made more elegant but my first cut is working well. One limitation of my implementation is that you need a pair of extension method overloads that has enough arguments for your Chart factory Func  to work. Another issue is that you need to have an explicit route defined for the Actions which return the chart image FileContentResult. The reason is that the Chart control always appends a ?<some-guid> which is part of the caching mechanism it uses for storing images when it is rendering itself as a WebForms control. Therefore, I need to have an explicit route without any query string and allow the query string the Chart control generates to dangle off the end. My assumption is that the arguments will be passed in order from left to right in the URL as they appear in the method call. Also, since we are taking over rendering the Chart control via our Controller we give up the baked-in image caching. This is probably not much of an issue but it makes sense to use OutputCaching attributes to reduce the hits on the Chart factory Funcs and therefore your database.

Here’s my MvcChart class which contains the adapter extension methods. Happy coding.

using System.IO;
using System.Web.UI;
using System.Web.UI.DataVisualization.Charting;

namespace System.Web.Mvc
{
    /// <summary>
    /// <para>This class adapts the System.Web.UI.DataVisualization.Charting.Chart API to something
    /// more friendly to ASP.Net MVC by attaching extension methods to the Controller type.Requires
    /// an explicit route mapping for all arguments. The arguments will be inserted into the Image 
    /// generating URL in order.</para>
    /// <para>In each controller we need a pair of ActionMethods. One generates the IMG, MAP and AREA tags.
    /// The other streams the chart PNG. The URL to the chart PNG method is referenced by the imageActionName
    /// parameter in the RenderChartMap() method call. We also need a factory method which generates the Chart
    /// object instance. The chart factory is passed as a delegate to these adapter extension methods.</para>
    /// <remarks>In order to render each chart in a browser, the factory method must be executed twice.
    /// Consider using the OutputCache attribute to reduce hits on the data backend.</remarks>
    /// </summary>
    public static class MvcChart
    {
        /// <summary>
        /// Render img tag and associated image map for a chart.
        /// </summary>
        /// <typeparam name="T">Type of the factory argument.</typeparam>
        /// <param name="imageActionName">Method name in this controller that streams the chart image.</param>
        /// <param name="arg">Id used by the chart factory to find the data to generate the chart.</param>
        /// <param name="chartFactory">Delegate capable of generating a new Chart instance.</param>
        /// <returns>ContentResult that generates HTML for the chart.</returns>
        public static ContentResult RenderChartMap<T>( this Controller controller, string imageActionName,
            T arg, Func<T, Chart> chartFactory )
        {
            Chart chart = chartFactory.Invoke( arg );
            //FWIW: ImageLocation URL will have ?{some-guid} appended which is part of its classic WebForms ASP.Net
            //web control rendering model
            chart.ImageLocation = string.Format( "{0}{1}/{2}/{3}/", 
                GetApplicationPath( controller ), 
                GetControllerName( controller ), 
                imageActionName, 
                arg );
            return RenderChartMap( chart );
        }

        /// <summary>
        /// Render img tag and associated image map for a chart.
        /// </summary>
        /// <typeparam name="T1">Type of the first factory argument.</typeparam>
        /// <typeparam name="T2">Type of the second factory argument.</typeparam>
        /// <param name="controller">Controller instance.</param>
        /// <param name="imageActionName">Method name in this controller that streams the chart image.</param>
        /// <param name="arg1">Id used by the chart factory to find the data to generate the chart.</param>
        /// <param name="arg2">Second parameter used by the chart factory.</param>
        /// <param name="chartFactory">Delegate capable of generating a new Chart instance.</param>
        /// <returns>ContentResult that generates HTML for the chart.</returns>
        public static ContentResult RenderChartMap<T1,T2>( this Controller controller, string imageActionName, 
            T1 arg1, T2 arg2, Func<T1, T2, Chart> chartFactory )
        {
            Chart chart = chartFactory.Invoke( arg1, arg2 );
            chart.ImageLocation = string.Format( "{0}{1}/{2}/{3}/{4}/", 
                GetApplicationPath( controller ), 
                GetControllerName( controller ), 
                imageActionName, 
                arg1, 
                arg2 );
            return RenderChartMap( chart );
        }

        /// <summary>
        /// Render img tag and associated image map for a chart.
        /// </summary>
        /// <typeparam name="T1">Type of the first factory argument.</typeparam>
        /// <typeparam name="T2">Type of the second factory argument.</typeparam>
        /// <typeparam name="T3">Type of the third factory argument.</typeparam>
        /// <param name="controller">Controller instance.</param>
        /// <param name="imageActionName">Method name in this controller that streams the chart image.</param>
        /// <param name="arg1">Id used by the chart factory to find the data to generate the chart.</param>
        /// <param name="arg2">Second parameter used by the chart factory.</param>
        /// <param name="arg3">Third parameter used by the chart factory.</param>
        /// <param name="chartFactory">Delegate capable of generating a new Chart instance.</param>
        /// <returns>ContentResult that generates HTML for the chart.</returns>
        public static ContentResult RenderChartMap<T1, T2, T3>( this Controller controller, string imageActionName,
            T1 arg1, T2 arg2, T3 arg3, Func<T1, T2, T3, Chart> chartFactory )
        {
            Chart chart = chartFactory.Invoke( arg1, arg2, arg3 );
            chart.ImageLocation = string.Format( "{0}{1}/{2}/{3}/{4}/{5}/",
                GetApplicationPath( controller ),
                GetControllerName( controller ),
                imageActionName,
                arg1,
                arg2,
                arg3 );
            return RenderChartMap( chart );
        }

        /// <summary>
        /// Render img tag and associated image map for a chart.
        /// </summary>
        /// <typeparam name="T1">Type of the first factory argument.</typeparam>
        /// <typeparam name="T2">Type of the second factory argument.</typeparam>
        /// <typeparam name="T3">Type of the third factory argument.</typeparam>
        /// <typeparam name="T4">Type of the fourth factory argument.</typeparam>
        /// <param name="controller">Controller instance.</param>
        /// <param name="imageActionName">Method name in this controller that streams the chart image.</param>
        /// <param name="arg1">Id used by the chart factory to find the data to generate the chart.</param>
        /// <param name="arg2">Second parameter used by the chart factory.</param>
        /// <param name="arg3">Third parameter used by the chart factory.</param>
        /// <param name="arg4">Fourth parameter used by the chart factory.</param>
        /// <param name="chartFactory">Delegate capable of generating a new Chart instance.</param>
        /// <returns>ContentResult that generates HTML for the chart.</returns>
        public static ContentResult RenderChartMap<T1, T2, T3, T4>( this Controller controller, string imageActionName,
            T1 arg1, T2 arg2, T3 arg3, T4 arg4, Func<T1, T2, T3, T4, Chart> chartFactory )
        {
            Chart chart = chartFactory.Invoke( arg1, arg2, arg3, arg4 );
            chart.ImageLocation = string.Format( "{0}{1}/{2}/{3}/{4}/{5}/{6}/",
                GetApplicationPath( controller ),
                GetControllerName( controller ),
                imageActionName,
                arg1,
                arg2,
                arg3,
                arg4);
            return RenderChartMap( chart );
        }

        /// <summary>
        /// Render img tag and associated image map for a chart.
        /// </summary>
        /// <typeparam name="T1">Type of the first factory argument.</typeparam>
        /// <typeparam name="T2">Type of the second factory argument.</typeparam>
        /// <typeparam name="T3">Type of the third factory argument.</typeparam>
        /// <typeparam name="T4">Type of the fourth factory argument.</typeparam>
        /// <typeparam name="T5">Type of the fifth factory argument.</typeparam>
        /// <param name="imageActionName">Method name in this controller that streams the chart image.</param>
        /// <param name="arg1">Id used by the chart factory to find the data to generate the chart.</param>
        /// <param name="arg2">Second parameter used by the chart factory.</param>
        /// <param name="arg3">Third parameter used by the chart factory.</param>
        /// <param name="arg4">Fourth parameter used by the chart factory.</param>
        /// <param name="arg5">Fifth parameter used by the chart factory.</param>
        /// <param name="chartFactory">Delegate capable of generating a new Chart instance.</param>
        /// <returns>ContentResult that generates HTML for the chart.</returns>
        public static ContentResult RenderChartMap<T1, T2, T3, T4, T5>( this Controller controller, string imageActionName,
            T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, Func<T1, T2, T3, T4, T5, Chart> chartFactory )
        {
            Chart chart = chartFactory.Invoke( arg1, arg2, arg3, arg4, arg5 );
            chart.ImageLocation = string.Format( "{0}{1}/{2}/{3}/{4}/{5}/{6}/{7}/",
                GetApplicationPath( controller ),
                GetControllerName( controller ),
                imageActionName,
                arg1,
                arg2,
                arg3,
                arg4,
                arg5 );
            return RenderChartMap( chart );
        }

        /// <summary>
        /// Render chart image byte stream as a PNG file.
        /// </summary>
        /// <typeparam name="T">Type of the id.</typeparam>
        /// <param name="controller">Controller instance.</param>
        /// <param name="arg">Id used by the chart factory to find the data to generate the chart.</param>
        /// <param name="chartFactory">Delegate capable of generating a new Chart instance.</param>
        /// <returns></returns>
        public static FileContentResult RenderChartImage<T>( this Controller controller, T arg, Func<T, Chart> chartFactory )
        {
            Chart chart = chartFactory.Invoke( arg );
            return RenderChartImage( chart );
        }

        /// <summary>
        /// Render chart image byte stream as a PNG file.
        /// </summary>
        /// <typeparam name="T1">Type of the first factory parameter.</typeparam>
        /// <typeparam name="T2">Type of the second factory parameter.</typeparam>
        /// <param name="controller">Controller instance.</param>
        /// <param name="arg1">Id used by the chart factory to find the data to generate the chart.</param>
        /// <param name="arg2">Second parameter used by the chart factory.</param>
        /// <param name="chartFactory">Delegate capable of generating a new Chart instance.</param>
        /// <returns></returns>
        public static FileContentResult RenderChartImage<T1, T2>( this Controller controller, 
            T1 arg1, T2 arg2, Func<T1, T2, Chart> chartFactory )
        {
            Chart chart = chartFactory.Invoke( arg1, arg2 );
            return RenderChartImage( chart );
        }

        /// <summary>
        /// Render chart image byte stream as a PNG file.
        /// </summary>
        /// <typeparam name="T1">Type of the first factory parameter.</typeparam>
        /// <typeparam name="T2">Type of the second factory parameter.</typeparam>
        /// <typeparam name="T3">Type of the third factory argument.</typeparam>
        /// <param name="controller">Controller instance.</param>
        /// <param name="arg1">Id used by the chart factory to find the data to generate the chart.</param>
        /// <param name="arg2">Second parameter used by the chart factory.</param>
        /// <param name="arg3">Third parameter used by the chart factory.</param>
        /// <param name="chartFactory">Delegate capable of generating a new Chart instance.</param>
        /// <returns></returns>
        public static FileContentResult RenderChartImage<T1, T2, T3>( this Controller controller, 
            T1 arg1, T2 arg2, T3 arg3, Func<T1, T2, T3, Chart> chartFactory )
        {
            Chart chart = chartFactory.Invoke( arg1, arg2, arg3 );
            return RenderChartImage( chart );
        }

        /// <summary>
        /// Render chart image byte stream as a PNG file.
        /// </summary>
        /// <typeparam name="T1">Type of the first factory parameter.</typeparam>
        /// <typeparam name="T2">Type of the second factory parameter.</typeparam>
        /// <typeparam name="T3">Type of the third factory argument.</typeparam>
        /// <typeparam name="T4">Type of the fourth factory argument.</typeparam>
        /// <param name="controller">Controller instance.</param>
        /// <param name="arg1">Id used by the chart factory to find the data to generate the chart.</param>
        /// <param name="arg2">Second parameter used by the chart factory.</param>
        /// <param name="arg3">Third parameter used by the chart factory.</param>
        /// <param name="arg4">Fourth parameter used by the chart factory.</param>
        /// <param name="chartFactory">Delegate capable of generating a new Chart instance.</param>
        /// <returns></returns>
        public static FileContentResult RenderChartImage<T1, T2, T3, T4>( this Controller controller,
            T1 arg1, T2 arg2, T3 arg3, T4 arg4, Func<T1, T2, T3, T4,  Chart> chartFactory )
        {
            Chart chart = chartFactory.Invoke( arg1, arg2, arg3, arg4 );
            return RenderChartImage( chart );
        }

        /// <summary>
        /// Render chart image byte stream as a PNG file.
        /// </summary>
        /// <typeparam name="T1">Type of the first factory parameter.</typeparam>
        /// <typeparam name="T2">Type of the second factory parameter.</typeparam>
        /// <typeparam name="T3">Type of the third factory argument.</typeparam>
        /// <typeparam name="T4">Type of the fourth factory argument.</typeparam>
        /// <typeparam name="T5">Type of the fifth factory argument.</typeparam>
        /// <param name="controller">Controller instance.</param>
        /// <param name="arg1">Id used by the chart factory to find the data to generate the chart.</param>
        /// <param name="arg2">Second parameter used by the chart factory.</param>
        /// <param name="arg3">Third parameter used by the chart factory.</param>
        /// <param name="arg4">Fourth parameter used by the chart factory.</param>
        /// <param name="arg5">Fourth parameter used by the chart factory.</param>
        /// <param name="chartFactory">Delegate capable of generating a new Chart instance.</param>
        /// <returns></returns>
        public static FileContentResult RenderChartImage<T1, T2, T3, T4, T5>( this Controller controller,
            T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, Func<T1, T2, T3, T4, T5, Chart> chartFactory )
        {
            Chart chart = chartFactory.Invoke( arg1, arg2, arg3, arg4, arg5 );
            return RenderChartImage( chart );
        }
        
        private static ContentResult RenderChartMap( Chart chart )
        {
            chart.RenderType = RenderType.ImageMap;
            //Make sure that each chart rendered into the page has a unique ID in HTML by
            //assigning a Guid to the ID property. Otherwise, they will all have the same
            //ID and the image map will only work for the first chart at best.
            chart.ID = Guid.NewGuid().ToString( "N" );
            using( var writer = new StringWriter() )
            using( var htmlTextWriter = new HtmlTextWriter( writer ) )
            {
                chart.RenderControl( htmlTextWriter );
                return new ContentResult() { Content = writer.ToString() };
            }
        }

        private static FileContentResult RenderChartImage( Chart chart )
        {
            using( MemoryStream chartStream = new MemoryStream() )
            {
                chart.SaveImage( chartStream, ChartImageFormat.Png );
                return new FileContentResult( chartStream.ToArray(), "image/png" ) { FileDownloadName = "chart.png" };
            }
        }

        private static string GetApplicationPath( Controller controller )
        {
            var applicationPath = controller.HttpContext.Request.ApplicationPath;
            applicationPath = applicationPath.EndsWith( "/" ) ? applicationPath : applicationPath + "/";
            return applicationPath;
        }

        private static string GetControllerName( Controller controller )
        {
            var controllerName = controller.GetType().Name;
            if( !controllerName.EndsWith( "Controller" ) )
                throw new InvalidOperationException( "Controller names must end with \"Controller\"" );

            controllerName = controllerName.Substring( 0, controllerName.Length - 10 ); ;
            return controllerName;
        }
    }
}

Secure Password Hashing in C#

Long-term storage of logon credentials in computer systems is a serious point of vulnerability. Too many systems store credentials in an unencrypted or weakly encrypted form that system administrators or database power-users can view or edit. This may seem OK as long as a set of conditions were true:

  • The administrators of the system are known to be trustworthy and no untrusted users have the ability to view, alter or copy the password file or table.
  • The normal execution flow of the system could not be used to reveal plaintext passwords.
  • The online and backup repositories of authentication data are secure.
    Employee turnover, the increasing complexity of internet-connected systems, and the spread of knowledge and tools for executing simple security exploits all work together to change the rules of the game so that storing passwords in plaintext is no longer acceptable.  Disgruntled employees are now widely acknowledged to be the single largest vulnerability for most companies, and are the most common attackers of computer systems.  Defects in production software, runtimes and platforms can also disclose sensitive information to attackers. A litany of recent headlines where a wide range of organizations have lost control of their authentication database prove that systems that store “secret” but plaintext logon information are unsafe.

Theory of Password Hashing in Brief

The key to hardening authentication systems to use a strong cryptographic function to hash passwords into an unrecoverable blob. A strong hashing algorithm must guard against brute force offline attack via a rainbow tables precompiled hashes on dictionary words. Rainbow tables and software to use them are publicly available which lowers the password cracking bar to the level of script kiddie.

Niels Furguson and Bruce Schneier in Practical Cryptography describe strong a strong hashing technique for safe storage of passwords which requires.

  • A publicly tested secure hashing algorithm such as SHA-256. Neither MD5 nor SHA-1 are good enough anymore.
  • A randomly-generated value known as a Salt is mixed with the plaintext. The salt value is not a secret, it exists to make the output of the hash non-deterministic. There should be a unique salt for each user and a new random salt value should be generated every time a password is set.
  • The hash is stretched, meaning that the output is fed back through the algorithm a large number of times in order to slow down the hashing algorithm to make brute force attacks impractical.

In order to authenticate a user, the system retrieves the salt for the username, calculates the hash for the password the user provided and compares that hash to the value in the authentication database. If the hashes match, then access is granted.

Implementation in C#

In hope of making it easy for .NET developers to implement good authentication security, my company published a secure password hashing algorithm in C# under the BSD license in 2005. This algorithm is a part of the security subsystem of our PeopleMatrix product.

Because the code is no longer available on thoughtfulcomputing.com, so I’m republishing hit here.

Download HashManager source with a sample application.

//*****************************************************************
// <copyright file="HashManager.cs" company="WolfeReiter">
// Copyright (c) 2005 WolfeReiter, LLC.
// </copyright>
//*****************************************************************

/*
** Copyright (c) 2005 WolfeReiter, LLC
**
** This software is provided 'as-is', without any express or implied warranty. In no 
** event will the authors be held liable for any damages arising from the use of 
** this software.
**
** Permission is granted to anyone to use this software for any purpose, including 
** commercial applications, and to alter it and redistribute it freely, subject to 
** the following restrictions:
**
**    1. The origin of this software must not be misrepresented; you must not claim 
**       that you wrote the original software. If you use this software in a product,
**       an acknowledgment in the product documentation would be appreciated but is 
**       not required.
**
**    2. Altered source versions must be plainly marked as such, and must not be 
**       misrepresented as being the original software.
**
**    3. This notice may not be removed or altered from any source distribution.
**
*/

using System;
using System.Globalization;
using System.Text;
using System.Security.Cryptography;

namespace WolfeReiter.Security.Cryptography
{
	/// <summary>
	/// HashManager provides the service of cryptographically hashing and verifying strings
	/// against an existing hash. HashManager uses the SHA-256 hashing transform by default.
	/// </summary>
	/// <remarks>
	/// <para>The MD5 and SHA1 algorithms should be avoided because they are not strong enough
	/// to resist attack with modern processors. Also, it appears that the algorithms for MD5 and SHA1
	/// may have a flaw known as "collision". The short story is that  an attacker can discover the 
	/// plaintext used to generate the hash in much fewer steps than expected.</para>
	/// <para>For example, SHA1 should theoretically require 2<sup>80</sup> steps to brute-force recover 
	/// the original plaintext. Researchers have recently claimed to have a method of recovering SHA1 plaintext
	/// in 2<sup>33</sup> steps.</para></remarks>
	[Serializable]
	public sealed class HashManager 
	{
		private HashAlgorithm _transform;
		private readonly ulong _iterations;

		/// <summary>
		/// CTOR. Creates a new HashManager object that uses the SHA-256 algorithm and stretches the entropy in the hash
		/// with 2<sup>16</sup> iterations.
		/// </summary>
		public HashManager() : this("SHA-256",65536){}
		/// <summary>
		/// CTOR. Creates a new HashManager object that uses the specified well-known hash transform algorithm.
		/// </summary>
		/// <param name="transform">Well-known transform algorithm (eg. MD5, SHA-1, SHA-256, SHA-384, SHA-512, etc.).</param>
		/// <param name="iterations">Number of iterations used to sretch the entropy of the plaintext. 2<sup>16</sup> iterations
		/// is a recommended minimum.</param>
		/// <exception cref="ArgumentOutOfRangeException">Throws if iterations is less than 1.</exception>
		public HashManager(string transform, ulong iterations) : this(HashAlgorithm.Create(transform),iterations){}

		/// <summary>
		/// CTOR. Creates a new HashManager object that uses provided transform hash algorithm.
		/// </summary>
		/// <param name="transform">HashAlgorithm object to use.</param>
		/// <param name="iterations">Number of iterations used to sretch the entropy of the plaintext. 2<sup>16</sup> iterations
		/// is a recommended minimum.</param>
		/// <exception cref="ArgumentOutOfRangeException">Throws if iterations is less than 1.</exception>
		public HashManager(HashAlgorithm transform, ulong iterations)
		{
			if( iterations < 1 )
				throw new ArgumentOutOfRangeException("iterations", iterations, "The number of iterations cannot be less than 1");
			_transform  = transform;
			_iterations = iterations;
		}
		/// <summary>
		/// CTOR. Creates a new HashManager object that uses the specified well-known hash transform algorithm.
		/// </summary>
		/// <param name="transform">Well-known transform algorithm (eg. MD5, SHA-1, SHA-256, SHA-384, SHA-512, etc.).</param>
		/// <param name="iterations">Number of iterations used to sretch the entropy of the plaintext. 2<sup>16</sup> iterations
		/// is a recommended minimum.</param>
		/// <exception cref="ArgumentOutOfRangeException">Throws if iterations is less than 1.</exception>
		public HashManager(string transform, long iterations) : this(HashAlgorithm.Create(transform),iterations){}

		/// <summary>
		/// CTOR. Creates a new HashManager object that uses provided transform hash algorithm.
		/// </summary>
		/// <param name="transform">HashAlgorithm object to use.</param>
		/// <param name="iterations">Number of iterations used to sretch the entropy of the plaintext. 2<sup>16</sup> iterations
		/// is a recommended minimum.</param>
		/// <exception cref="ArgumentOutOfRangeException">Throws if iterations is less than 1.</exception>
		public HashManager(HashAlgorithm transform, long iterations)
		{
			if( iterations < 1 )
				throw new ArgumentOutOfRangeException("iterations", iterations, "The number of iterations cannot be less than 1");
			_transform  = transform;
			_iterations = (ulong)iterations;
		}
		/// <summary>
		/// Hashes a plaintext string.
		/// </summary>
		/// <param name="s">Plaintext to hash. (not nullable)</param>
		/// <param name="salt">Salt entropy to mix with the s. (not nullable)</param>
		/// <returns>HashManager byte array.</returns>
		/// <exception cref="ArgumentNullException">Throws if either the s or salt arguments are null.</exception>
		public byte[] Encode( string s, byte[] salt )
		{
			if( s==null )
				throw new ArgumentNullException("s");
			if( salt==null )
				throw new ArgumentNullException("salt");

            return Encode( ConvertStringToByteArray( s ), salt );
		}

        public byte[] Encode( byte[] plaintext, byte[] salt )
        {
            byte[] sp = salt;
            byte[] hash = plaintext;
            //stretching via multiple iterations injects entropy that makes collision plaintext recovery much
            //more difficult.
            for( ulong i = 0; i < _iterations; i++ )
            {
                sp = Salt( hash, salt );
                hash = _transform.ComputeHash( sp );
            }

            return hash;
        }

		private byte[] Salt( byte[] p, byte[] salt )
		{
			byte[] buff = new byte[p.Length + salt.Length];
			for( int i=0; i<p.Length; i++ )
				buff[i] = p[i];
			for( int i=0; i<salt.Length; i++ )
				buff[i + p.Length] = salt[i];
			
			return buff;
		}

		/// <summary>
		/// Verifies a plaintext string against an existing hash. This is a case-sensitive operation.
		/// </summary>
		/// <param name="s">Plaintext to verify</param>
		/// <param name="hash">HashManager to compare with the s</param>
		/// <param name="salt">Salt that was used with the original s to generate the hash</param>
		/// <returns>True if the s is the same as the one used to generate the original hash.
		/// Otherwise false.</returns>
		/// <exception cref="ArgumentNullException">Throws if any of the s, hash or salt arguments are null.</exception>
		public bool Verify( string s, byte[] hash, byte[] salt )
		{
			if( s == null )
				throw new ArgumentNullException("s");
			if( salt == null )
				throw new ArgumentNullException("salt");
			if( hash == null )
				throw new ArgumentNullException("hash");

            byte[] testhash = Encode( s, salt );
            return _Verify( testhash, hash );
		}

        public bool Verify( byte[] plaintext, byte[] hash, byte[] salt )
        {
            if( plaintext == null )
                throw new ArgumentNullException( "plaintext" );
            if( salt == null )
                throw new ArgumentNullException( "salt" );
            if( hash == null )
                throw new ArgumentNullException( "hash" );

            byte[] testhash = Encode( plaintext, salt );
            return _Verify( testhash, hash );
        }

        private bool _Verify( byte[] a, byte[] b )
        {
            if( a.Length != b.Length )
                return false;
            for( int i = 0; i < a.Length; i++ )
            {
                if( a[i] != b[i] )
                    return false;
            }
            return true;
        }

		/// <summary>
		/// Generates a 32 byte (256 bit) cryptographically random salt.
		/// </summary>
		/// <returns>256 element Byte array containing cryptographically random bytes.</returns>
		public static byte[] GenerateSalt()
		{
			return GenerateSalt(32);
		}
		/// <summary>
		/// Generates a cryptogrpahically random salt of arbirary size.
		/// </summary>
		/// <param name="size">size of the salt</param>
		/// <returns>Byte array containing cryptographically random bytes.</returns>
		public static byte[] GenerateSalt(int size)
		{
			Byte[] saltBuff = new Byte[size];
			RandomNumberGenerator.Create().GetBytes( saltBuff );
			return saltBuff;
		}

		/// <summary>
		/// Convert a hash (or salt or any other) byte[] to a hexadecimal string.
		/// </summary>
		/// <param name="hash">Byte[] to convert to hex.</param>
		/// <returns></returns>
		public static string ConvertToHexString( byte[] hash )
		{
			StringBuilder sb = new StringBuilder();
			foreach( byte b in hash )
			{
				string bytestr = Convert.ToInt32(b).ToString("X").ToLower();
				if( bytestr.Length==1)
					bytestr = '0' + bytestr;
				sb.Append( bytestr );
			}
			return sb.ToString();
		}

		/// <summary>
		/// Convert a hexadecimal string to byte[].
		/// </summary>
		/// <param name="s"></param>
		/// <returns></returns>
		public static byte[] ConvertFromHexString( string s )
		{
			string hex = s.ToLower();
			if( hex.StartsWith("0x") )
				hex = hex.Substring(2);

			//ensure an even hex number
			hex = (hex.Length % 2 == 0) ? hex : '0' + hex;
			//2 hex digits per byte
			byte[] b = new byte[hex.Length/2];
			for(int i=0,j=0; i<hex.Length; i+=2,j++)
			{
				b[j] = byte.Parse( hex.Substring(i,2), NumberStyles.HexNumber );
			}
			return b;
		}

		/// <summary>
		/// Convert a hash (or salt or any other) byte[] to a base64 string.
		/// </summary>
		/// <param name="hash">Byte[] to convert to base64</param>
		/// <returns></returns>
		public static string ConvertToBase64String( byte[] hash )
		{
			return Convert.ToBase64String(hash);
		}
		
		/// <summary>
		/// Convert a base64 encoded string into a byte array.
		/// </summary>
		/// <param name="s">Base64 string.</param>
		/// <returns>Byte array.</returns>
		public static byte[] ConvertFromBase64String( string s )
		{
			return Convert.FromBase64String( s );
		}

		/// <summary>
		/// Converts a String to a Byte array.
		/// </summary>
		/// <remarks>Only works on .NET string objects or Unicode encoded strings.</remarks>
		/// <param name="s">String to convert</param>
		/// <returns>Byte array.</returns>
		public static byte[] ConvertStringToByteArray( string s )
		{
			if (s == null)
				return null;
			Char[] chars = s.ToCharArray();
			Encoder encoder = Encoding.Unicode.GetEncoder();
			int bytecount = encoder.GetByteCount( chars, 0, chars.Length, true );
			byte[] outbuff = new byte[bytecount];
			encoder.GetBytes(chars, 0, chars.Length, outbuff, 0, true );
			return outbuff;
		}
	}
}

Ximian is Dead, Long Live Xamarin

I was depressed when I heard that Attachmate disbanded the Mono team as a part of it’s acquisition of Novell but Miguel de Icaza has a lot of chutzpa. He has reformed his team in nothing flat and launched a new company to continue the development of Mono and its associated C#-based mobile toolkits, MonoToch (iOS) and  MonoDriod (Android) and Moonlight (Silverlight for Linux).

To make a long story short, the plan to spin off was not executed. Instead on Monday May 2nd, the Canadian and American teams were laid off; Europe, Brazil and Japan followed a few days later. These layoffs included all the MonoTouch and MonoDroid engineers and other key Mono developers. Although Attachmate allowed us to go home that day, we opted to provide technical support to our users until our last day at Novell, which was Friday last week.

Now, two weeks later, we have a plan in place, which includes both angel funding for keeping the team together, as well as a couple of engineering contracts that will help us stay together as a team while we ship our revenue generating products.

~Miguel de Icaza

All the best Miguel.

Simple ASP.Net MVC Globalization with Graceful Fallback

We recently began work on a website project using ASP.Net MVC 3.0 where one of the requirements was that the site will be globalized into different languages. However, one of the design considerations is that the content is going to be originally produced in English and then translated and published as available into the other supported languages. What the site needs to do is gracefully degrade to English when the desired globalized content is not available.

Other issues are that some languages are wordier or  more compact than English which can affect the graphic design. For example “Structured Investment Vehicles” (30 characters) translates in French to “Véhicules d’Investissement Structurés” (37 characters). The French version requires 23% characters which is—roughly speaking (because of proportional fonts)—about 20% more space than the English version. It’s pretty typical for French phrases to contain 20-30% more characters than the English equivalent which means that resource file strings and just swapping out text can ruin the visual design layout. What is needed is re-layout of parts of views to accommodate the space consumed by different languages.

The idea is that we have English as an invariant which has every view the site uses in the normal place. We then have a /Views/Globalization directory which contains a subdirectory with the ISO 639-1 two-letter macro-language code ( ar = Arabic, fr = French, es = Spanish, etc.) which contain whatever translated content is available. If the browser requests a particular non-English language translation, we want to views to come from the appropriate Globalization directory  but if the content doesn’t exist, we fall back to English.

Our solution is to use views, partial views and master pages as the unit of globalization. In order to do this we created a new IViewEngine which extends whatever view engine we would otherwise be using. Our team is comfortable with WebForms so we extended WebFormViewEngine.

Example View Layout

/Views
    /Globalization
        /ar
            /Home
                /Index.aspx
            /Shared
                /Site.master
                /Navigation.aspx
        /es
            /Home
                /Index.aspx
            /Shared
                /Navigation.aspx
        /fr
            /Home
                /Index.aspx
            /Shared
    /Home
        /Index.aspx
    /Shared
        /Error.aspx
        /Footer.aspx
        /Navigation.aspx
        /Site.master

In the sample above, French is not fully globalized so when French views are served, the Navigation.ascx partial view should be served from /Views/Shared/Navigation.ascx, the Arabic views declare themselves as using the /Views/Globalization/ar/Shared/Site.master rather than /Views/Shared/Site.master and the default English Error.aspx view is always served.

jQuery Script to Switch Languages

/// <reference path="jquery-1.4.4.js" />
/// <reference path="jquery.cookie.js" />

//declare namespace to avoid collisions with extension js in the browser
var mySiteNamespace = {};

mySiteNamespace.switchLanguage = function (lang) {
    $.cookie('language', lang);
    window.location.reload();
};

$(document).ready(function () {
    // attach mySiteNamespace.switchLanguage to click events based on css classes
    $('.lang-english').click(function () { mySiteNamespace.switchLanguage('en'); });
    $('.lang-french').click(function () { mySiteNamespace.switchLanguage('fr'); });
    $('.lang-arabic').click(function () { mySiteNamespace.switchLanguage('ar'); });
    $('.lang-spanish').click(function () { mySiteNamespace.switchLanguage('es'); });
});

This little JavaScript attaches a switchLanguage function to elements (such as <a/>) which declare the specified css classes. It sets a language cookie which our custom IViewEngine is looking for.

Extending WebFormViewEngine

The key methods to override when extending WebFormViewEngine are CreatePartialView and CreateView. What we need to do is detect what language the browser is requesting and then choose the correct globalized view if it exists and fall back to the default behavior if it doesn’t. For simplicity, I’m showing code that uses a cookie to determine what language the end user wants but this detection can be as sophisticated as you like.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Text.RegularExpressions;
using System.IO;

namespace System.Web.Mvc
{
    public sealed class WebFormGlobalizationViewEngine : WebFormViewEngine
    {
        protected override IView CreatePartialView( ControllerContext controllerContext, string partialPath )
        {
            partialPath = GlobalizeViewPath( controllerContext, partialPath );
            return new WebFormView( controllerContext, partialPath, null, ViewPageActivator );
        }

        protected override IView CreateView( ControllerContext controllerContext, string viewPath, string masterPath )
        {
            viewPath = GlobalizeViewPath( controllerContext, viewPath );
            return base.CreateView( controllerContext, viewPath, masterPath );
        }

        private static string GlobalizeViewPath( ControllerContext controllerContext, string viewPath )
        {
            var request = controllerContext.HttpContext.Request;
            var lang = request.Cookies["language"];
            if( lang != null &&
                !string.IsNullOrEmpty(lang.Value) &&
                !string.Equals( lang.Value, "en", StringComparison.InvariantCultureIgnoreCase ) )
            {
                string localizedViewPath = Regex.Replace(
                    viewPath,
                    "^~/Views/",
                    string.Format( "~/Views/Globalization/{0}/",
                    lang.Value
                    ) );
                if( File.Exists( request.MapPath( localizedViewPath ) ) )
                { viewPath = localizedViewPath; }
            }
            return viewPath;
        }
    }
}

Registration in Global.asax.cs

The final step is to register WebFormsGlobalizationViewEngine when the Application.Start event fires.

protected void Application_Start()
{

    ViewEngines.Engines.Clear();
    ViewEngines.Engines.Add( new WebFormGlobalizationViewEngine() );

    // rest of application start stuff
}

Summing Up

There are a lot of ways to globalize content. This solution has the advantage of being straightforward and flexible. The main disadvantage is that we have to maintain translations of all the views and partial views.

C# in Depth 2nd Edition for Kindle but not from Amazon

skeet2_cover150Jon Skeet blogged this morning that C# in Depth 2nd edition is now available on Kindle. The original C# in Depth is just about the best programming book I’ve ever read, so I will definitely check this out. Don’t look on the Amazon Kindle store, though. Manning is self-publishing eBooks through its own website in PDF, epub and mobi. Mobi is the un-DRMed proto-format of Kindle and is read by Kindle devices and the Kindle desktop software. Interesting.

petricek_cover150Before I get C# in Depth 2nd Edition, though, I think I’ll check out Real-World Functional Programming by Tomas Petricek and Jon Skeet. This book is about both F# and C# (surely mostly LINQ). I’ve been playing around with Haskell lately and while it is interesting, it doesn’t seem all that practical. F# seems like a more approachable language and it targets .NET/Mono so it can mix with C# code that already exists and generally seems likely to be more immediately practical for real-world projects.

Also, if you have never heard of Jon Skeet, he’s an over-achiever who knows more about C# than just anyone not on the compiler team at Microsoft and he has by far the highest score on Stack Overflow. I don’t know how he has time for all of this because his day job has him working at Google in Java on the Android Market. He was interviewed on This Devleoper’s Life 1.1.3. Give it a listen.

%d bloggers like this: