Pretty Printing Exceptions

Here are some snippets you might find useful for pretty-printing exception stack traces (using the trace string rather than the StackTrace class).

First, a regular expression to extract namespace, method name, argument and source details from a stack trace string:

\sat\s(?<namespace>.*?)\.(?<method>(\.ctor|<\.ctor>[^(]*?|[^.(]+))\(\s*(((?<type>[^,\s)]+)\s+(?<arg>[^,\s)]+)\s*,?\s*?)*?)\s*\)( in (?<file>.*?):line (?<line>[0-9]*))?

Next, a helper class that uses the regex to return a collection of method details:

public class ExceptionParser
{
    private static Regex stackTraceExpr = new Regex(
        @"\sat\s(?<namespace>.*?)\.(?<method>(\.ctor|<\.ctor>[^(]*?|[^.(]+))\(\s*(((?<type>[^, )]+)\s+(?<arg>[^, )]+)\s*,?\s*?)*?)\s*\)( in (?<file>.*?):line (?<line>[0-9]*))?" );

    /// <summary>
    /// Parses the stack trace.
    /// </summary>
    /// <param name="stackTrace">The stack trace.</param>
    /// <returns></returns>
    public static IEnumerable<Method> ParseStackTrace( string stackTrace )
    {
        var matches = stackTraceExpr.Matches( stackTrace ).Cast<Match>();

        foreach ( var match in matches )
        {
            var line = match.Groups[ "line" ].Value;

            yield return new Method
            {
                Namespace = match.Groups[ "namespace" ].Value,
                Name = match.Groups[ "method" ].Value,
                Arguments = ParseArguments( match ).ToArray(),
                Source = match.Groups[ "source" ].Value,
                Line = !string.IsNullOrEmpty( line ) ? (int?)int.Parse( line ) : null
            };
        }
    }

    /// <summary>
    /// Parses the arguments.
    /// </summary>
    /// <param name="match">The match.</param>
    /// <returns></returns>
    private static IEnumerable<Argument> ParseArguments( Match match )
    {
        var type = match.Groups[ "type" ];
        var arg = match.Groups[ "arg" ];
        var types = type.Captures.Cast<Capture>().Select( c => c.Value ).Union( new[] { type.Value } ).GetEnumerator();
        var args = arg.Captures.Cast<Capture>().Select( c => c.Value ).Union( new[] { arg.Value } ).GetEnumerator();

        while ( types.MoveNext() && args.MoveNext() )
        {
            yield return new Argument
            {
                Type = types.Current,
                Name = args.Current
            };
        }
    }

    public struct Argument
    {
        public string Type;
        public string Name;
    }

    public struct Method
    {
        public string Namespace;
        public string Name;
        public Argument[] Arguments;
        public string Source;
        public int? Line;
    }
}

From there you might want to render it as a WPF FlowDocument or series of Inlines in WPF or Silverlight (you can use this directly or as an IValueConverter):

public class PrettyExceptionConverter : DependencyObject, IValueConverter
{
    #region Dependency properties

    public static readonly DependencyProperty ErrorStyleProperty = RegisterStyle( "ErrorStyle", MakeStyle( Colors.Red, FontStyles.Italic, FontWeights.Bold ) );
    public Style ErrorStyle { get { return (Style)GetValue( ErrorStyleProperty ); } set { SetValue( ErrorStyleProperty, value ); } }
    private Inline Error( string text ) { return new Run( text ) { Style = ErrorStyle }; }

    public static readonly DependencyProperty NamespaceStyleProperty = RegisterStyle( "NamespaceStyle", MakeStyle( Colors.Blue ) );
    public Style NamespaceStyle { get { return (Style)GetValue( NamespaceStyleProperty ); } set { SetValue( NamespaceStyleProperty, value ); } }
    private Inline Namespace( string text ) { return new Run( text ) { Style = NamespaceStyle }; }

    public static readonly DependencyProperty MethodNameStyleProperty = RegisterStyle( "MethodNameStyle", MakeStyle( Colors.Black, null, FontWeights.Bold ) );
    public Style MethodNameStyle { get { return (Style)GetValue( MethodNameStyleProperty ); } set { SetValue( MethodNameStyleProperty, value ); } }
    private Inline MethodName( string text ) { return new Run( text ) { Style = MethodNameStyle }; }

