Posts Tagged Silverlight

Making printing easier in Silverlight 4

Well, what an exciting week!  First Visual Studio 2010 is released, followed by Silverlight 4 yesterday!  Consequently, I was inspired to post about something new!  I’ve been spending some time looking at the new printing feature in Silverlight 4, and while on the surface it looks like a pretty simple and lower-level set of APIs, it’s possible to build rich frameworks on top of them for accomplishing common printing tasks.  In this post, I’ll take a look at an attempt I made (and added to SLaB) at building such a higher-level API over printing that makes printing collections of data easier.

Specifically, I’ve been building a CollectionPrinter control – a control that paginates, previews, and prints collections in a template-driven, designer-friendly way.  With this control, printing a collection can be entirely XAML-based and code-free!

For example, the image below shows some sample data (courtesy of the Blend 4 RC) within a DataGrid being printed across multiple pages.

A printed collection.

And you can see it in action by clicking here.

Of course, this is just an example of how one might want to build such a library, but it hopefully inspires some ideas you all might have around printing!  I’d love to know what you think!

So, how does printing work?

The printing APIs in Silverlight 4 center around the PrintDocument class.  SilverlightShow has a great article on the basics of using this class.  Basically, the workflow for printing in Silverlight 4 is the following:

  1. Create a new PrintDocument
  2. Attach a handler to the PrintDocument.PrintPage event, which will be called for each page you choose to print
  3. Call PrintDocument.Print(), passing in a document name (which will appear in the print spooler, for example)
  4. The user is prompted to print
  5. In each PrintPage call:
    1. If you have content ready to print:
      1. Choose some UI to print to the page and set the PageVisual in the PrintPageEventArgs
      2. If there are more pages to print, set HasMorePages on PrintPageEventPargs to true
    2. If you’re not ready to print yet:
      1. Set PageVisual to null
      2. Set HasMorePages to true (you will be called back after a short delay for the content – up to 8 attempts will be made to print a page before printing fails)

And that’s it!  For each page, Silverlight will render a bitmap of the visuals you provided in PageVisual (just like with WriteableBitmap), then send this bitmap to the printer.

That all seems pretty straightforward… why do I need anything more?

Well, it is pretty straightforward.  However, there’s a fair amount of work that you would have to do in order to create a coherently printed document:

  • Pagination – in order to figure out which items to print on each page, you will need to add items to the page, searching for the item that would flow beyond the end of the page.  This involves measuring/arranging pages and expanding templates, keeping track of the items you’ve printed so far, handling cases where items are too big for the page, and so on.
  • Page layout – it’s very common to want headers/footers on your pages, which further complicates the pagination process.
  • Page context – when printing a particular page, you often want some additional context, such as which items are on the page, the page number, the total number of pages (so that you can print “Page 1 / 10”, whether this is the first or last page, etc.  This context must be calculated and tracked, and is difficult when the printing process is progressive as with the above API (e.g. how can I print the first page without first having rendered all of the pages, so that I know what the total page count will be?)
  • Dealing with content not in the visual tree – often, you want to print UI directly from the visual tree, which is much easier.  But when printing controls that are NOT in the visual tree, life gets much more complicated.  As with WriteableBitmap, controls used as the PageVisual don’t get a full visual tree pass, meaning that events like FrameworkElement.Loaded don’t get raised.  Some controls use such events to do initialization, such as the charting controls in the Silverlight Toolkit.  In order to properly print arbitrary controls, it’s often helpful to actually place the control in the visual tree.
  • Handling animations – many controls (again, such as the charting controls in the Silverlight Toolkit) have animations and transitions by default.  For the charting controls, the data points being charted fade in by default.  If you try to print this when the control is created, you’ll get a blank chart!  One way to deal with this (if it’s the desired behavior) is to walk the visual tree, causing any running storyboard to SkipToFill.
  • Print preview – while it’s not really possible to give a full preview of what a printed document will look like (because you can’t find out what the size/margins of the page will be before printing has started), one can approximate it and at least provide a hint as to what to expect when printing begins.
  • Designability – Working with pages to print in Blend or Visual Studio’s designer can be somewhat difficult, especially if you need to write the code to handle the above cases.

As you can see, there are a number of complicating factors when doing sophisticated printing, but these can be abstracted away if the domain you’re given is specific enough.  In this case, we’re printing a collection, and we can use the items in the collection as the units between which we can break up pages.

OK, cool, I think I get it.  So, how does it work?

You can use the CollectionPrinter much like an ItemsControl.  It’s fundamentally DataTemplate-driven.  It has an ItemsSource and an ItemTemplate, but instead, you can specify a BodyTemplate (which, by default, is an ItemsControl :) ).  For headers, footers, and such, you can specify additional DataTemplates.

All of the DataTemplates are bound to a “CollectionPrintContext” which contains the following pieces of information:

  • CurrentItems – the set of items being printed on this page (this collection will be built up dynamically during printing/previewing)
  • CurrentPage/CurrentPageIndex – the page number (1- or 0-based) being printed
  • First/Last items (so that you can print “Items 1-10” or “Aa – Aardvark” on each page):
    • FirstItem/FirstItemIndex/FirstItemValue – the first item being printed on the page (1-based index, 0-based index, or actual value, respectively)
    • LastItem/LastItemIndex/LastItemValue – the last item being printed on the page (1-based index, 0-based index, or actual value, respectively)
  • PageCount – the total number of pages to print (note: this is a nullable int value and may not be provided if there isn’t time to finish pre-rendering all pages before the PrintPage callback limit is reached)
  • PageMargins – the margins of the page being printed
  • PrintableArea – the size of the space in which the page can be printed

The hope is that with these pieces of information to bind to, you can print rich pages for your users.  The rest is just creative use of bindings :)

To kick off printing, either call the Print() method on the CollectionPrinter or bind a button to the PrintCommand property on the CollectionPrinter for completely code-free printing!

The CollectionPrinter will attempt to address all of the issues above, spawning invisible popups to ensure that the UI actually has a moment to be in the visual tree, walking the visual tree to skip animations to fill, and most importantly paginating all of the items in the ItemsSource.

Let’s see it in action!

Using an ItemTemplate

The most basic case for printing with the CollectionPrinter is to use an ItemTemplate, just as you would with an ItemsControl.  The CollectionPrinter will happily handle cases where items are irregularly sized, so be as rich as you’d like!

For example:

ItemTemplate-based Printing

The XAML style for this CollectionPrinter is as follows:

<Style x:Key="PrintStyle"
        TargetType="SLaB:CollectionPrinter">
    <Setter Property="ItemTemplate">
        <Setter.Value>
            <DataTemplate>
                <Border BorderThickness="1"
                        BorderBrush="Gray">
                    <Grid>
                        <Grid.Resources>
                            <Style TargetType="TextBlock">
                                <Setter Property="FontSize"
                                        Value="12" />
                            </Style>
                        </Grid.Resources>
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition />
                            <ColumnDefinition />
                        </Grid.ColumnDefinitions>
                        <StackPanel Grid.Column="0"
                                    Margin="10">
                            <Image Source="{Binding Photo}"
                                   Height="{Binding Age}"
                                   Width="{Binding Age}"
                                   HorizontalAlignment="Left" />
                            <TextBlock Text="{Binding Name, StringFormat='Name: {0}'}" />
                            <TextBlock Text="{Binding Age, StringFormat='Age: {0}'}" />
                            <TextBlock Text="{Binding Address, StringFormat='Address: {0}'}"
                                       TextWrapping="Wrap" />
                        </StackPanel>
                        <toolkit:Chart Grid.Column="1"
                                       Title="{Binding Name}">
                            <toolkit:ScatterSeries DependentValuePath="X"
                                                   IndependentValuePath="Y"
                                                   Title="Values"
                                                   ItemsSource="{Binding Values}" />
                        </toolkit:Chart>
                    </Grid>
                </Border>
            </DataTemplate>
        </Setter.Value>
    </Setter>
    <Setter Property="HeaderTemplate">
        <Setter.Value>
            <DataTemplate> ... </DataTemplate>
        </Setter.Value>
    </Setter>
    <Setter Property="FooterTemplate">
        <Setter.Value>
            <DataTemplate> ... </DataTemplate>
        </Setter.Value>
    </Setter>
</Style>
<SLaB:CollectionPrinter x:Name="printer"
                        Style="{StaticResource PrintStyle}" />

All it takes to print based on this style is setting the ItemsSource (in this case to some Blend sample data) and binding a button to the PrintCommand on the control.  In my header and footer templates, I have controls that are bound to the CollectionPrintContext, and whose visibilities are determined by binding to things like IsFirstPage (see the source included in the SLaB download for more details).

