Chris Cavanagh’s Blog

Interactive Fiction + Linux + Moonlight

November 2, 2009 · Leave a Comment

Moonlight 2 beta 7 (at time of writing) is getting really close to working with SilverGlulxe / SilverFyre!  The only issue right now is none of the story text appears… ;) (could just be a simple font issue).

FYI one of the cool things about SilverGlulxe is its handy “Transcript” option (link at bottom of game).  It gives you a transcript of the story so far (including your commands) and lets you send a copy (including XML source) by email.  Apparently it’s very useful for story debugging [if you’re an Interactive Fiction writer].

kick it

→ Leave a CommentCategories: .NET

Silverlight VisualBrush and rounded corners

September 24, 2009 · 4 Comments

As you know Silverlight 3 doesn’t support VisualBrush, which can make things like this pretty tricky.

Here’s my attempt at a workaround :)   It’s a control called VisualImage which can be pointed at any element and exposes it as a WriteableBitmap.  You could bind an Image to this to create a reflection effect like this (don’t forget to look at Jeff Prosise’s sample):

image

You could even bind it to an ImageBrush… if it supported binding.  To work around this, VisualImage can be bound to the ImageBrush instead.  One application of this is for clipped, rounded corners on any element (here’s the WPF way):

image

You can try a live sample here (source on CodePlex).

Although VisualImage is a Control, it doesn’t render anything itself – it just acts as an intermediary between your visual and whatever you want to bind it to.  Here’s everything you need (also available here):

/// <summary>
/// VisualImage
/// </summary>
public class VisualImage : Control
{
    #region Visual DependencyProperty

    public static readonly DependencyProperty VisualProperty = DependencyProperty.Register(
        "Visual",
        typeof( FrameworkElement ),
        typeof( VisualImage ),
        new PropertyMetadata( OnVisualChanged ) );

    public FrameworkElement Visual
    {
        get { return (FrameworkElement)GetValue( VisualProperty ); }
        set { SetValue( VisualProperty, value ); }
    }

    private static void OnVisualChanged( DependencyObject obj, DependencyPropertyChangedEventArgs args )
    {
        var visualImage = obj as VisualImage;
        visualImage.OnVisualChanged( args );
    }

    private void OnVisualChanged( DependencyPropertyChangedEventArgs args )
    {
        if ( args.OldValue != null ) ( (FrameworkElement)args.OldValue ).SizeChanged -= VisualImage_SizeChanged;
        if ( args.NewValue != null )
        {
            var visual = (FrameworkElement)args.NewValue;
            visual.SizeChanged += VisualImage_SizeChanged;
            PrepareBitmap( (int)visual.RenderSize.Width, (int)visual.RenderSize.Height );
        }
    }

    private void VisualImage_SizeChanged( object sender, SizeChangedEventArgs e )
    {
        PrepareBitmap( (int)e.NewSize.Width, (int)e.NewSize.Height );
    }

    #endregion // Visual DependencyProperty

    #region Bitmap DependencyProperty

    public static readonly DependencyProperty BitmapProperty = DependencyProperty.Register(
        "Bitmap",
        typeof( WriteableBitmap ),
        typeof( VisualImage ),
        null );

    public WriteableBitmap Bitmap
    {
        get { return (WriteableBitmap)GetValue( BitmapProperty ); }
        set { SetValue( BitmapProperty, value ); }
    }

    #endregion // Bitmap DependencyProperty

    #region ImageBrush DependencyProperty

    public static readonly DependencyProperty ImageBrushProperty = DependencyProperty.Register(
        "ImageBrush",
        typeof( ImageBrush ),
        typeof( VisualImage ),
        null );

    public ImageBrush ImageBrush
    {
        get { return (ImageBrush)GetValue( ImageBrushProperty ); }
        set { SetValue( ImageBrushProperty, value ); }
    }

    #endregion // VisualBrush DependencyProperty

    /// <summary>
    /// Initializes a new instance of the <see cref="VisualImage"/> class.
    /// </summary>
    public VisualImage()
    {
    }