    public static readonly DependencyProperty BracketStyleProperty = RegisterStyle( "BracketStyle", MakeStyle( Colors.DarkRed ) );
    public Style BracketStyle { get { return (Style)GetValue( BracketStyleProperty ); } set { SetValue( BracketStyleProperty, value ); } }
    private Inline Bracket( string text ) { return new Run( text ) { Style = BracketStyle }; }

    public static readonly DependencyProperty SeparatorStyleProperty = RegisterStyle( "SeparatorStyle", MakeStyle( Colors.DarkBlue ) );
    public Style SeparatorStyle { get { return (Style)GetValue( SeparatorStyleProperty ); } set { SetValue( SeparatorStyleProperty, value ); } }
    private Inline Separator( string text ) { return new Run( text ) { Style = SeparatorStyle }; }

    public static readonly DependencyProperty SpaceStyleProperty = RegisterStyle( "SpaceStyle", null );
    public Style SpaceStyle { get { return (Style)GetValue( SpaceStyleProperty ); } set { SetValue( SpaceStyleProperty, value ); } }
    private Inline Space() { return new Run( " " ) { Style = SpaceStyle }; }

    public static readonly DependencyProperty TypeNameStyleProperty = RegisterStyle( "TypeNameStyle", MakeStyle( Colors.Teal ) );
    public Style TypeNameStyle { get { return (Style)GetValue( TypeNameStyleProperty ); } set { SetValue( TypeNameStyleProperty, value ); } }
    private Inline TypeName( string text ) { return new Run( text ) { Style = TypeNameStyle }; }

    public static readonly DependencyProperty ArgumentStyleProperty = RegisterStyle( "ArgumentStyle", MakeStyle( Colors.DarkGreen ) );
    public Style ArgumentStyle { get { return (Style)GetValue( ArgumentStyleProperty ); } set { SetValue( ArgumentStyleProperty, value ); } }
    private Inline Argument( string text ) { return new Run( text ) { Style = ArgumentStyle }; }

    public static readonly DependencyProperty SourceStyleProperty = RegisterStyle( "SourceStyle", MakeStyle( Colors.Gray ) );
    public Style SourceStyle { get { return (Style)GetValue( SourceStyleProperty ); } set { SetValue( SourceStyleProperty, value ); } }
    private Inline Source( string text ) { return new Run( text ) { Style = SourceStyle }; }

    public static readonly DependencyProperty LocationStyleProperty = RegisterStyle( "LocationStyle", MakeStyle( Colors.DarkRed ) );
    public Style LocationStyle { get { return (Style)GetValue( LocationStyleProperty ); } set { SetValue( LocationStyleProperty, value ); } }
    private Inline Location( string text ) { return new Run( text ) { Style = LocationStyle }; }

    public static DependencyProperty ChildIndentProperty = DependencyProperty.Register( "ChildIndent", typeof( double ), typeof( PrettyExceptionConverter ), new PropertyMetadata( 25d ) );
    public double ChildIndent { get { return (double)GetValue( ChildIndentProperty ); } set { SetValue( ChildIndentProperty, value ); } }

    #endregion

    #region Static helpers

    private static DependencyProperty RegisterStyle( string name, Style defaultStyle )
    {
        return DependencyProperty.Register(
            name,
            typeof( Style ),
            typeof( PrettyExceptionConverter ),
            new PropertyMetadata( defaultStyle ) );
    }

    private static Style MakeStyle( Color color )
    {
        return MakeStyle( color, null, null );
    }

    private static Style MakeStyle( Color color, FontStyle? fontStyle )
    {
        return MakeStyle( color, fontStyle, null );
    }

    private static Style MakeStyle( Color color, FontStyle? fontStyle, FontWeight? fontWeight )
    {
        var style = new Style();
        style.Setters.Add( new Setter( Run.ForegroundProperty, new SolidColorBrush( color ) ) );
        if ( fontStyle.HasValue ) style.Setters.Add( new Setter( Run.FontStyleProperty, fontStyle.Value ) );
        if ( fontWeight.HasValue ) style.Setters.Add( new Setter( Run.FontWeightProperty, fontWeight.Value ) );
        return style;
    }

    #endregion

    #region FlowDocument helpers