Changing the BodyTemplate (and printing a DataGrid)

Now, that’s all wonderful if all I want to do is print something that looks like an ItemsControl.  But what can we do if we want to print a DataGrid (with column headers, etc.)?  Well, the CollectionPrinter doesn’t depend on being given an ItemsControl at any point.  Instead, it determines pagination based upon the DesiredSize of its content after measuring/arranging.  As a result, anything that grows as its bound content is changed will work with the CollectionPrinter.  You can easily print a DataGrid in this way, as I’ve done below:

DataGrid-based PrintingAnd the XAML barely changes from what you see above.  In this case, instead of using an ItemTemplate, I set a BodyTemplate, which is a DataGrid (with vertical scrolling disabled) with a variety of columns.  In this case, I’ll specify the DataTemplates inline:

<SLaB:CollectionPrinter xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk"
                        xmlns:chartingToolkit="clr-namespace:System.Windows.Controls.DataVisualization.Charting;assembly=System.Windows.Controls.DataVisualization.Toolkit"
                        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                        xmlns:SLaB="http://www.davidpoll.com/SLaB">
    <SLaB:CollectionPrinter.HeaderTemplate>
        <DataTemplate> ... </DataTemplate>
    </SLaB:CollectionPrinter.HeaderTemplate>
    <SLaB:CollectionPrinter.FooterTemplate>
        <DataTemplate> ... </DataTemplate>
    </SLaB:CollectionPrinter.FooterTemplate>
    <SLaB:CollectionPrinter.BodyTemplate>
        <DataTemplate>
            <sdk:DataGrid ItemsSource="{Binding CurrentItems}"
                          AutoGenerateColumns="False"
                          VerticalScrollBarVisibility="Disabled">
                <sdk:DataGrid.Columns>
                    <sdk:DataGridTextColumn Binding="{Binding Name}"
                                            Header="Name" />
                    <sdk:DataGridTextColumn Binding="{Binding Address}"
                                            Header="Address" />
                    <sdk:DataGridTextColumn Binding="{Binding Age}"
                                            Header="Age" />
                    <sdk:DataGridTemplateColumn Header="Image"
                                                IsReadOnly="True">
                        <sdk:DataGridTemplateColumn.CellTemplate>
                            <DataTemplate>
                                <Image Source="{Binding Photo}"
                                       Height="50"
                                       Width="50" />
                            </DataTemplate>
                        </sdk:DataGridTemplateColumn.CellTemplate>
                    </sdk:DataGridTemplateColumn>
                    <sdk:DataGridTemplateColumn Header="Values"
                                                Width="*"
                                                IsReadOnly="True">
                        <sdk:DataGridTemplateColumn.CellTemplate>
                            <DataTemplate>
                                <chartingToolkit:Chart Title="{Binding Name}">
                                    <chartingToolkit:ScatterSeries DependentValuePath="X"
                                                                   IndependentValuePath="Y"
                                                                   Title="Values"
                                                                   ItemsSource="{Binding Values}" />
                                </chartingToolkit:Chart>
                            </DataTemplate>
                        </sdk:DataGridTemplateColumn.CellTemplate>
                    </sdk:DataGridTemplateColumn>
                </sdk:DataGrid.Columns>
            </sdk:DataGrid>
        </DataTemplate>
    </SLaB:CollectionPrinter.BodyTemplate>
</SLaB:CollectionPrinter>

Explicitly providing individual pages

You can also use the CollectionPrinter for printing multiple pages explicitly (automatically dealing with all of the PrintPage callbacks, running animations, etc.) by providing a series of DataTemplates (one for each page) as the ItemsSource, changing the BodyTemplate to a ContentControl, and setting the maximum number of items to print per page to 1.  For example:

Individual Pages

And, of course, the corresponding XAML:

<SLaB:ObservableObjectCollection x:Key="Pages">
    <DataTemplate>
        <StackPanel>
            <TextBlock FontWeight="Bold"
                        FontSize="20">Lorem Ipsum</TextBlock>
            <TextBlock TextWrapping="Wrap"
                        Margin="10"
                        xml:space="preserve">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer ultrices cursus tortor ac egestas. Pellentesque semper lobortis enim, vel imperdiet dolor vehicula ac. Suspendisse auctor tempus molestie. Cras pulvinar sagittis libero, vel pretium ipsum consectetur sit amet. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Vestibulum feugiat nunc eget ante euismod ac facilisis velit aliquet. Vestibulum eget nulla magna, eget scelerisque ligula. Cras nec nisi faucibus leo fermentum euismod eu vel lacus. Etiam lacus massa, pulvinar id tempor eget, varius at lorem. Praesent venenatis nisi ac ipsum facilisis at suscipit magna sollicitudin. Phasellus placerat imperdiet hendrerit. Nulla ac risus velit. Sed orci lorem, imperdiet vel ultrices et, viverra ut leo. Mauris feugiat, diam eget mollis tempus, est leo pellentesque risus, vitae lacinia ante felis hendrerit elit. 

Suspendisse potenti. Donec dui justo, ultrices quis condimentum vel, bibendum vel nisi. Pellentesque suscipit fermentum dui vel sodales. Nulla vitae tortor vel orci posuere vestibulum. Curabitur non lacus quam. Nulla sit amet tempor libero. Integer dictum lectus ut sem adipiscing vitae fringilla felis accumsan. Mauris ut risus felis, ut pulvinar quam. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Integer fermentum, turpis sit amet tincidunt fermentum, metus mauris bibendum nisi, nec tincidunt purus enim nec nisi. Sed faucibus congue ultricies. Maecenas sed lorem id sem ornare imperdiet ut vitae libero. Curabitur mi diam, ornare sit amet dignissim eu, imperdiet sed nibh. Donec ultrices libero sed ipsum sollicitudin in dapibus elit rutrum. Nulla egestas tempus est, nec semper lacus sodales vel. Quisque consectetur turpis nunc, eu pretium felis. Etiam non adipiscing elit. 

Cras sit amet volutpat metus. Nunc eu augue eu urna placerat adipiscing in vel lacus. Etiam auctor orci nec dui adipiscing non viverra nisl gravida. In lacinia venenatis lobortis. Vestibulum dignissim, dolor ut feugiat ultricies, eros odio adipiscing augue, quis congue turpis augue quis mauris. Aliquam at ligula sem. Aenean eget arcu ac odio eleifend convallis. Aenean eu tellus ac eros placerat aliquam. Nam consectetur neque sed massa accumsan mollis. Pellentesque in mi erat, eget tristique elit. Praesent mattis magna sed est placerat bibendum venenatis nulla facilisis. Duis nec mollis nisi. Vestibulum et eros vitae felis vestibulum scelerisque. Donec venenatis, nulla vel rutrum tempus, purus nulla feugiat felis, eu semper diam nibh tincidunt metus. 

Praesent venenatis aliquet vulputate. In suscipit, nulla ut pulvinar ullamcorper, diam ligula sagittis enim, fermentum tempus nunc neque sed nisi. Vivamus aliquam rutrum scelerisque. Phasellus suscipit, quam sed suscipit pretium, massa nunc elementum lectus, et adipiscing arcu turpis sed dolor. Suspendisse potenti. Proin nisl mauris, sodales tincidunt ultricies sed, placerat quis enim. Nulla elementum nunc vel sapien venenatis venenatis vel eu ligula. Sed vitae erat ante. Etiam nec sapien nec sapien sagittis hendrerit. Duis at odio dolor. Sed condimentum euismod felis, ut congue dolor luctus quis. Duis non tellus enim. Quisque quis odio erat. Nulla nulla mi, dapibus ut euismod ut, adipiscing nec lacus. Aliquam faucibus dui at est accumsan ut laoreet erat pellentesque. Nullam id malesuada tellus. Donec et ligula tincidunt metus rutrum pretium. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; 

Sed lacinia dignissim scelerisque. Duis pharetra elit et nisl euismod viverra. Nam semper, purus ut luctus tincidunt, eros nisi aliquam nunc, non semper lorem enim sit amet augue. Vestibulum adipiscing tortor a magna tristique fringilla. Etiam porta volutpat odio, eu posuere velit mollis non. Mauris ut arcu quis lectus dapibus condimentum. Pellentesque non bibendum nisi. In hac habitasse platea dictumst. Maecenas laoreet lorem ut sem pellentesque id facilisis nibh pulvinar. Vivamus tempus erat placerat diam condimentum ac bibendum felis egestas. Quisque mollis hendrerit risus, ac euismod metus dapibus nec. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Phasellus quis ipsum euismod dolor pharetra viverra eget a augue. Morbi lorem enim, porta ut congue quis, pretium et enim.
            </TextBlock>
        </StackPanel>
    </DataTemplate>
    <DataTemplate>
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto" />
                <RowDefinition Height="*" />
            </Grid.RowDefinitions>
            <TextBlock FontWeight="Bold"
                        FontSize="20">Lorem Ipsum</TextBlock>
            <Image Grid.Row="1"
                    HorizontalAlignment="Center"
                    VerticalAlignment="Center"
                    Source="/ScratchPrintingProject;component/SLaB Logo.png" />
        </Grid>
    </DataTemplate>