    /// <summary>
    /// Prepares the bitmap.
    /// </summary>
    /// <param name="width">The width.</param>
    /// <param name="height">The height.</param>
    private void PrepareBitmap( int width, int height )
    {
        Bitmap = new WriteableBitmap( width, height );
        Invalidate();
    }

    /// <summary>
    /// Invalidates the VisualImage and causes WriteableBitmap to be refreshed.
    /// </summary>
    public void Invalidate()
    {
        if ( Bitmap != null && Visual != null )
        {
            Array.Clear( Bitmap.Pixels, 0, Bitmap.Pixels.Length );
            Bitmap.Render( Visual, this.RenderTransform );
            Bitmap.Invalidate();

            if ( ImageBrush != null && ImageBrush.ImageSource != Bitmap )
            {
                ImageBrush.ImageSource = Bitmap;
            }
        }
    }
}

For performance reasons it only refreshes the WriteableBitmap when the target Visual’s size changes.  You can call the Invalidate() method to force a refresh (consider calling it from CompositionTarget.Rendering if you want it to refresh every frame).

Here’s how to get rounded corners on anything (similar to WPF technique, with added VisualImage and named ImageBrush):

                <Grid HorizontalAlignment="Center" VerticalAlignment="Center">
                    <Border x:Name="mask" Background="White" CornerRadius="20" Padding="10"/>
                    <local:VisualImage Name="visualImage" Visual="{Binding ElementName=mask}" ImageBrush="{Binding ElementName=brush}"/>
                    <Image Source="http://farm2.static.flickr.com/1429/1430528819_edb63b79a6.jpg">
                        <Image.OpacityMask>
                            <ImageBrush x:Name="brush"/>
                        </Image.OpacityMask>
                    </Image>
                </Grid>

And here’s a reflection:

                <TextBlock x:Name="myText" FontSize="96">Hello</TextBlock>
                <local:VisualImage Name="reflectImage" Visual="{Binding ElementName=myText}"/>
                <Image Source="{Binding Bitmap, ElementName=reflectImage}" RenderTransformOrigin="0.5,0.2">
                    <Image.RenderTransform>
                        <ScaleTransform ScaleY="-0.8"/>
                    </Image.RenderTransform>
                    <Image.OpacityMask>
                        <LinearGradientBrush StartPoint="0.5,0" EndPoint="0.5,1">
                            <GradientStop Offset="0" Color="#00FFFFFF"/>
                            <GradientStop Offset="1" Color="#80FFFFFF"/>
                        </LinearGradientBrush>
                    </Image.OpacityMask>
                </Image>

kick it

→ 4 CommentsCategories: .NET

YouCube 3.0 (Chromium WebBrowser) – Source Code

September 8, 2009 · 7 Comments

Source code for YouCube 3.0 (WPF 3D Chromium-based WebBrowser) is now on CodePlex.  See the related WPF Chromium WebBrowser project for the Awesomium wrapper source.

kick it

→ 7 CommentsCategories: .NET

WPF 3D Chromium Browser

August 27, 2009 · 18 Comments

UPDATE: Now shows separate browser on each face! (a fast PC will help :) ).  Click a face in “browse” mode to select it and change the URL (or interact with it).  Source code available on CodePlex (see related post).

There’s an updated version of YouCube (WPF 3D Web Browser) available based on my Awesomium / Chromium control.  Try it here!

image

If you prefer you can run the application locally instead (ZIP here).

image

kick it

→ 18 CommentsCategories: .NET

WPF Chromium WebBrowser source code!

August 25, 2009 · 77 Comments

The source code for my WPF Chromium WebBrowser control is now available on CodePlex.

The source includes:

Cjc.ThreeDeemium – The sample application, currently without any 3D features.  Go figure.
Cjc.ChromiumBrowser – The WPF Chromium WebBrowser control.  Depends on Cjc.AwesomiumWrapper and the two Awesomium / Chromium C++ DLLs (Awesomium.dll and icudt38.dll).
Cjc.AwesomiumWrapper – A Managed C++ / CLI wrapper around Awesomium.  This is much easier to maintain than the old P/Invoke stuff, and is almost nice to look at :)
Awesomium – C++ headers and libraries for Awesomium / Chromium.

There’s also a bonus project :) :

Cjc.WebSnapshot – A small command-line utility using Cjc.AwesomiumWrapper to snapshot a URL (you need to specify a full URL including the scheme – http://chriscavanagh.wordpress.com etc).

→ 77 CommentsCategories: .NET

A Real WPF WebBrowser

August 25, 2009 · 140 Comments

UPDATE: Source code available!  See post here.  Fixed keyboard / Javascript bug :)   Added some sample pixel shader effects! (including HatchingEffect by Charles Bissonnette).  If you need Awesomium_d.dll (for debug builds) you can get it here.

Being able to render and interact with webpages within WPF opens up some great opportunities.  While WPF already includes a WebBrowser, it’s just a wrapper around IE’s ActiveX control.  Unfortunately this prevents it playing nicely with WPF’s layout system :(

Mixing Google’s Chromium project, a great wrapper called Awesomium and a little WPF pixie dust, we finally have one :) :

WPF Chromium Control

You can try the ClickOnce application here.  Source is available on CodePlex (discussed here).  You should find it works great with Flash and Silverlight (assuming plug-ins already installed) but currently it might choke on XBAPs and Java applets.

If you prefer to run the binaries locally, you can get them here :)

image

image

For more fun, have a look at YouCube 3!

kick it

→ 140 CommentsCategories: .NET

Silverlight 3 + ClearType

July 9, 2009 · 6 Comments

As we knew, Silverlight 3 includes ClearType support.  Now it’s released we can finally see it in action :) :

image

More importantly, it works great with SilverGlulxe!

→ 6 CommentsCategories: .NET

WPF 2D Physics Plus

June 25, 2009 · 1 Comment

Rohit Gupta is working on some great enhancements to the WPF 2D Physics demo I posted.  Features include:

  • Click to remove a body from the world
  • Click and “push” the body in any given direction

Be sure to take a look!  The project is here on CodePlex and you can run it as a ClickOnce app here.

→ 1 CommentCategories: .NET

MVC IsAuthorized and AuthorizedActionLink

June 11, 2009 · 6 Comments

UPDATE: Fixed broken IsAuthenticated logic (thanks Travis).

Robert Dean published a great post last year about his Security Aware Html.ActionLink.  It allowed you to conditionally render links based on the [AuthorizeAttribute] assigned to a controller’s action methods.

Here’s an alternative implementation based on expressions, so you get type safety, intellisense and slightly simpler code :)   First, a helper to determine if an action is authorized:

public static class AuthorizationExtensions
{
    private static Dictionary<Expression, AuthorizeAttribute[]> expressionAuthorizers = new Dictionary<Expression, AuthorizeAttribute[]>();

    /// <summary>
    /// Determines whether the specified action is authorized.
    /// </summary>
    /// <typeparam name="TController">The type of the controller.</typeparam>
    /// <param name="helper">The helper.</param>
    /// <param name="actionMethod">The action method.</param>
    /// <returns>
    ///     <c>true</c> if the specified helper is authorized; otherwise, <c>false</c>.
    /// </returns>
    public static bool IsAuthorized<TController>( this HtmlHelper helper, Expression<Action<TController>> action )
    {
        var call = action.Body as MethodCallExpression;

        if ( call == null ) return false;

        var authorizers = expressionAuthorizers.ContainsKey( action )
            ? expressionAuthorizers[ action ]
            : expressionAuthorizers[ action ] = GetAttributes<AuthorizeAttribute>( call );

        return ( authorizers.Length > 0 )
            ? authorizers.All( a => a.IsAuthorized( helper.ViewContext.HttpContext.User ) )
            : true;
    }