    /// <summary>
    /// Renders the specified <see cref="Exception"/>.
    /// </summary>
    /// <param name="ex">The ex.</param>
    /// <returns></returns>
    public FlowDocument Render( Exception ex )
    {
        var doc = new FlowDocument
        {
            FontFamily = new FontFamily( "Arial" ),
            FontSize = 14d,
            PagePadding = new Thickness( 0 )
        };

        doc.Blocks.AddRange( RenderExceptions( ex ) );

        return doc;
    }

    private Inline LineBreak()
    {
        return new LineBreak();
    }

    #endregion

    #region Rendering helpers

    /// <summary>
    /// Renders the <see cref="Exception"/> and inner Exceptions.
    /// </summary>
    /// <param name="ex">The ex.</param>
    /// <returns></returns>
    public IEnumerable<Block> RenderExceptions( Exception ex )
    {
        var indent = 0d;
        var hanging = ChildIndent / 2;

        for ( ; ex != null; ex = ex.InnerException )
        {
            foreach ( var block in RenderException( ex, indent, hanging ) ) yield return block;
            indent += ChildIndent;
        }
    }

    /// <summary>
    /// Renders the <see cref="Exception"/>.
    /// </summary>
    /// <param name="ex">The ex.</param>
    /// <param name="indent">The indent.</param>
    /// <param name="hanging">The hanging indent.</param>
    /// <returns></returns>
    public IEnumerable<Block> RenderException( Exception ex, double indent, double hanging )
    {
        var paragraph = new Paragraph
        {
            TextAlignment = TextAlignment.Left,
            IsHyphenationEnabled = false,
            TextIndent = -hanging,
            Margin = new Thickness( indent + hanging, 0, 0, 0 )
        };

        paragraph.Inlines.AddRange( RenderException( ex ) );

        yield return paragraph;
    }

    /// <summary>
    /// Renders the <see cref="Exception"/>.
    /// </summary>
    /// <param name="ex">The ex.</param>
    /// <returns></returns>
    public IEnumerable<Inline> RenderException( Exception ex )
    {
        if ( ex != null )
        {
            yield return Error( ex.Message );

            if ( ex.StackTrace != null )
            {
                yield return LineBreak();
                foreach ( var method in RenderStackTrace( ex.StackTrace ) ) yield return method;
            }
        }
    }

    /// <summary>
    /// Renders the stack trace.
    /// </summary>
    /// <param name="stackTrace">The stack trace.</param>
    /// <returns></returns>
    public IEnumerable<Inline> RenderStackTrace( string stackTrace )
    {
        if ( stackTrace != null )
        {
            var first = true;

            foreach ( var method in ExceptionParser.ParseStackTrace( stackTrace ) )
            {
                if ( !first ) yield return LineBreak();
                else first = false;

                foreach ( var inline in RenderMethod( method ) ) yield return inline;
            }
        }
    }

    /// <summary>
    /// Renders the method.
    /// </summary>
    /// <param name="method">The method.</param>
    /// <returns></returns>
    public IEnumerable<Inline> RenderMethod( ExceptionParser.Method method )
    {
        foreach ( var ns in method.Namespace.Split( '.' ) )
        {
            yield return Namespace( ns );
            yield return Separator( "." );
        }

        yield return MethodName( method.Name );
        yield return Bracket( "(" );

        if ( method.Arguments.Length > 0 )
        {
            yield return Space();

            var first = true;

            foreach ( var arg in method.Arguments )
            {
                if ( !first )
                {
                    yield return Bracket( "," );
                    yield return Space();
                }
                else first = false;

                yield return TypeName( arg.Type );
                yield return Space();
                yield return Argument( arg.Name );
            }

            yield return Space();
        }

        yield return Bracket( ")" );
    }

    #endregion

    #region IValueConverter Members

    public object Convert( object value, Type targetType, object parameter, System.Globalization.CultureInfo culture )
    {
        return ( value is Exception )
            ? (object)Render( (Exception)value )
            : RenderStackTrace( value as string );
    }

    public object ConvertBack( object value, Type targetType, object parameter, System.Globalization.CultureInfo culture )
    {
        throw new NotImplementedException();
    }

    #endregion
}

You can set the Style properties to change the appearance.  Here’s an example that puts the stack traces inside nested Expanders:

image

Maybe you’d prefer to render as HTML (useful for health monitoring emails etc):