</SLaB:ObservableObjectCollection>
<Style x:Key="PrintStyle"
        TargetType="SLaB:CollectionPrinter">
    <Setter Property="MaximumItemsPerPage"
            Value="1" />
    <Setter Property="BodyTemplate">
        <Setter.Value>
            <DataTemplate>
                <ContentControl ContentTemplate="{Binding CurrentItems[0]}"
                                HorizontalContentAlignment="Stretch"
                                VerticalContentAlignment="Stretch" />
            </DataTemplate>
        </Setter.Value>
    </Setter>
    <Setter Property="HeaderTemplate">
        <Setter.Value>
            <DataTemplate> ... </DataTemplate>
        </Setter.Value>
    </Setter>
</Style>
<SLaB:CollectionPrinter x:Name="printer"
                        ItemsSource="{StaticResource Pages}"
                        Style="{StaticResource PrintStyle}" />

Above, I have two pages: one with a bunch of text, and one with a large image on it.  I still get all of the context for my headers/footers, and can use this as a way to get the benefits of using the CollectionPrinter without being locked into an ItemsControl-like behavior.

Headers and Footers

Creating Headers and Footers for each page is also really easy with the CollectionPrinter – just specify a HeaderTemplate or a FooterTemplate.  These DataTemplates are bound to the CollectionPrintContext, which you can use to generate your header and footer info.  I use some ValueConverters (in one of the SLaB libraries, if you’d like to reuse them) to conditionalize Visibility of a title for a document to the first page, and to change colors/text for the other pages.  I also use the FirstItemValue/LastItemValue to provide a “Aa – Aardvark” (like you’d find in a dictionary) footer on each page.

For example, the sample pages above use the following XAML for their header/footers:

<SLaB:CollectionPrinter.HeaderTemplate>
    <DataTemplate>
        <StackPanel HorizontalAlignment="Stretch">
            <StackPanel.Resources>
                <SLaB:BoolConverter x:Key="BoolConverter" />
            </StackPanel.Resources>
            <StackPanel HorizontalAlignment="Right"
                        Orientation="Horizontal">
                <TextBlock Text="{Binding CurrentPage, StringFormat='{}Page {0} '}" />
                <TextBlock Text="{Binding PageCount, StringFormat='{}/ {0}'}" />
            </StackPanel>
            <TextBlock HorizontalAlignment="Center"
                        Visibility="{Binding IsFirstPage, Converter={StaticResource BoolConverter}}"
                        FontSize="32">This is a test document!</TextBlock>
            <TextBlock HorizontalAlignment="Center"
                        Visibility="{Binding IsLastPage, Converter={StaticResource BoolConverter}}"
                        FontSize="16">This is the last page!</TextBlock>
            <TextBlock HorizontalAlignment="Center"
                        FontSize="14">
                <TextBlock.Foreground>
                    <Binding Path="CurrentPage">
                        <Binding.Converter>
                            <SLaB:EvenOddConverter>
                                <SLaB:EvenOddConverter.Even>
                                    <SolidColorBrush Color="Blue" />
                                </SLaB:EvenOddConverter.Even>
                                <SLaB:EvenOddConverter.Odd>
                                    <SolidColorBrush Color="Red" />
                                </SLaB:EvenOddConverter.Odd>
                            </SLaB:EvenOddConverter>
                        </Binding.Converter>
                    </Binding>
                </TextBlock.Foreground>
                <TextBlock.Text>
                    <Binding Path="CurrentPage"
                                StringFormat="This page is {0}">
                        <Binding.Converter>
                            <SLaB:EvenOddConverter>
                                <SLaB:EvenOddConverter.Even>
                                    Even
                                </SLaB:EvenOddConverter.Even>
                                <SLaB:EvenOddConverter.Odd>
                                    Odd
                                </SLaB:EvenOddConverter.Odd>
                            </SLaB:EvenOddConverter>
                        </Binding.Converter>
                    </Binding>
                </TextBlock.Text>
            </TextBlock>
        </StackPanel>
    </DataTemplate>
</SLaB:CollectionPrinter.HeaderTemplate>
<SLaB:CollectionPrinter.FooterTemplate>
    <DataTemplate>
        <StackPanel HorizontalAlignment="Center"
                    Orientation="Horizontal">
            <TextBlock Text="{Binding FirstItemValue.Name}" />
            <TextBlock Text=" - " />
            <TextBlock Text="{Binding LastItemValue.Name}" />
        </StackPanel>
    </DataTemplate>
</SLaB:CollectionPrinter.FooterTemplate>

Nifty, isn’t it? :)

That’s a lot of XAML!  Do I have to write all of that by hand?

Of course not!  In fact, with the CollectionPrinter, Blend gives you a nice little print preview (especially if you use it as the root of a XAML file) and lets you modify the various templates directly in Blend, inline with the rest of the page!  You can adjust the “CurrentPageIndex” property to preview your CollectionPrinter configuration in the designer.

Editing templates within Blend

To accomplish this in Blend:

  • Select a CollectionPrinter on the designer (either making it the root of a XAML document or selecting it in your existing XAML)
  • Right-click the CollectionPrinter in the Objects and Timeline Window
  • Choose “Edit Additional Templates”
  • Choose a template to edit, and either edit a copy, edit the current template, or create an empty template.
  • Edit away!  Drag/drop your printed content!

So, what’s it all add up to?

It’s very common for business applications to want to print the data they’ve collected, and it’s not hard to see how that might be a chore.  Silverlight 4 introduced the printing feature, which allows printing at a very low level, but it also allows a huge amount of freedom in determining exactly what gets rendered to the page.  This freedom can make simple printing tasks a chore.  My hope is that the CollectionPrinter helps demonstrate how one might build a general-purpose (but more constrained than the built-in API) printing API.  I’d love to know what you think.  For me, it was primarily an experiment to see how close I could come to making printing large data sets easy and designable.  Does this come close to what you’d hope for?  What would your ideal be?

Sweet!  Can I try it?!

I’m glad you’re so enthused!  I’m tempted to say no, but wouldn’t torture you like that :) .  You can give it a shot using my latest build of Silverlight and Beyond.

You can see the live demos here (Requires Silverlight 4):

Printing samples

If you’d like to save time printing (yeah, it takes a while – it renders some pretty large bitmaps!), you can see some sample output files below.  Notice that the “Long” documents don’t have the total page count displayed, since it took longer to pre-render all of the pages than the PrintPage retry limit would permit:

  • ItemTemplate-based Document – Short (PDF 7.5 MB), Long (PDF 37.7 MB)
  • DataGrid-based Document – Short (PDF 7.9 MB), Long (PDF 37.0 MB)
  • Individual page-based Document – Download (PDF 5.8 MB)