    /// <summary>
    /// Gets the specified attributes for an action method.
    /// </summary>
    /// <param name="call">The call.</param>
    /// <returns></returns>
    private static TAttribute[] GetAttributes<TAttribute>( MethodCallExpression call ) where TAttribute : Attribute
    {
        return call.Object.Type.GetCustomAttributes( typeof( TAttribute ), true )
            .Union( call.Method.GetCustomAttributes( typeof( TAttribute ), true ) )
            .Cast<TAttribute>()
            .ToArray();
    }

    /// <summary>
    /// Determines whether the specified <see cref="AuthorizeAttribute"/> authorizes the specified user.
    /// </summary>
    /// <param name="authorize">The <see cref="AuthorizeAttribute"/>.</param>
    /// <param name="user">The user.</param>
    /// <returns>
    ///     <c>true</c> if the specified user is authorized; otherwise, <c>false</c>.
    /// </returns>
    public static bool IsAuthorized( this AuthorizeAttribute authorize, IPrincipal user )
    {
        if ( !user.Identity.IsAuthenticated ) return false;

        var users = authorize.Users.SplitString();
        if ( users.Length > 0 && !users.Contains( user.Identity.Name, StringComparer.OrdinalIgnoreCase ) ) return false;

        var roles = authorize.Roles.SplitString();
        if ( roles.Length > 0 && !roles.Any( user.IsInRole ) ) return false;

        return true;
    }

    /// <summary>
    /// Splits and trims the specified string.
    /// </summary>
    /// <param name="original">The original.</param>
    /// <returns></returns>
    public static string[] SplitString( this string original )
    {
        return string.IsNullOrEmpty( original )
            ? new string[ 0 ]
            : original.Split( '.' ).Select( s => s.Trim() ).Where( s => !string.IsNullOrEmpty( s ) ).ToArray();
    }
}

With that, it’s possible to write markup like this:

<% if ( Html.IsAuthorized<ModelingController>( c => c.Index() ) ) {%>
    <div>My conditional content...</div>
<%}%>
 

If you’re using ASP.NET MVC Futures, it’s straightforward to write an authorized ActionLink:

public static class LinkExtensions
{
    /// <summary>
    /// Render an HTML action link if authorized by the target action.
    /// </summary>
    /// <typeparam name="TController">The type of the controller.</typeparam>
    /// <param name="helper">The helper.</param>
    /// <param name="action">The action.</param>
    /// <param name="linkText">The link text.</param>
    /// <returns></returns>
    public static string AuthorizedActionLink<TController>( this HtmlHelper helper, Expression<Action<TController>> action, string linkText )
        where TController : Controller
    {
        return AuthorizedActionLink<TController>( helper, action, linkText, null );
    }

    /// <summary>
    /// Render an HTML action link if authorized by the target action.
    /// </summary>
    /// <typeparam name="TController">The type of the controller.</typeparam>
    /// <param name="helper">The helper.</param>
    /// <param name="action">The action.</param>
    /// <param name="linkText">The link text.</param>
    /// <param name="htmlAttributes">The HTML attributes.</param>
    /// <returns></returns>
    public static string AuthorizedActionLink<TController>( this HtmlHelper helper, Expression<Action<TController>> action, string linkText, object htmlAttributes )
        where TController : Controller
    {
        var routeValuesFromExpression = ExpressionHelper.GetRouteValuesFromExpression<TController>( action );

        return helper.IsAuthorized( action )
            ? helper.RouteLink( linkText, routeValuesFromExpression, new RouteValueDictionary( htmlAttributes ) )
            : null;
    }
}

NOTE – You could do the same thing without MVC Futures, but would need a little more code (you’d need something like ExpressionHelper’s GetRouteValuesFromExpression method).

Here’s a markup example:

<%= Html.AuthorizedActionLink<ModelingController>( c => c.Index(), "Modeling" ) %>
 

→ 6 CommentsCategories: .NET

Pretty Printing Exceptions

May 31, 2009 · 1 Comment

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>

→ 1 CommentCategories: .NET