public class PrettyExceptionPrint
{
    #region Properties

    public static readonly string ErrorStyle = "PEP_Error";
    private string Error( string text ) { return Span( text, ErrorStyle ); }

    public static readonly string NamespaceStyle = "PEP_Namespace";
    private string Namespace( string text ) { return Span( text, NamespaceStyle ); }

    public static readonly string MethodNameStyle = "PEP_MethodName";
    private string MethodName( string text ) { return Span( text, MethodNameStyle ); }

    public static readonly string BracketStyle = "PEP_Bracket";
    private string Bracket( string text ) { return Span( text, BracketStyle ); }

    public static readonly string SeparatorStyle = "PEP_Separator";
    private string Separator( string text ) { return Span( text, SeparatorStyle ); }

    public static readonly string SpaceStyle = "PEP_Space";
    private string Space() { return Span( " ", SpaceStyle ); }

    public static readonly string TypeNameStyle = "PEP_TypeName";
    private string TypeName( string text ) { return Span( text, TypeNameStyle ); }

    public static readonly string ArgumentStyle = "PEP_Argument";
    private string Argument( string text ) { return Span( text, ArgumentStyle ); }

    public static readonly string SourceStyle = "PEP_Source";
    private string Source( string text ) { return Span( text, SourceStyle ); }

    public static readonly string LocationStyle = "PEP_Location";
    private string Location( string text ) { return Span( text, LocationStyle ); }

    public double ChildIndent { get; set; }

    #endregion

    #region HTML helpers

    private string Span( object content )
    {
        return Span( content, null );
    }

    private string Span( object content, string css )
    {
        return string.Format( "<span{0}>{1}</span>", string.IsNullOrEmpty( css ) ? null : " class='" + css + "'", content );
    }

    private string LineBreak()
    {
        return "<br/>\n";
    }

    #endregion

    #region Rendering helpers

    /// <summary>
    /// Renders the <see cref="Exception"/> and inner Exceptions.
    /// </summary>
    /// <param name="ex">The ex.</param>
    /// <returns></returns>
    public IEnumerable<string> RenderExceptions( Exception ex )
    {
        var indent = 0d;
        var hanging = ChildIndent / 2;

        for ( ; ex != null; ex = ex.InnerException )
        {
            foreach ( var block in RenderException( ex, indent, hanging ) ) yield return block;
            indent += ChildIndent;
        }
    }

    /// <summary>
    /// Renders the <see cref="Exception"/>.
    /// </summary>
    /// <param name="ex">The ex.</param>
    /// <param name="indent">The indent.</param>
    /// <param name="hanging">The hanging indent.</param>
    /// <returns></returns>
    public IEnumerable<string> RenderException( Exception ex, double indent, double hanging )
    {
        foreach ( var markup in RenderException( ex ) ) yield return markup;
    }

    /// <summary>
    /// Renders the <see cref="Exception"/>.
    /// </summary>
    /// <param name="ex">The ex.</param>
    /// <returns></returns>
    public IEnumerable<string> RenderException( Exception ex )
    {
        if ( ex != null )
        {
            yield return Error( ex.Message );

            if ( ex.StackTrace != null )
            {
                yield return LineBreak();
                foreach ( var method in RenderStackTrace( ex.StackTrace ) ) yield return method;
            }
        }
    }

    /// <summary>
    /// Renders the stack trace.
    /// </summary>
    /// <param name="stackTrace">The stack trace.</param>
    /// <returns></returns>
    public IEnumerable<string> RenderStackTrace( string stackTrace )
    {
        if ( stackTrace != null )
        {
            var first = true;

            foreach ( var method in ExceptionParser.ParseStackTrace( stackTrace ) )
            {
                if ( !first ) yield return LineBreak();
                else first = false;

                foreach ( var inline in RenderMethod( method ) ) yield return inline;
            }
        }
    }

    /// <summary>
    /// Renders the method.
    /// </summary>
    /// <param name="method">The method.</param>
    /// <returns></returns>
    public IEnumerable<string> RenderMethod( ExceptionParser.Method method )
    {
        foreach ( var ns in method.Namespace.Split( '.' ) )
        {
            yield return Namespace( ns );
            yield return Separator( "." );
        }

        yield return MethodName( method.Name );
        yield return Bracket( "(" );

        if ( method.Arguments.Length > 0 )
        {
            yield return Space();

            var first = true;

            foreach ( var arg in method.Arguments )
            {
                if ( !first )
                {
                    yield return Bracket( "," );
                    yield return Space();
                }
                else first = false;

                yield return TypeName( arg.Type );
                yield return Space();
                yield return Argument( arg.Name );
            }

            yield return Space();
        }

        yield return Bracket( ")" );
    }