Finally, some source code for you:

  • Live Sample (source — found in the SLaB v0.5 source under "ScratchPrintingProject")
  • SLaB v0.5 (includes source, a sample app, some tests, and binaries)

    • For the latest version, please check out SLaB on my Downloads and Samples page.
    • The v0.5 download of SLaB includes the following changes:
      • Updated for SL4 RTW
      • Added CollectionPrinter for printing collections of items
      • Fixed a bug with Sitemap-based controls that caused some pack Uris to be evaluated as "equivalent" (and thus highlighted) even when they were not
      • Added EvenOddConverter that allows you to select an arbitrary value based on whether the input value was even or odd
      • Other minor bugfixes

    Enjoy, and let me know if you have any questions, thoughts, or ideas!

    Remember, SLaB is just a collection of the samples and experimental components I’ve been putting together so that they’re all in one place.  I can’t make any guarantees about maintaining them, fixing bugs, not making breaking changes, etc., but you’re more than welcome to try them out, use them, and let them inspire your development (or show you what not to do if you really dislike something I’m doing!) :) .

    P.S. Do you know you talk to yourself in these blog posts?

    Yes, yes I do, Mr. “Person behind the headers of the sections in my blog posts who is actually me.” 

     

    UPDATE 4/25/2010: Thanks for the feedback in the comments on this post.  I have updated the CollectionPrinter to help with the DataGrid AutoGenerateColumns issue reported by “mb”.  It may still have problems if the ItemsSource is not an IEnumerable<T> (but rather just a plain IEnumerable), but I think this should cover the 90% case.

    You can download the newest bits here.  I also added a nice little utility method to SLaB that helps get a MethodInfo using a compiled Expression (using the LINQ expression libraries).  Please let me know if you encounter other issues!

    , , , ,

    118 Comments

    New in the Silverlight 4 RC: XAML Features

    Today, the Silverlight 4 RC was announced and made available to the masses.  But you may be asking yourself: what’s new since the beta?  Well, I’d like to dive into one of the areas where a bunch of new work was done to improve the development experience – the XAML Parser.  With Silverlight 4, we’ve done a significant overhaul of the XAML Parser, allowing us to add new features and improve consistency within the platform and with WPF’s XAML support.

    So, let’s take a quick walk through some of the new things you can do with Silverlight XAML as of the RC!  This is by no means an exhaustive list, but it’s definitely some of the bigger items.

    Direct Content

    This is one of those small inconsistencies with WPF that people hit almost immediately when they try to write Silverlight XAML after moving over from the WPF world.  Specifically, things like this now work in Silverlight:

    <Button>Click me!</Button>
    

    I know, I know – exciting, right!?  In Silverlight 3, you had to use attribute syntax or explicitly surround “Click Me!” in a string, like this:

    <Button Content="Click me!" />
    

    or…

    <Button xmlns:sys="clr-namespace:System;assembly=mscorlib">
        <sys:String>Click me!</sys:String>
    </Button>
    

    I know when I first made the move from WPF to Silverlight, this was among the most irksome and frustrating things about its XAML parser.  With Silverlight 4, that frustration is gone, and I can enjoy direct content in my controls once again!

    See the image below for more examples:

    A quick demo of direct content support in Silverlight 4 XAML.

    xml:space=”preserve”

    In previous versions of Silverlight, the XAML parser was rather liberal about how it applied whitespace.  In general, it didn’t discard extra whitespace as most XML parsers will.

    For example, the following XAML:

    <TextBlock>
        <TextBlock.Text>This
    Text
    Is
    On
    Separate
    Lines
        </TextBlock.Text>
    </TextBlock>
    

    Looked like this:

    Whitespace preservation in prior versions of Silverlight.

    The XAML parser in Silverlight 4 has finally corrected this behavior, and has also added support for xml:space=”preserve”.

    When you recompile your application with the Silverlight 4 RC, the same XAML will produce the following:

    image 

    To get back the old behavior, add xml:space=”preserve” to your XAML:

    <TextBlock xml:space="preserve">
        <TextBlock.Text>This
    Text
    Is
    On
    Separate
    Lines
        </TextBlock.Text>
    </TextBlock>
    

    Finally, you have control over your whitespace back!  The results are much more predictable, and you can now be explicit about what you want the text content to be.

    Please note: when you upgrade your application to Silverlight 4 (i.e. recompile for Silverlight 4), you’ll need to watch out for this change!

    ISupportInitialize

    If you were a fan of the ISupportInitialize interface in the full framework, it’s finally made its way into Silverlight!  The XAML parser in Silverlight will now call ISupportInitialize.BeginInit() and ISupportInitialize.EndInit() on your classes (if you implement the interface) before and after setting properties defined in XAML.  This allows you to wait to do work until after all of your properties have been set, allowing you to handle properties that would otherwise be order-dependent and add some validation that combinations of properties are valid.

    It’s a convenient and welcome functionality to have around, especially for those of us that like declarative programming and are happy to use XAML as our format for doing so!

    XmlnsDefinition attribute

    In Silverlight 3 and earlier, how many times did you find yourself in a situation like this?

    <UserControl x:Class="SilverlightApplication1.MainPage"
                 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                 xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
                 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
                 xmlns:controls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls"
                 xmlns:data="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data"
                 xmlns:input="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Input"
                 xmlns:navigationMappings="clr-namespace:System.Windows.Navigation;assembly=System.Windows.Controls.Navigation"
                 xmlns:navigation="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Navigation"
                 xmlns:ohmygoshtherearetoomanyofthese="clr-namespace:System.Windows.Controls.Primitives;assembly=System.Windows.Controls.Data"
                 mc:Ignorable="d"
                 d:DesignHeight="300"
                 d:DesignWidth="400">...
    

    And that’s just with some of the SDK assemblies!  Declaring xmlns’s in your XAML was a chore, and it was extremely easy to end up with a huge mess of them, as they require one definition per namespace/assembly.  Add in the toolkit controls and any custom libraries, and you’re easily looking at 20 lines of these definitions.  This is more than just a problem of convenience – it makes XAML intellisense a big problem as well.  Visual Studio starts its intellisense experience by waiting for an xmlns to be entered, such as “input:”.  Within that namespace, it will filter your options to valid tags for your context within the XAML.

    But, if you’re like me and like to break your libraries up into small pieces, this is not very helpful – I have to remember exactly which xmlns has which set of things that derives from the type I’m trying to create in XAML, and if I got it wrong, there’s not much there to help me.

    Silverlight 4 adds support for the XmlnsDefinitionAttribute in custom assemblies, and the SDK has been updated to take advantage of this.  Now, you can replace all of the code above with this:

    <UserControl x:Class="SilverlightApplication1.MainPage"
                 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                 xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
                 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
                 xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk"
                 mc:Ignorable="d"
                 d:DesignHeight="300"
                 d:DesignWidth="400">...
    

    Likewise, with the next update of the Silverlight Toolkit, you will likely see a similar change, allowing you access to all referenced toolkit controls from one xmlns!  Phew!  What a relief!

    With that in mind, I’ve also updated SLaB to use XmlnsDefinitionAttribute, which really helps clean up the use of those libraries with Silverlight 4.

    This is one of my favorite features, if only because it makes working with XAML so much simpler in Visual Studio, and now it’s available for you and other control/XAML-centric developers to use in your libraries as well!

    Xmlns flexibility

    Silverlight 3 required that your default namespace always be the following:

    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    

    This was sometimes irritating when you wanted to use XAML as a serialization format for something other than UI, since you had to preface every element with some other xmlns.  In Silverlight 4, you can now change the default xmlns namespace at will, which you can use to clean up your XAML and customize your serialization format as you wish.

    Custom IDictionary support

    In XAML, it’s supposed to be possible to add items to a dictionary just as you would a list by using the “x:Key” attribute.  In prior versions of Silverlight, the only case where this was allowed was when used inside of a ResourceDictionary.

    With Silverlight 4, anything that implements IDictionary can be used in XAML.  To this end, I’ve provided a simple BindableDictionary in my SLaB libraries that you can now use like so (although you could just as easily use a Dictionary<object, SomeType> or a custom implementation of IDictionary):

    <SLaB:BindableDictionary x:Key="ValueBag"
                                    xmlns:sys="clr-namespace:System;assembly=mscorlib">
        <sys:Double x:Key="Value Between Zero and 100">37.9184273</sys:Double>
        <sys:Boolean x:Key="A boolean">True</sys:Boolean>
    </SLaB:BindableDictionary>
    

    This is extremely useful for more complex XAML scenarios where you want a Dictionary rather than a List to be declared in XAML.  One example I’ve been wanting to play with: an INavigationContentLoader (it’s an obsession – I’m sick, I know :) ) that maps a protocol (e.g. http:// or pack://) to another INavigationContentLoader, allowing protocol-specific content loading within a single application.

    Non-DependencyProperty Attached Properties

    Although you’re probably used to seeing them as such, attached properties are actually completely separate from DependencyProperties (just as properties are distinct from DependencyProperties).  In order to style or bind to them, they must be DependencyProperties.  Otherwise, it’s just an API convention:

    using System.Collections.Generic;
    using System.Windows.Controls;
    
    namespace SilverlightApplication1
    {
        public class ClassWithAttachedProperty
        {
            private static Dictionary<Grid, int> values = new Dictionary<Grid, int>();
            public static int GetGridMetadata(Grid g)
            {
                return values[g];
            }
            public static void SetGridMetadata(Grid g, int someValue)
            {
                values[g] = someValue;
            }
        }
    }

    Which can then be used in XAML like so:

    <UserControl x:Class="SilverlightApplication1.MainPage"
                 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                 xmlns:my="clr-namespace:SilverlightApplication1">
        <Grid x:Name="LayoutRoot" my:ClassWithAttachedProperty.GridMetadata="150">
        </Grid>
    </UserControl>
    

    Note: I don’t recommend keeping a dictionary of Grid—>int around :) .  That’s a giant memory leak waiting to happen!  It’s just convenient for demonstrative purposes.

    Better error messages

    One of the biggest complaints about XAML in Silverlight has been its poor reporting of errors when you’ve done something wrong.  This should have significantly improved in Silverlight 4.  In general, you’ll get more context and much clearer error messages at runtime when you make a mistake in XAML.  In general, that feeling of “what the heck just happened?” should be lifting when it comes to working with XAML.  It’s still not perfect, but things have improved significantly.

    Wow, that’s a lot of stuff!

    Yep, there’s been plenty of change with the parser, and the improvements should lead to a better experience when doing declarative development using XAML in Silverlight!  Please let me know what you think!  What’s your favorite feature or biggest peeve?

    What about SLaB?

    I’ve updated SLaB for the Silverlight 4 RC.  There’s a bunch of new stuff in there that I hope to blog about soon.  In the meantime, here’s a snippet from the changelog:

    • For the latest version, please check out SLaB on my Downloads and Samples page.
    • The v0.4 download of SLaB includes the following changes:
      • Added ZipUtilities so that the contents of a zip file can be discovered
      • Updated XapLoader to use ZipUtilities, allowing all TPEs to work rather than being limited to single-file TPEs where the file is named the same as the zip
      • Added Sitemap-based controls: BreadCrumbNavigator and TreeViewNavigator
      • Added ChangeLinq libraries for working with INotifyCollectionChanged collections and LINQ
      • Added ObservableDictionary and BindableDictionary, which raise INotifyCollectionChanged and INotifyPropertyChanged events as the dictionary changes, making it more usable with Binding
      • Added a basic MEFContentLoader
      • Updated to use XmlnsDefinitionAttribute wherever possible
      • Added XmlnsDefinition attributes to all libraries and updated ScratchApplication to use them
      • Other minor bugfixes

    Enjoy, and let me know if you have any questions, thoughts, or ideas!

    Remember, SLaB is just a collection of the samples and experimental components I’ve been putting together so that they’re all in one place.  I can’t make any guarantees about maintaining them, fixing bugs, not making breaking changes, etc., but you’re more than welcome to try them out, use them, and let them inspire your development (or show you what not to do if you really dislike something I’m doing!) :) .

    , , , , ,

    19 Comments

    Silverlight 4 RC Announced at MIX 2010!

    If you’re not watching already, head on over to http://live.visitmix.com to watch the keynote, where Scott Guthrie has just announced the Silverlight 4 RC!

    So, for those of you who’ve been wondering where I’ve been with my blog for the last month, now you know :) .  There’s lots to look forward to, and I’ll be talking about some of what’s new and different in the Silverlight 4 RC soon.

    In the meantime, you can grab the bits here:  http://www.silverlight.net/getstarted/silverlight-4/

    It’s all very exciting!  Stay tuned for more!

    , , ,

    No Comments

    On-demand loading of assemblies with Silverlight Navigation – Revisited for Silverlight 4 Beta

    Way back in July, shortly after Silverlight 3 was released, I posted a technique that allowed you to use the Navigation framework in the SDK to load pages in dlls that would be downloaded as part of the navigation process.  The solution relied on two things: a workaround to the navigation framework’s inability to navigate to Pages in dynamically-loaded assemblies, and a derived version of the Frame class that hid many methods in order to orchestrate downloads of dlls and their dependencies.

    With Silverlight 4’s INavigationContentLoader extensibility point, we can address this scenario much more effectively, and are no longer locked into the workarounds and strict constraints that Silverlight 3’s navigation feature placed on us.  In this post, I’ll walk through the use of another ContentLoader I’ve been working on and look at how it simplifies building multi-xap applications.

    Getting a jump-start

    If you’d like to get started quickly and see the magic of multi-xap applications, follow these steps (note: Requires the Silverlight 4 Beta and an active internet connection) or download the source for the steps below here (you may need to fix up the references after downloading SLaB):

    1. Download and extract SLaB
    2. Open Visual Studio 2010 and create a new Silverlight 4 project using the Silverlight Navigation Application template.  When prompted to create a corresponding ASP.NET web application, just click “OK” and let Visual Studio create a web project for you.
    3. Add a reference to SLaB.Navigation.ContentLoaders.Xap.dll from the extracted Binaries folder.
    4. Add the following line of code to App.xaml.cs: PackUriParser.Initialize();
    5. Replace the Frame in MainPage.xaml with the following code (new code is bold/italic– the rest is identical to the default project xaml except for formatting):
    <navigation:Frame x:Name="ContentFrame"
                      Style="{StaticResource ContentFrameStyle}"
                      Source="/Home"
                      Navigated="ContentFrame_Navigated"
                      NavigationFailed="ContentFrame_NavigationFailed">
        <navigation:Frame.UriMapper>
            <uriMapper:UriMapper>
                <uriMapper:UriMapping Uri=""
                                      MappedUri="/Views/Home.xaml" />
                <uriMapper:UriMapping Uri="/{pageName}"
                                      MappedUri="/Views/{pageName}.xaml" />
            </uriMapper:UriMapper>
        </navigation:Frame.UriMapper>
        <navigation:Frame.ContentLoader>
            <SLaB:XapContentLoader xmlns:SLaB="clr-namespace:SLaB.Navigation.ContentLoaders.Xap;assembly=SLaB.Navigation.ContentLoaders.Xap"
                                   EnableCrossDomain="True" />
        </navigation:Frame.ContentLoader>
    </navigation:Frame>
    

    1. Run the application.
    2. Replace “#/Home” in your browser’s address bar with “#pack://http:,,open.depoll.com,SimpleApplication,SimpleApplication.xap/SimpleApplication;component/Depoll.xaml?Source=http://open.depoll.com&File=wildlife.wmv”
    3. Wait for a moment, and then enjoy the (admittedly underwhelming) show!

    Whoa, what just happened?!

    You just created an application that loaded a page in another xap!  It took only two actions to make this happen: registering the “PackUriParser” and setting the Frame’s ContentLoader to a XapContentLoader with cross-domain access enabled.  When you added the “pack://…” to the browser’s address bar, you told the content loader to downlaod a xap at http://open.depoll.com/SimpleApplication/SimpleApplication.xap (which is on my personal site, and has a very permissive cross-domain policy) and load the page “Depoll.xaml”.

    The XapContentLoader makes loading pages in other xaps (and reducing your download sizes) as easy as coming up with a URI that points to the page.  Read on to learn more about how to use the XapContentLoader and some of the problems associated with multi-xap applications.

    Why load pages in external xaps?

    This has always been a particularly interesting scenario to me.  The size of your application’s xap file can have a big impact on your users’ experience with your Silverlight application.  Since the entire xap and its dependencies (e.g. cached assemblies) must download before users can interact with your application, keeping the xap’s size small means users can start using your application sooner and are less likely to give up and click away.

    Partitioning your application into smaller-sized chunks that download only when users attempt to access that piece of functionality can also save bandwidth (since users need not download parts of the application they won’t use).

    The folks behind both MEF (Package catalogs) and Prism (Modules) have great libraries that help you accomplish this partitioning.  To me, navigation is a natural delineator of application functionality – Pages represent pieces of functionality your users can access, so triggering loading of other assemblies/xaps as part of navigation means that users will get the pieces of the application when they want to use them.

    In this post, I will be outlining a XapContentLoader, which makes loading pages in other Xaps a 2-lines-of-code problem to solve.  I’ve added this ContentLoader and some related utilities to SLaB, so you can use it yourself or play with the code if you like!

    XapLoader

    The first component I needed to make this scenario work was a way to download and load xap files.  As such, I wrote a XapLoader utility.  The strategy I used for loading xaps was the following:

    1. Download the xap
    2. Read the AppManifest.xaml within the xap to locate the assemblies therein
    3. Load all assemblies within the xap
    4. Download zip files for the “ExtensionParts” within the AppManifest (used for the “cached assemblies” feature)
    5. Load the assembly associated with each zip file

    Why worry about ExtensionParts?  My hope is to use this feature to solve the shared dependency problem.  For example, the System.Windows.Controls.Data.dll file in the SDK (containing the DataGrid control) is a fairly large assembly (446 kb uncompressed).  I’d like to avoid having users download this assembly when the app first loads, so I partition my application into four xaps: Main.xap, Foo.xap, Bar.xap, and Baz.xap.  Foo.xap and Bar.xap both use the DataGrid control.  Since I have no guarantees about the order in which my users will access the application, I need to make sure both of those xaps have access to that assembly, so I partition my application as follows:

    Multiple xaps have a shared dependencyThis is a bit painful, since now when I download both Foo.xap and Bar.xap, I’m forced to download System.Windows.Controls.Data.dll twice!  Instead, if I turn on the assembly caching feature introduced in Silverlight 3, I can get both Foo.xap and Bar.xap to point to the same System.Windows.Controls.Data.dll.  By taking it out of the actual xaps and making it a separate download, I need only download it once (in fact, the browser’s cache will take care of it for me!).  Now, my application is partitioned in this way:

    Xaps with a cached assembly dependency

    And now I’ve effectively partitioned the application without keeping redundant libraries around that would increase the overall download size of the application.

    My implementation allows you to do this (using the same mechanism as “assembly caching”), but has the following limitations:

    • Cached assembly .zip files must contain only one assembly per zip.
    • The name of the .zip file must be the same as the assembly name (replacing “.dll” with “.zip”)

    This loader returns a “Xap” object, which has all of the loaded assemblies related with the xap, a “Manifest”, which contains the same data as the Deployment defined in AppManifest.xaml within the xap, and a means to get the original streams from which each assembly was loaded (for use in keeping local copies, if that’s what you’d like to do).  The XapContentLoader, described below, uses this loader to find and load xaps that would contain additional pages.

    XapContentLoader

    The XapContentLoader is an implementation of INavigationContentLoader that will download an external xap and load a page in one of its assemblies.  This content loader uses a slightly modified version of the pack uri scheme (which I’m probably abusing a bit here, but oh well :) ).  Technically, it only takes two lines of code/XAML to use the XapContentLoader:

    First, add the following line to your Application’s startup code (usually in App.xaml.cs), which registers the pack uri scheme with the built-in Uri class, so that you can create pack Uris in code and XAML without having exceptions be thrown:

    PackUriParser.Initialize();

    Then, in XAML:

    <navigation:Frame x:Name="ContentFrame"
                      Source="/Views/Home.xaml">
        <navigation:Frame.ContentLoader>
            <xapLoader:XapContentLoader />
        </navigation:Frame.ContentLoader>
    </navigation:Frame>
    

    And that’s it!  Alright, that’s all.  See you later…

    Ok, ok, I won’t leave you hanging like that!  Let’s look at how you would now use this content loader to load a page in another xap.  First, a quick review of the pack uri scheme as I understand it:

    pack://<authority>/<path>

    There are three parts to this Uri:

    • “pack://” – this is the scheme name of the Uri.  All absolute pack Uris begin with this.
    • “<authority>” – this is actually another Uri (just replace “/” with “,” and escape any other necessary characters).  For the XapContentLoader, this is a path to some xap.  For example, I might use the following authority: “http:,,www.davidpoll.com,xaps,MyApplication.xap”.  This indicates that I’ll be loading a page within the xap at that location.  I also support two other “special” authorities: “application:///” and “siteoforigin://”.  “application:///” cannot have an additional path attached to it, since I translate this as the location of the initial xap from which the application was loaded.  It’s basically equivalent to Application.Current.Host.Source.  “siteoforigin://” is replaced with the directory from which the application’s xap was loaded.  As such, any of the following authorities would be valid:
      • http:,,www.davidpoll.com,xaps,MyApplication.xap
      • https:,,www.davidpoll.com,securexaps,MySecureApplication.xap
      • application:,,,
      • siteoforigin:,,MyApplication.xap (equivalent to the first item in this list if MyApplication.xap was the entry point xap)
    • “<path>” – this is the path within the xap.  Usually, this looks like: “/SomeLibrary;component/Views/Page1.xaml”.  If no assembly name is provided, I assume they are referring to the “EntryPointAssembly” referred to in the AppManifest.xaml file of the xap.

    Some examples of valid Uris to navigate to (all equivalent assuming MyApplication.xap was the initial xap):

    • /Views/Page1.xaml
    • pack://application:,,,/Views/Page1.xaml
    • pack://application:,,,/MyApplication;component/Views/Page1.xaml
    • pack://http:,,www.davidpoll.com,xaps,MyApplication.xap/Views/Page1.xaml
    • pack://http:,,www.davidpoll.com,xaps,MyApplication.xap/MyApplication;component/Views/Page1.xaml
    • pack://siteoforigin:,,MyApplication.xap/Views/Page1.xaml
    • pack://siteoforigin:,,MyApplication.xap/MyApplication;component/Views/Page1.xaml

    Unfortunately, coming up with these Uris can be a bit of a pain, so I’ve provided a custom type of UriMapping that works on a per-xap basis to make this simpler:

    <navigation:Frame.UriMapper>
        <navUtils:UriMapper>
            <xapLoader:PackUriMapping XapLocation="siteoforigin://TernaryXap.xap"
                                      Uri="/remote/{assemblyname}/{path}"
                                      MappedPath="/{path}" />
        </navUtils:UriMapper>
    </navigation:Frame.UriMapper>
    

    Now, I needn’t ever actually write a pack uri myself.  Instead, I can have hyperlinks like “/remote/TernaryXap/Views/Page1.xaml”, and these mappings will generate the appropriate pack uris for you.  They work just like the UriMapping built into the SDK in terms of replacing values in the MappedPath.  “{assemblyname}” is a special token (that you could omit if you set the PackUriMapping.AssemblyName property) that allows you to make the assembly name part of your path.

    Enhancing the experience

    Great, so now we’ve got navigation integrated with on-demand xap loading.  It takes very few lines of code, and is quick to set up.  Note that my xaps have no concrete knowledge of one another (even the hyperlinks are just text, and users could manually enter other hyperlinks).  But web requests are slow, and could fail.  How can we improve the experience around this type of navigation?

    Well, I’ve got two ideas for you that you might recognize: BusyIndicator and ErrorPageLoader.

    Since web requests can fail (lost connectivity, bad links, servers being down, or other random problems), we should make sure users don’t see exceptions under those circumstances.  For this, we’ll whip out the ErrorPageLoader from our trusty utility belt and use it to load a local error page if something goes wrong:

    <loaders:ErrorPageLoader>
        <loaders:ErrorPageLoader.ContentLoader>
            <xapLoader:XapContentLoader />
        </loaders:ErrorPageLoader.ContentLoader>
        <loaders:ErrorPageLoader.ErrorContentLoader>
            <navigationLoader:PageResourceContentLoader />
        </loaders:ErrorPageLoader.ErrorContentLoader>
        <loaders:ErrorPage ErrorPageUri="/Views/ErrorPage.xaml" />
    </loaders:ErrorPageLoader>
    

    You’ll notice that I’ve used the built-in (SDK) PageResourceContentLoader as my ErrorContentLoader.  I did this because, presumably, something bad has happened with the XapContentLoader, so I want to use a ContentLoader with low likelihood of failure in order to display an error page.

    Next, users shouldn’t be left hanging while they wait for a link to load.  We can use the BusyIndicator control in the toolkit in order to let the user know that something is going on.  The XapContentLoader has an “IsBusy” property as well as a “Progress” property.  We can make the BusyIndicator’s progress bar display progress and appear by binding it to these properties on the XapContentLoader.  The following handy XAML accomplishes this:

    <toolkit:BusyIndicator IsBusy="{Binding ContentLoader.ContentLoader.IsBusy, ElementName=ContentFrame}"
                           DisplayAfter="0:0:0.1">
        <toolkit:BusyIndicator.BusyContent>
            <Grid>
                <Grid.RowDefinitions>
                    <RowDefinition Height="Auto" />
                    <RowDefinition Height="Auto" />
                </Grid.RowDefinitions>
                <TextBlock Text="Loading page..." Margin="0,0,0,4" />
                <ProgressBar Grid.Row="1"
                             Minimum="0"
                             Maximum="1"
                             Height="25"
                             Value="{Binding ContentLoader.ContentLoader.Progress, ElementName=ContentFrame}" />
            </Grid>
        </toolkit:BusyIndicator.BusyContent>
        <toolkit:BusyIndicator.ProgressBarStyle>
            <Style TargetType="ProgressBar">
                <Setter Property="Visibility" Value="Collapsed" />
            </Style>
        </toolkit:BusyIndicator.ProgressBarStyle>
        <navigation:Frame x:Name="ContentFrame"
                          Source="/Views/Home.xaml">
            <navigation:Frame.ContentLoader>
                <loaders:ErrorPageLoader>
                    <loaders:ErrorPageLoader.ContentLoader>
                        <xapLoader:XapContentLoader />
                    </loaders:ErrorPageLoader.ContentLoader>
                    <loaders:ErrorPageLoader.ErrorContentLoader>
                        <navigationLoader:PageResourceContentLoader />
                    </loaders:ErrorPageLoader.ErrorContentLoader>
                    <loaders:ErrorPage ErrorPageUri="/Views/ErrorPage.xaml" />
                </loaders:ErrorPageLoader>
            </navigation:Frame.ContentLoader>
        </navigation:Frame>
    </toolkit:BusyIndicator>
    

    Note that I’ve hidden the BusyIndicator’s default progress bar, and added one to its BusyContent that binds to the XapContentLoader’s progress.  Now, when users navigate to pages and have to wait for them to load, they are presented with the BusyIndicator and a progress bar letting them know how much is left to be downloaded.  The application continues to appear responsive, and users can continue to work with the rest of the application.

    Cross-domain considerations and limiting access

    We’ve now unleashed a lot of power.  Your application can now load pages in any xap that Silverlight can get access to using a WebClient.  If the Frame control you’re using integrates with the browser, users can type any uri into the browser to navigate to, and right now, the XapContentLoader will blindly go and load their code.

    This could be a bit of a security issue (I’m no expert, but this one sticks out like a sore thumb).

    Suppose domain supersecret.com has a cross-domain policy that allows my domain (and only my domain), davidpoll.com, to access services on it.  My domain is using the XapContentLoader, and can load pages in any xap that WebClient can access.  A developer whose domain is evil.com realizes this, and decides to try to get access to supersecret.com’s services.  He’s able to do this by:

    • Adding a cross-domain policy that allows davidpoll.com to access xaps on evil.com
    • Pointing the Silverlight application on davidpoll.com to his xap on evil.com (this will work, since WebClient just checks evil.com’s cross-domain policy)
    • Now that evil.com’s xap has been loaded and is running on davidpoll.com’s application, he can access supersecret.com’s services (since the evil.com’s code is now running on davidpoll.com)!

    Because of this problem, I’ve disabled cross-domain loading on the XapContentLoader by default.  You can re-enable it, but please be aware of this potential problem.  The AuthContentLoader is a convenient way to restrict access to domains you don’t want users to be able to access just by changing the Uri.  Here, I’ve enabled cross-domain access, but restricted access only to my other domain (this one happens to be my personal website that I don’t update too often :) ):

    <auth:AuthContentLoader>
        <auth:AuthContentLoader.ContentLoader>
            <xapLoader:XapContentLoader EnableCrossDomain="True" />
        </auth:AuthContentLoader.ContentLoader>
        <auth:NavigationAuthorizer>
            <auth:NavigationAuthRule UriPattern="pack://((siteoforigin:,,)|(authority:,,)|(http:,,open\.depoll\.com)).*">
                <auth:Allow Users="*" />
            </auth:NavigationAuthRule>
            <auth:NavigationAuthRule UriPattern="pack://.+:,,.*">
                <auth:Deny Users="*" />
            </auth:NavigationAuthRule>
        </auth:NavigationAuthorizer>
    </auth:AuthContentLoader>
    

    This allows users to access xaps on my site (open.depoll.com), while denying access to other pack uris.  Coupled with the ErrorContentLoader, a consistent, safe experience can be provided for users.

    MEF and Prism

    At this point, you might be asking yourself why I rolled my own solution to this problem?  MEF and Prism both have very effective implementations for modularization of applications, and you could certainly build very similar ContentLoaders based upon their libraries for loading xaps (in fact, I hope to see folks out there do so! :) ).  Really, the reason I rolled my own libraries for this is twofold:

    • Support for cached assemblies – I wanted to make sure to attempt to solve the shared dependency problem described earlier
    • Size – it’s important to keep the size of the bootstrapping assemblies small in order to reap the benefits of dynamic loading.  The total size of the libraries required for the XapContentLoader is about 25k compressed.  I’d still like to pare that down, so if folks have ideas, let me know :)

    If you’ve got ideas for how best to use MEF or Prism with INavigationContentLoader, let me know!  If you’re already using them for modularization and want to integrate them with navigation, this is possible in Silverlight 4 thanks to INavigationContentLoader.

    The goods

    As you know by now if you’ve been following my blog, I never leave you stranded without code and a live sample!  Take a look!

    An application using XapContentLoader

    Click around using the links on top and see what happens to the Uri in the browser’s address bar.  You’ll note the cross-domain access as well as the use of QueryStrings (which still work, of course!).  Open up Fiddler or some other tool to verify that xaps and zip files for cached assemblies are only downloaded once thanks to browser caching.  Enjoy it!

    Finally, some source code for you:

  • Live Sample (source — found in the SLaB v0.3 source under "ScratchApplication")
  • SLaB v0.3 (includes source, a sample app, some tests, and binaries)
    • For the latest version, please check out SLaB on my Downloads and Samples page.
    • The v0.3 download of SLaB includes the following changes:
      • Added more extensible UriMapper that replicates the built-in UriMapper’s behavior but allows UriMappings to be more extensible.
      • Added UiUtilities, the first of which allows you to execute a function on the UI thread and block the calling thread until it has completed (safe to call even from the UI thread)
      • Added XapLoader, which downloads a Xap and any "cached assemblies"
      • Added XapContentLoader, which uses the XapLoader to navigate to pages in downloaded assemblies
      • Added PackUri utilities so that pack Uris can be used to download and navigate to pages in Xaps
      • Signed all assemblies (public binaries use a private key, another key is distributed with source)
      • Added a build task that will generate extmap files for all libraries — now assembly caching works with SLaB assemblies
      • Fixed a bug with the AuthLoader where all rules would be run, even if one rule already allowed access
      • Other minor bugfixes

    Enjoy, and let me know if you have any questions, thoughts, or ideas!

    Remember, SLaB is just a collection of the samples and experimental components I’ve been putting together so that they’re all in one place.  I can’t make any guarantees about maintaining them, fixing bugs, not making breaking changes, etc., but you’re more than welcome to try them out, use them, and let them inspire your development (or show you what not to do if you really dislike something I’m doing!) :) .

  • , , , , , ,

    74 Comments

    Opening up Silverlight 4 Navigation: Authentication/Authorization in an INavigationContentLoader

    Continuing my series of posts on ways to use INavigationContentLoader (a feature in the Silverlight 4 Beta SDK), in this post, I’ll explore another idea for a composable INavigationContentLoader that protects access to your pages based upon the credentials of the user of your Silverlight application.  To demonstrate its use, I’m using a WCF RIA Services application (purely for their Authentication/Authorization provisions).

    The basic problem is this: as you would with a website, you build an application with multiple pages – some of which are intended for anonymous users or users in a particular role and some are intended for users with greater privileges.  How do you prevent such users from navigating to those pages and give them an appropriate user experience if they do try to reach pages for which they are not authorized?

    To that end, I’ve added another INavigationContentLoader to my SLaB examples – the “AuthContentLoader” – that checks to see whether your user is authorized to view a page before navigating to it.  If the user is not authenticated, the AuthContentLoader throws, resulting in a NavigationFailed event on the Frame/NavigationService that you can handle in order to provide better feedback to your users if there is an UnauthorizedAccessException.

    Cool – How does it work?

    If you’re familiar with authorization with ASP.NET (settings you might add to your web.config file), this ContentLoader’s use should come pretty easily to you.  For example, in ASP.NET, you might have the following in your web.config:

    <system.web>
      <authorization>
        <allow roles="Role1, Role2" />
        <allow users="SuperUser"/>
        <deny roles="Role3, Role4" users="LessImpressiveUser" />
        <deny users="?" />
        <allow users="*"/>
      </authorization>
    </system.web>
    

    I tried to keep the API for the AuthContentLoader quite similar.  With the AuthContentLoader, you can:

    • Allow or deny users access to pages that match a Regular Expression (allowing you to scope the authorization to particular pages)
    • Allow or deny users access to pages based upon their roles, authentication status, and user name
    • Wrap any other INavigationContentLoader in order to protect access

    To accomplish this, there are just a few steps:

    1. Set your Frame.ContentLoader to an AuthContentLoader.
    2. Bind AuthContentLoader.Principal to any IPrincipal (in my example, I’ll use the built-in authentication context in WCF RIA Services).  This is what the ContentLoader uses to check the user’s credentials.
    3. Add rules for your pages.  If no rule matches a page, the page is assumed to be broadly accessible.

    Put all of this together, and you end up with something like this:

    <navigation:Frame ...>
        <navigation:Frame.ContentLoader>
            <authLoader:AuthContentLoader Principal="{Binding User, Source={StaticResource WebContext}}">
                <authLoader:NavigationAuthorizer>
                    <authLoader:NavigationAuthRule UriPattern="^/Views/About\.xaml\??.*$">
                        <authLoader:Deny Users="?" />
                        <authLoader:Allow Users="*" />
                    </authLoader:NavigationAuthRule>
                    <authLoader:NavigationAuthRule UriPattern="^/Views/RegisteredUsersPage.xaml\??.*$">
                        <authLoader:Allow Roles="Registered Users" />
                    </authLoader:NavigationAuthRule>
                </authLoader:NavigationAuthorizer>
            </authLoader:AuthContentLoader>
        </navigation:Frame.ContentLoader>
    </navigation:Frame>
    

    Here, we have rules for About.xaml (with any querystring) and RegisteredUsersPage.xaml (again, with any querystring).  Note that the UriPattern is a Regex, and must also take into account the possible query strings that could be attached to the request.  I know it looks a little arcane, but it does the trick, and allows you to specify whole sets of Uri’s that share the same authorization characteristics (e.g. any page in the “PrivateViews” folder could be restricted to Administrators).

    The XAML snippet above places restrictions on two pages:

    • About.xaml – anonymous (Principal == null || Principal.Identity == null || Principal.Identity.IsAuthenticated == false) users are denied, and all other users are allowed
    • RegisteredUsersPage.xaml – only users that belong to the “Registered Users” role are allowed
    • Users are granted access to all other pages

    Like the ErrorPageLoader, the AuthContentLoader can take another ContentLoader (but defaults to the PageResourceContentLoader if none is specified), and will delegate the actual loading (after the user has been authorized) to that loader.

    And there you go!  Easy as pie!  Feel free to give it a try and play around with it!  Happy New Year!

    Wait!  Don’t stop yet!  Please tie this back to your other posts!

    Relax!  I won’t leave you hanging!  After all, what’s the point of having two composable INavigationContentLoaders (AuthContentLoader and ErrorPageLoader) if you’re not going to use them together? :)

    I made a very specific choice with the AuthContentLoader: when the user doesn’t have permission to access a page, the AuthContentLoader throws an exception.  That’s very convenient when you want to use the ErrorPageLoader to handle authentication failures.  The AuthContentLoader, by default, throws an UnauthorizedAccessException, so we can handle that exception explicitly using the ErrorPageLoader.  In this case, we’ll redirect users who visit an unauthorized page to another page that directs them to log in.  The XAML for this follows:

    <errorLoader:ErrorPageLoader>
        <errorLoader:ErrorPageLoader.ErrorPages>
            <errorLoader:ErrorPage ExceptionType="UnauthorizedAccessException" ErrorPageUri="/LoginPage" />
        </errorLoader:ErrorPageLoader.ErrorPages>
        <errorLoader:ErrorPageLoader.ErrorContentLoader>
            <errorLoader:ErrorRedirector />
        </errorLoader:ErrorPageLoader.ErrorContentLoader>
        <errorLoader:ErrorPageLoader.ContentLoader>
            <authLoader:AuthContentLoader Principal="{Binding User, Source={StaticResource WebContext}}">
                <authLoader:NavigationAuthorizer>
                    <authLoader:NavigationAuthRule UriPattern="^/Views/About\.xaml\??.*$">
                        <authLoader:Deny Users="?" />
                        <authLoader:Allow Users="*" />
                    </authLoader:NavigationAuthRule>
                    <authLoader:NavigationAuthRule UriPattern="^/Views/RegisteredUsersPage.xaml\??.*$">
                        <authLoader:Allow Roles="Registered Users" />
                    </authLoader:NavigationAuthRule>
                </authLoader:NavigationAuthorizer>
            </authLoader:AuthContentLoader>
        </errorLoader:ErrorPageLoader.ContentLoader>
    </errorLoader:ErrorPageLoader>
    

    Cool!  Now, if users navigate to a page they’re not authorized to see, they’ll be redirected to a login page!  Note the exception type being handled (UnauthorizedAccessException), the ErrorPageUri (unmapped, because we’re using the ErrorRedirector, which will cause a brand new navigation to take place – including mapping), and the use of the ErrorRedirector to redirect to a new Uri rather than just loading alternate content (allowing the user to come back to the page rather than assuming that the login page is the real content).

    Ok, we’re now in pretty good shape, but users can still hit problems besides the UnauthorizedAccessException, such as attempting to load a page that does not exist.  In my last post, we solved this using an ErrorPageLoader, and we’ll do the same this time.  In these cases, I actually do want to load alternate content rather than redirect to an error page, since this is the typical experience with web error pages (e.g. a 404 page).  I can accomplish this by adding a second ErrorPageLoader to the mix, like so:

    <errorLoader:ErrorPageLoader>
        <errorLoader:ErrorPageLoader.ErrorPages>
            <errorLoader:ErrorPage ErrorPageUri="/Views/ErrorPage.xaml" />
        </errorLoader:ErrorPageLoader.ErrorPages>
        <errorLoader:ErrorPageLoader.ContentLoader>
            <errorLoader:ErrorPageLoader>
                <errorLoader:ErrorPageLoader.ErrorPages>
                    <errorLoader:ErrorPage ExceptionType="UnauthorizedAccessException" ErrorPageUri="/LoginPage" />
                </errorLoader:ErrorPageLoader.ErrorPages>
                <errorLoader:ErrorPageLoader.ErrorContentLoader>
                    <errorLoader:ErrorRedirector />
                </errorLoader:ErrorPageLoader.ErrorContentLoader>
                <errorLoader:ErrorPageLoader.ContentLoader>
                    <authLoader:AuthContentLoader Principal="{Binding User, Source={StaticResource WebContext}}">
                        <authLoader:NavigationAuthorizer>
                            <authLoader:NavigationAuthRule UriPattern="^/Views/About\.xaml\??.*$">
                                <authLoader:Deny Users="?" />
                                <authLoader:Allow Users="*" />
                            </authLoader:NavigationAuthRule>
                            <authLoader:NavigationAuthRule UriPattern="^/Views/RegisteredUsersPage\.xaml\??.*$">
                                <authLoader:Allow Roles="Registered Users" />
                            </authLoader:NavigationAuthRule>
                        </authLoader:NavigationAuthorizer>
                    </authLoader:AuthContentLoader>
                </errorLoader:ErrorPageLoader.ContentLoader>
            </errorLoader:ErrorPageLoader>
        </errorLoader:ErrorPageLoader.ContentLoader>
    </errorLoader:ErrorPageLoader>
    

    And that’s it!  Now, when users access your site, they’re protected from any type of error and have access restricted appropriately!  Give it a shot with my live sample application:

    Note: Log in with User = “Test”, Password = “_Testing”

    Also Note: I’ve been having a little trouble with my server, so if this doesn’t work for you, feel free to try downloading and running the code locally (see below for a link).

    Live Sample Application

    First, try clicking on the restricted links at the top of the application and observe the login page that appears.  Next, click the broken link (or type something random into your browser after the “#” in the URL), and observe the error page that appears.  Now, log in to the application (feel free to create your own account or use the test account above – any account should give you access to the pages that are currently restricted), and try visiting those pages again!

    Ok, I think I’m beginning to get it.  Give me the goods so I can go play with it!

    As always, I can’t leave you empty-handed.  I’ve added the AuthContentLoader to SLaB, which you can download to get both binaries and code.  I’ve also included the source (which requires WCF RIA Services and the Silverlight 4 Beta) for the demo application I linked to above:

    • Live Sample (source)
    • SLaB v0.0.2 (includes source, a sample app, some tests, and binaries)
      • For the latest version, please check out SLaB on my Downloads and Samples page.
      • The v0.0.2 download of SLaB includes:
        • AuthContentLoader and related classes
        • ErrorPageLoader moved into its own assembly (to keep its size down)

    In Conclusion…

    In my humble opinion, there is a lot of power in composing these types of INavigationContentLoaders.  With the AuthContentLoader, you can prevent Uri’s from being loaded in the context of your application.  Whether you’re just trying to provide a good user experience or actually prevent users from reaching certain Uri’s (e.g. dynamically downloaded XAPs/assemblies that should only be accessible if you’re logged in) a ContentLoader like this could be useful.  The AuthContentLoader works well with WCF RIA Services, which provides easy access through its “WebContext” concept to a User that represents both an IPrinciple and an IIdentity.  Stay tuned for more ideas – I’m still working on some fun little experiments.  Hopefully these posts inspire some cool ideas!  If you’ve got ‘em, I’d love to hear ‘em!

    Remember, SLaB is just a collection of the samples and experimental components I’ve been putting together so that they’re all in one place.  I can’t make any guarantees about maintaining them, fixing bugs, not making breaking changes, etc., but you’re more than welcome to try them out, use them, and let them inspire your development (or show you what not to do if you really dislike something I’m doing!) :) .

    Disclaimer

    The AuthContentLoader does not protect your data in any way – it simply provides a user experience around navigating to pages that may have restricted access.  Users can still open up your XAP and see its contents (so the XAML/code for those restricted pages isn’t protected), but it does do an effective job of limiting what users are able to access within your application.  You should still be aggressively securing your application if need be.  :)

    Don’t you have something else to say?

    Oh, yeah!  Happy New Year!  Here’s to (and good riddance to) the noughties, bring on the teens!  I hope you all have had a wonderful holiday season, and enjoy prosperity in the year ahead.

    , , , , ,

    43 Comments