    #endregion
}

Then just add some CSS for the PEP_* classes to change the appearance:

<style>
    html, body { font-family: Arial; }
    .PEP_Namespace { color: darkblue; }
    .PEP_MethodName { color: black; font-weight: bold; }
    .PEP_Bracket { color: darkgreen; }
    .PEP_TypeName { color: teal; }
    .PEP_Argument { color: darkred; }
</style>

It’ll look something like this:

image

Rendering to XML is even easier:

public class PrettyExceptionPrint
{
    /// <summary>
    /// Renders the <see cref="Exception"/> and inner Exceptions.
    /// </summary>
    /// <param name="ex">The ex.</param>
    /// <returns></returns>
    public static IEnumerable<XElement> RenderExceptions( Exception ex )
    {
        for ( ; ex != null; ex = ex.InnerException ) yield return RenderException( ex );
    }

    /// <summary>
    /// Renders the <see cref="Exception"/>.
    /// </summary>
    /// <param name="ex">The ex.</param>
    /// <returns></returns>
    public static XElement RenderException( Exception ex )
    {
        return new XElement( "Exception",
            new XAttribute( "Type", ex.GetType() ),
            new XAttribute( "Message", ex.Message ),
            new XElement( "StackTrace", RenderStackTrace( ex.StackTrace ) ) );
    }

    /// <summary>
    /// Renders the stack trace.
    /// </summary>
    /// <param name="stackTrace">The stack trace.</param>
    /// <returns></returns>
    public static IEnumerable<XElement> RenderStackTrace( string stackTrace )
    {
        return from method in ExceptionParser.ParseStackTrace( stackTrace )
               select RenderMethod( method );
    }

    /// <summary>
    /// Renders the method.
    /// </summary>
    /// <param name="method">The method.</param>
    /// <returns></returns>
    public static XElement RenderMethod( ExceptionParser.Method method )
    {
        return new XElement( "Method",
            new XAttribute( "Namespace", method.Namespace ),
            new XAttribute( "Name", method.Name ),
            new XElement( "Arguments",
                from arg in method.Arguments
                select new XElement( "Argument",
                    new XAttribute( "Type", arg.Type ),
                    new XAttribute( "Name", arg.Name ) ) ) );
    }
}

Then you could transform it to HTML with some XSLT like this:

<?xml version="1.0" encoding="UTF-8" ?>

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" exclude-result-prefixes="xsl">

    <xsl:output method="html"/>

    <xsl:variable name="lineFeed" select="'&#xD;&#xA;'"/>

    <xsl:template match="/">

        <style>
            p.exception { border: 1px solid #AAAAAA; background-color: #FFAEB9; padding: 4px; }
            table.error { border: 1px solid lightsteelblue; border-collapse: collapse; font-size: 9pt; margin-bottom: 8px; }
            table.error th { text-align: left; background-color: lightsteelblue; padding: 4px 4px 4px 4px; }
            table.error td { border: 1px solid lightsteelblue; padding: 4px 4px 4px 4px; }
            .title { background-color: white; }
            .namespace { color: navy; }
            .method { font-weight: bold; }
            .type { color: green; }
            .var { color: gray; }
            .trace { padding-left: 2em; text-indent: -2em; font-size: 8pt; }
        </style>

        <xsl:copy-of select="Exceptions/Header/*"/>

        <!--
        <Exceptions>
            <Exception Type="type" Message="message">
                <StackTrace>
                    <Method Namespace="namespace" Type="type" Method="method">
                        <Argument Type="type" Name="name"/>
                    </Method>
                </StackTrace>
            </Exception>
        </Exceptions>
        -->
        <xsl:for-each select="Exceptions/Exception">
            <table class="error">
                <tr>
                    <th>
                        <xsl:value-of select="@Type"/>
                    </th>
                </tr>
                <tr>
                    <td>
                        <xsl:value-of select="@Message"/>
                    </td>
                </tr>
                <xsl:if test="StackTrace/Method">
                    <tr>
                        <td colspan="2">
                            <xsl:for-each select="StackTrace/Method">
                                <div class="trace">
                                    <span class="namespace">
                                        <xsl:if test="@Namespace">
                                            <xsl:value-of select="@Namespace"/>
                                            <xsl:text>.</xsl:text>
                                        </xsl:if>
                                        <xsl:if test="@Type">
                                            <xsl:value-of select="@Type"/>
                                            <xsl:text>.</xsl:text>
                                        </xsl:if>
                                        <span class="method">
                                            <xsl:value-of select="@Method"/>
                                        </span>
                                    </span>
                                    <xsl:text>(</xsl:text>
                                    <xsl:for-each select="Parameter">
                                        <xsl:if test="position()>1">,</xsl:if>
                                        <xsl:if test="@Type">
                                            <xsl:text> </xsl:text>
                                            <span class="type">
                                                <xsl:value-of select="@Type"/>
                                            </span>
                                        </xsl:if>
                                        <xsl:text> </xsl:text>
                                        <span class="var">
                                            <xsl:value-of select="@Name"/>
                                        </span>
                                    </xsl:for-each>
                                    <xsl:text> )</xsl:text>
                                </div>
                            </xsl:for-each>
                        </td>
                    </tr>
                </xsl:if>
            </table>
        </xsl:for-each>

    </xsl:template>

</xsl:stylesheet>

Disabling HTML Input Elements

If you’re using IE, you can disable all input elements inside a div by setting “disabled=true”.  Unfortunately (thanks to standards compliance nonsense 😉 ) it doesn’t work anywhere else.

If you’re using jQuery, take a look here.  If not, this bit of script might help:

function SetDisabledStates( container, newStates )
{
    var states = [];

    var replace = function( element )
    {
        states[ element.id ] = element.disabled;
        element.disabled = isArray( newStates ) ? newStates[ element.id ] : newStates;
    };

    var inputs = container.getElementsByTagName("input");
    for ( var i = 0; i < inputs.length; ++ i ) replace( inputs[ i ] );

    var selects = container.getElementsByTagName( "select" );
    for ( var i = 0; i < selects.length; ++ i ) replace( selects[ i ] );

    return states;
}

function isArray( obj )
{
   return obj.constructor.toString().indexOf("Array") != -1;
}

To preserve and override the states, do something like this:

var disabledStates = SetDisabledStates( myContainer, true );

 

Then to restore the states, do this:

SetDisabledStates( myContainer, disabledStates );

 

ASP.NET WebForm Routing with SiteMaps

I’ve updated my ASP.NET WebForm Routing demo with basic SiteMap support.  It’s targeting ASP.NET 3.5 SP1 and expects your WebForm (Page) to implement the IRoutablePage interface (or you can derive from RoutablePage).  If you’re using ASP.NET 4.0 you can omit this dependency (as RequestContext is already available on the Page 🙂 ).

SiteMap raises the SiteMapResolve event whenever its CurrentNode property is read.  This gives you an opportunity to determine the current node based on the route, without requiring a custom SiteMapProvider.  The example below takes the current route URL, removes any route values and looks for a matching SiteMapNode:

private SiteMapNode SiteMap_SiteMapResolve( object sender, SiteMapResolveEventArgs e )
{
    var routable = e.Context.CurrentHandler as IRoutablePage;

    if ( routable != null )
    {
        var rc = routable.Routing.RequestContext;
        var route = rc.RouteData.Route;
        var segments = route.GetVirtualPath( rc, null ).VirtualPath.Split( '/' );
        var path = "~/" + string.Join( "/", segments.Take( segments.Length - rc.RouteData.Values.Count ).ToArray() );

        return SiteMap.Provider.FindSiteMapNodeFromKey( path );
    }

    return null;
}

You create a .sitemap file in the usual way, but put your “routed” URLs in instead:

<?xml version="1.0" encoding="utf-8" ?>
<siteMap xmlns="http://schemas.microsoft.com/AspNet/SiteMap-File-1.0" >
    <siteMapNode url="~/" title="Home" description="Home">
        <siteMapNode url="~/Search" title="Search" description="Search" />
        <siteMapNode url="~/Details" title="Details" description="Details" />
        <siteMapNode url="~/NoMaster" title="No Master" description="No Master">
            <siteMapNode url="~/NoMaster/MySub" title="My Subpage" description="My Subpage"/>
        </siteMapNode>
    </siteMapNode>
</siteMap>

You can use the regular SiteMapPath control to show the current location.  However, you might prefer to render your own.  The sample project includes a couple of helper methods to achieve just that:

/// <summary>
/// Gets SiteMap path for the current request.
/// </summary>
/// <returns></returns>
public string SiteMapPath()
{
    var pages = SiteMap.CurrentNode.For( n => n != null, n => n.ParentNode ).Reverse().Select( n => SiteMapLink( n ) );

    return string.Join( " > ", pages.ToArray() );
}

/// <summary>
/// Gets a SiteMap link.
/// </summary>
/// <param name="node">The node.</param>
/// <returns></returns>
private string SiteMapLink( SiteMapNode node )
{
    var span = string.Format( "<span class=\"siteMapLink\">{0}</span>", node.Title );

    return ( node != SiteMap.CurrentNode )
        ? string.Format( "<a href=\"{0}\">{1}</a>", node.Url, span )
        : span;
}

You can read more about the “For” extension method in my earlier post.

Source code for the sample project is available on CodePlex.  You can try the demo online right here 🙂 .

IF + Silverlight + Linux … Almost!

Using the latest preview of Moonlight 2.0, SilverGlulxe (Interactive Fiction interpreter) is [almost] running on Linux!

Here’s what it currently looks like:

image

Text is missing formatting (there should be some bold and italic in there) and the Enter key doesn’t work, which makes it tricky to give it commands :)  Also the scale slider has some issues (text not reformatting to fit when it’s resized).  Other than that it’s looking great!

SilverGlulxe does some interesting things with threads, so it could be that’s behind the “Enter issue”.  I’ll probably wait a few more [Moonlight] previews, then have a closer look at my code… 🙂

More Extension Methods

Here are a couple more extension methods you may find useful:

Collection splitter:

Split a collection into a sequence of fixed-size groups:

/// <summary>
/// Splits the specified items into fixed-size groups.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="source">The source.</param>
/// <param name="size">The group size.</param>
/// <returns>Returns collection of groups.</returns>
public static IEnumerable<IGrouping<int, T>> Split<T>( this IEnumerable<T> source, int size )
{
    var index = 0d;

    return source.GroupBy( v => (int)( ( index++ ) / size ) );
}

You could use it like this:

var items = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 };
var chunks = items.Split( 5 );

“For” enumerator:

Generate a sequence of elements like a for loop:

/// <summary>
/// Generates a sequence of elements while <paramref name="predicate"/> is <see cref="true"/>.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="value">The initial value.</param>
/// <param name="predicate">A predicate to control continuation of the sequence.</param>
/// <param name="selector">A function to retrieve the next element.</param>
/// <returns>Returns a sequence of elements.</returns>
public static IEnumerable<T> For<T>( this T value, Predicate<T> predicate, Func<T, T> selector )
{
    while ( predicate != null && predicate( value ) )
    {
        yield return value;

        value = ( selector != null ) ? selector( value ) : default( T );
    }
}

It’s good for making non-enumerable sequences enumerable:

var pages = SiteMap.CurrentNode.For( n => n != null, n => n.ParentNode ).Reverse();

return string.Join( " > ", pages.Select( p => p.Title ).ToArray() );

Comments, suggestions and improvements always welcome!

More on Moonlight 2.0 Preview 1

Mark Monster made a great post about running Silverlight on Linux using Moonlight 2.0 Preview 1.  If you’re on Windows and just want a peek, there’s a nifty project called Portable Ubuntu for Windows (in IE8 you might need Compatibility View for the site).  You can install it like a regular app, then just fire up Ubuntu within Windows whenever you like – you don’t even need a Virtual PC 🙂

I threw a few of my demos at it and it’s great to see soft-body physics running pretty well (it’s a bit slower than Silverlight, but I’m sure they’ll improve that before release).  Some of my other demos didn’t do so well – interestingly they were apps that used WebClient (or maybe HttpWebRequest) to request something from the server.  No doubt that’ll be fixed soon too 🙂 (those Mono guys rock).

Here’s a screenshot (it’s Firefox on Linux, in a regular Vista window! 🙂 ):

image