Friday, October 21, 2011

How to track currently deployed code version (revision from source control)


General rule of thumb is that at any moment in time you should be able to answer question: Which version (revision in svn) of code is currently deployed on production?
 
In this post I will present four approaches which can be used to determine this. I will start from simplest but least recommended ending with recommended but also relatively easy to implement solution. 

Introduction

 To be able to provide answer for question regarding deployed code version you can:
  1. remember version
  2. update svn_revision.txt file on production server
  3. create tag in subversion
  4. store revision in number in assembly

1. Remembering - Storing in human memory
I don’t trust even myself that I will manage remember whether 1789 or 1798 revision was deployed yesterday. If I find someone with better short-time memory than my I still would not be convinced to this approach... Forget this way…
 

2. Updating svn_revision.txt file
You can promise to each other in your team that you will update this file with current revision just after deploying new version to production, but… Remember you should have consistent deployment procedure for each server so don’t forget to update on:

  • production
  • staging
  • acceptance test environment
  • integration environment (where each build is dropped from build server)
It’s not the case if you forget but when you forget to update this. I can bet that when you’re in hurry deploying hotfix to hotfix when site is down you won’t care about such small detail as revision version in text file.
 

3. Creating tag in subversion
It’s common practice to create tag to associate revision with specific application version (check svn book). It can be easily used to track which version goes to production and later any team member can check repository. It seems to be more convenient than storing in text file on server but:

  • if we consistently treat deployment to each environment we should also tag deployment to each of them, It can cause creating many tag for version which has never reached even staging environment
  • It’s hard to track rollbacks on production environment. Even though we have last tag for revision 1701 we can be sure that production environment was not rolled back to version 1700 yesterday during night
  • It requires manual process to create tag or having it done by deployment script, what means we have to access SVN repository from production environment – not recommended and very often not possible.

4. Storing revision number in assembly
This solution assumes revision number is embedded in assembly. Very often we already have version number by AssemblyVersion attribute in AssemblyInfo.cs set to
 

[AssemblyVersion(1.7.2.0)]
 

Common practice is to update it manually: when big release is planned first or second number is upgraded; when minor fix is release only third or fourth number is changed- see semVer).
 

What I propose is to adapt this notation to follow:

Crucial here is that it must be done automatically during build. Only then we can remove human factor and avoid updating this.
 

This approach has following advantages
  • It’s easy to figure out on any environment  what version is currently deployed
  • Even if you rollback or do hotfix current SVN revision is up to date
  • No room for human error, since it’s done automatically
  • Access to SVN is not needed during deployment
  • Revision number can be determined on each environment in same way (production, staging, test etc.)

Implementation
Implementation presented below assumes SVN is used but this approach can be used with virtually any version control system (as long as it has notion of revision/check-in/version number)
 

Msbuild script presented below:
-    Verifies current revision from working copy
-    Updates [AssemblyInfo(…)] attribute
-    Builds project with revision number embedded in assembly

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" >
  <UsingTask TaskName="SvnInfo" AssemblyFile="tools\build\MSBuild.Community.Tasks.dll"/>
  <UsingTask TaskName="AssemblyInfo" AssemblyFile="tools\build\AssemblyInfoTask.dll"/>

  <Target Name="setup-version" >
    <SvnInfo LocalPath="." ToolPath="tools\svn">
      <Output TaskParameter="LastChangedRevision" PropertyName="LastChangedRevision"/>
    </SvnInfo>

    <!-- it will number like 1789 -->
    <Message Text="Last changed revision is: $(LastChangedRevision)"/>

    <AssemblyInfo AssemblyInfoFiles="src\CommonAssemblyInfo.cs" 
           AssemblyRevision="$(LastChangedRevision)"
           AssemblyFileRevision="$(LastChangedRevision)" >
      <Output TaskParameter="MaxAssemblyVersion" 
           PropertyName="MaxAssemblyVersion"/>
    </AssemblyInfo>

    <Message Text="Version: $(MaxAssemblyVersion)"/>
    <!-- it will be like 1.7.0.1789 -->
  </Target>
</Project>

Note: I use MSBuildCommunityTasks to discover revision number and update [AssemblyVersion] attribute
Note: everything what is necessary to build project is stored in repository- in this case we need svninfo.exe so it’s also stored in repository beside source code in directory “tools\svn”

Summary
From four presented approaches how to control revision number deployed on specific environment I would recommend using the last one. Having revision number automatically embedded in assembly has several advantages: simplifies discovery, eliminates chance for human error, does not change current deployment process, etc.

Friday, March 4, 2011

Mobile URL Rewriter

Recently Making Waves has published Mobile Url Rewriting module on EPiCode. It’s friendly Url engine, which translates incoming Url, so it points to specific template based on configured rules. Most useful scenario is providing device-specific versions (e.g. for mobile devices) using different templates while retrieving data from a single data source (Page Data).

This flexible solution is editor-friendly because only one form for each page must be filled and it becomes a source for different page templates.

For latest documentation, source code and binaries visit module's home page page.


How it works

To illustrate how it works, consider differences between using default friendly Url provider and Making Waves’ rule based Url provider

For default friendly url provider
  • /Articles/Website-Launch url is translated to /Templates/Articles/NewsTemplate.aspx?id=1234
  • /Articles/Website-Launch/Mobile returns 404 error

For rule based url rewrite provider with configured mobile rule (as suffix rule)
  • /Articles/Website-Launch is translated to /Templates/Articles/News.aspx?id=1234
  • /Articles/Website-Launch/Mobile is translated to /Mobile/Templates/Articles/News.aspx?id=123&Rule=”Mobile”
  • /Articles/Website-Launch/Lite is translated to /Lite/Templates/Articles/News.aspx?id=123&Rule=”Lite” (e.g. Lite/Full website version like MSDN)

Additionaly further template selection can be made based on User Agent (sent by browser), so for example IPhone users can be served a different version then other mobile devices, which may have low resolution displays.

Note: Url suffix (“Mobile”) and path for templates (“/Mobile/Templates/”) are examples and can be configured in web.config



You can check how it works in production at Making Waves’ website. Additionaly if you visit this website from mobile device you will be automatically redirected to mobile version.

Rule selection

When client tries to access any Url rule based rewriter, selects rule which will perform further processing. All rules are probed for matching and first, which accepts url, is responsible for template selection.

In an example below, third rule matches url and then selects the fourth template. After page is rendered, the rule is also responsible for transforming non-friendly urls into corresponding friendly urls for this rule from page markup.



Features
  • Configurable in web.config and programmatically
  • Extensible rule selection engine
  • Translates urls in rendered page using rule choosen to select template

Supported rules

Download
Visit dowload section at Module's page


Sample Configuration

In web.config
 <configSections>
  <section name="makingWaves.urlRewrite" type="MakingWaves.Common.EPiServer.UrlRewriting.Configuration.UrlRewriteSection, MakingWaves.Common.EPiServer.UrlRewriting"/>
  ...
</configSections>

<makingWaves.urlRewrite>
  <rules>
    <add name="MobileRule"
    type="MakingWaves.Common.EPiServer.UrlRewriting.AgentBasedSuffixMatchingUrlRewriteRule, MakingWaves.Common.EPiServer.UrlRewriting"
    rootPath="MobilePath/Regular"
    urlSuffix="Mobile">
      <ruleProperties>
        <add propertyType="agent" agentPattern=".*Android.*" pathSuffix="MobilePath/Android"/>
        <add propertyType="agent" agentPattern=".*IPhone.*" pathSuffix="MobilePath/IPhone"/>
      </ruleProperties>
    </add>
  </rules>
</makingWaves.urlRewrite>


In episerver.config
<episerver>
  <urlRewrite
  defaultProvider="RuleBasedUrlRewriteProvider">
    <providers>
      <add name="RuleBasedUrlRewriteProvider"
      description="My provider supporting multiple templates"
      type="MakingWaves.Common.EPiServer.UrlRewriting.RuleBasedUrlRewriteProvider,MakingWaves.Common.EPiServer.UrlRewriting"/>
      ...
    </providers>
  </urlRewrite>
</episerver>



Additionally, if you want a mobile client to be redirected to a proper version of a page consider using the following snippet using extension method GetLinkUrlByRule:
if (HttpContext.Current.Request.Browser.IsMobileDevice)
{
    this.Response.Redirect(CurrentPage.GetLinkUrlByRule("MobileRule"));
}

Contributed by

  • Andrzej Zapotoczny (andrzej.zapotoczny _mail_at_ makingwaves.pl)
  • Mirosław Jedynak (miroslaw.jedynak _mail_at_ makingwaves.pl)
  • Krzysztof Danielewisz (krzysztof.danielewicz_mail_at_makingwaves.pl) 
from Making Waves ( http://www.makingwaves.com)

Monday, February 21, 2011

Writing custom properties: #3 Edit control

In this post I will present how to create initial version of edit control for Guid property, which was introduced in previous posts.

Features
Created control will have:
  • Text input for Guid
  • Button for creating new Guid
This post is first step in more complex implementation which will come with AJAX functionality and On-Page-Edit support.

Controls render types
EPiServer properties can be rendered in one of the following modes (defined by EPiServer.Core.RenderType):
  • Default – when viewing property in View Mode. It’s used e.g. when you display property using <episerver:property PropertyName=”..”/> . In this mode CreateDefaultControls method is invoked.
  • Edit – when editing page in Edit Mode or in Quick Edit Mode. In this mode CreateEditControls method is invoked.
  • OnPageEdit – if PropertyDataControl.SupportsOnPageEdit returns true and <episerver:property PropertyName=”..”/> is used  property is rendered in this mode. Otherwise uses default view. These mode will be covered in more details soon.

Basic server control implementation
All controls for editing custom properties in EPiServer must inherit from PropertyDataControl or derived class.

In simplest scenario when supporting only edit mode functionally CreateEditControls must create controls which are used in edit mode. Edited values should be saved in ApplyEditChanges method.
Additionaly SetupEditControls updates visual representation value based on data from PropertyData
public class GuidPropertyControl : PropertyDataControl
{
    protected TextBox EditBox { get; set; }

    public GuidProperty GuidProperty
    {
        get { return (GuidProperty)this.PropertyData; }
    }

    public override void CreateEditControls()
    {
        //create text box
        this.EditBox = new TextBox { MaxLength = 0xff };
        this.ApplyControlAttributes(this.EditBox);
        this.Controls.Add(this.EditBox);

        //button for generating new guid
        //consider using LanguageManager for texts
        var button = new Button { Text = "New" };
        this.ApplyControlAttributes(button);
        button.Click +=  
              (sender, e) => { this.EditBox.Text = FormatGuid(Guid.NewGuid()); };
        this.Controls.Add(button);

        this.SetupEditControls();
    }


    private static string FormatGuid(Guid guid)
    {
        return guid.ToString();
    }

    //Saves value in underlying PropertyData 
    public override void ApplyEditChanges()
    {
        Guid value;
        if (string.IsNullOrEmpty(this.EditBox.Text))
        {
            value = Guid.Empty;
        }
        else
        {
            try
            {
                value = new Guid(this.EditBox.Text);
            }
            catch (FormatException e)
            {
                this.AddErrorValidator(e.Message);
                return;
            }
        }
        this.SetValue(value);
    }

    protected override void SetupEditControls()
    {
        this.EditBox.Text = FormatGuid(this.GuidProperty.Guid);
    } 
    //representation in View Mode
    public override  void CreateDefaultControls()
    {
        Label target = new Label();
        target.Text = FormatGuid(GuidProperty.Guid);
        this.CopyWebAttributes(target);
        this.Controls.Add(target);
    }

}


Post back support
If you would try to click “New button” in edit mode you would notice strange behavior: Message box warning about leaving page and loosing unsaved value.



The reason for this is that Javascipt OnBeforeUnload event is raised and EPiServer warns you that pages is going to be left with possible data loss.
There are two solutions for this:
  • Use ToolButton from EPiServer namespace instead of regular ASP Button and set DisablePageLeaveCheck to true

public override void CreateEditControls()
{
    //same code creating TextBox


    var button = new ToolButton
                        {
                            Text = "New", 
                            DisablePageLeaveCheck = true
                        };

   //same code using button
   
    SetupEditControls()
}

  • Use ScriptDisablePageLeaveEvent to disable checking specific event raised from our control. This approach is more flexible because not only button is supported and it’s possible to specify which event should suppress checking.

public override void CreateEditControls()
{
    //same code creating TextBox

    //same code creating Button

    ScriptDisablePageLeaveEvent scriptDisablePageLeaveEvent =
            new ScriptDisablePageLeaveEvent
            {
                EventTarget = button,
                EventType = EventType.Click
            };
        this.Controls.Add(scriptDisablePageLeaveEvent);
   
    SetupEditControls()
}

Page leave check considerations
You may wonder how it happed that without writing any code EPiServer can discover that value of Guid property has changed and warn about it. In fact EPiServer PageLeaveCheck attach to each html’s input event onchange and whenever this event is raise IsPageChanged is set to true. No magic!

This approach is working really well in most scenarios, since it’s common to store value in text boxes (sometimes in read-only mode like for Page Reference property). However, if you go into scenario where values are stored only in hidden field and displayed to user directly (without textbox) you will notice that Page Leave Check does not work anymore.

In that case you may manually mark page as changed using following Javascript

savePropetyValue: function (value) {
    //change hidden field value
    $("#hidden_field").val("...") ;

    //mark page as changed
    if (EPi.PageLeaveCheck.enabled) {
        EPi.PageLeaveCheck.SetPageChanged(true);
    }
};

Please be patient: more advanced Javascript usage in custom controls will be covered in future posts. It will include using JQuery, packaging javacripts, AJAX support, etc.

Sunday, February 13, 2011

Writing custom properties: #2 Default value

In previous post I presented how to create simple property returning Guid values. In this post I will briefly describe how to deal with default values for custom properties.
  1. Writing custom properties: #1 Custom value type
  2. Writing custom properties: #2 Default value 
  3. Writing custom properties: #3 Edit control

Admin UI based approach
The easiest way is to use admin UI and set default values for properties using three available modes:


Unfortunately this approach has several drawbacks:
  • Error-prone: It’s possible that for some reason administrator will not set this value. Application logic may not be prepared for this case.
  • Default value is applied only when creating page:  If you add new property to page type, value will not be updated for empty properties.
  • Editor can remove default value so property will fall back to empty/null value
  • String representation: It may be not easy/possible to specify string representation

In my opinion this value should be used as suggestion for editor, not as way to enforce data correctness.

Ensuring default value in custom property
Second approach for defining default value is setting it inside custom property.

There is method PropertyData.SetDefaultValue(), which is called when property is created (even if is null) or when cleared.

Together with SetDefaultValue, you will usually need implement IsNull property returning value indicating if current property value is equal to it's default.

Sample implementation for GuidPropety may be following:
[PageDefinitionTypePlugIn]
public class GuidProperty : PropertyString
{ 
    // for rest of code check previous post
    //.....

    protected override void SetDefaultValue()
    {
        base.SetDefaultValue();

        this.value = Guid.Empty;
        //or set custom default value – default product code in store etc.
        //this.value = new Guid(“12345678-1234-1234-1234-123456789012”);
    }

    public override bool IsNull
    {
        get
        {
           return this.value == Guid.Empty;
           //return this.value == new Guid(“12345678-1234-1234-1234-123456789012”);
        }
    }
}


Some readers may ask why setting guid field to Guid.Empty is necessary, when Sytem.Guid is value type and it’s default value is Guid.Empty. Answer is that SetDefaultValue is called when property is cleared with PropertyData.Clear() so reseting to default value is necessary.

Usage
For newly created page or property added to exiting page

var value = CurrentPage["Guid"]; // “00000000-0000-0000-0000-000000000000”  
                               // or e.g “12345678-1234-1234-1234-123456789012”

var isNull = CurrentPage.Property["Guid"].IsNull; // returns true


Collection based custom properties
For collection types Microsoft design guidelines state:
Array (and collection) properties should never return a null reference. Null can be difficult to understand in this context.
http://msdn.microsoft.com/en-us/library/k2604h5s.aspx

To follow this guideline using SetDefaultValue is invaluable. Having custom property GuidCollectionProperty it can be implemented like below:

[PageDefinitionTypePlugIn] 
public class GuidCollectionProperty : PropertyString
{
    // for rest of code check previous post
    //.....
    protected override void SetDefaultValue()
    {
        base.SetDefaultValue();
        this.value = new List<Guid>();
    }

    public override bool IsNull
    {
        get
        {
            return this.value == null || this.value.Count == 0;
        }
    }
}

Note: Admin default value override
It’s still possible to set default value using admin UI to override default value set by property creator. Good thing is that default value format will be verified, so it is not allowed to set “abc” as default value for GuidProperty.

Note: Virtual call in constructor
Current implementation of PropertyData base class calls virtual method SetDefaultValue() in constructor. This is discouraged because calling virtual method in base class can cause accessing uninitialized values in derived class, since derived constructor would not be invoked by that time.
Do not call virtual members from constructors. Calling virtual methods during construction will cause the most derived override to be called, even though the most derived constructor has not been fully run yet
http://msdn.microsoft.com/en-us/library/ms182331(v=vs.80).aspx
http://blogs.msdn.com/b/brada/archive/2004/08/12/213951.aspx

What does it mean for us? In order to preserve backward compatibility this behavior, I guess, won’t be fixed soon. Therefore we should be aware what we use in SetDafaultValue() and check if it has been initialized in constructor before accessing value.

Monday, February 7, 2011

Writing custom properties: #1 Custom value type

This is first post from series showing how to implement custom property in EPiServer in "proper" way without unnecessary hacking.
  1. Writing custom properties: #1 Custom value type
  2. Writing custom properties: #2 Default value
  3. Writing custom properties: #3 Edit control

Introduction
In EPiServer all custom properties must inherit from abstract base class PropertyData.  It’s value can be stored using one of following underlying types:
•    Boolean
•    Category
•    Date
•    FloatNumber
•    LinkCollection
•    LongString
•    Number
•    PageReference
•    PageType
•    String

Those types are defined in enumeration EPiServer.Core.PropertyDataType and when creating custom property you have to decide which storage type is closest to your requirements. From my experience you will usually use number or variations of string storage. The easiest way to start is to use one of default property type defined in EPiServer as your base class.

Guid Property
In this tutorial series I will show how to create complete custom property used for storing and editing Guid value. Using Guid as example will be easy to understand but still possible to show many features available for custom properties.

As usage example you may consider integrating with external system where product identifiers are stored as Guids and editor is responsible for providing them to our CMS.

Analysis

Guid are easily stored in string using different formats (the most popular is “{96C7DFDF-668C-4A71-AA79-1E8898B1A0E4}”), therefore as base type I will use PropertyString – originally used for storing up to 255 characters.

If we would leave property unchanged, editor could easily mistype value, which may cause system failure when application will be parsing invalid string. Creating custom property can prevent human error.

It’s also crucial to return proper object type, so developer can avoid parsing and, what is even worse, invalid format error handling in every place value is used.

//bad – requires parsing and error handling
Guid value=new Guid(CurrentPage[“GuidProperty”].Value); 

//good – returned value is System.Guid
Guid value= (Guid) CurrentPage[“GuidProperty”].Value; 

 Implementation

[PageDefinitionTypePlugIn]
public class GuidProperty : PropertyString
{
    private Guid value;

    public override Type PropertyValueType
    {
        get { return typeof(Guid); }
    }

    public override object Value
    {
        get { return this.value; }
        set
        {
            this.SetPropertyValue(value, delegate
            {
                //currenty we don't have custom editor control for guid, 
                //therefore if standard string control value sets value from editor
                //we try to parse it. Throwing exception in this place will show nice
                //message that provided value "is not valid value for property 
                //'YourPropertyName'"
                this.value = value is string? new Guid((string)value) : (Guid)value;

                if (Equals(value, Guid.Empty))
                    this.Clear();
                else
                    this.Modified();
            });
        }
    }

    public override void LoadData(object newValue)
    {
        this.Value = this.DeserializeValue(newValue as string);
    }

    public override object SaveData(PropertyDataCollection properties)
    {
        return this.SerializeValue(this.value);
    }

    protected string SerializeValue(Guid value)
    {
        return value.ToString();
    }
    protected Guid DeserializeValue(string value)
    {
        if (string.IsNullOrEmpty(value))
        {
            return Guid.Empty;
        }        
        return new Guid(value);        
    }
}

 Usage

var value = CurrentPage["Guid"]; // e.g. c75dfcdb-258c-47a0-a2ea-81f0b92341e4
var type = CurrentPage["Guid"].GetType(); //System.Guid (not System.String!)

Friday, February 4, 2011

Dynamic splash screen in WPF

In WPF 3.5 SP1 new splash screen functionality was introduced. It’s now possible to add static image which is shown before main window is loaded.

Read more on MSDN: http://msdn.microsoft.com/en-us/library/cc656886.aspx

Unfortunately current implementation has some limitations. Since only static images are supported (BMP, GIF, JPEG, PNG, or TIFF format):
  • It’s not possible to use XAML to define splash screen design
  • It’s not possible to display dynamic informations like:
    • List of available plugins
    • Licensee information
    • Plugin being currently loaded. 

Solution overview
Solution for those issues is:
  1. Show built-in WPF splash screen
  2. Load regular WPF window (dynamic splash screen) which is pixel-perfect same as previously displayed splash screen but displays also dynamic information
  3. When all plugins and main window is loaded hide dynamic splash screen and show main window.


Implementation : DynamicSplashScreen class
DynamicSplashScreen is reusable class which  can be used as base class for your custom splash screen. Check inline comments for details.

public class DynamicSplashScreen:Window
{
    public DynamicSplashScreen()
    {
        //Defaults for splash screen
        this.ShowInTaskbar = false;
        this.WindowStartupLocation = WindowStartupLocation.Manual;
        this.ResizeMode = ResizeMode.NoResize;
        this.WindowStyle = WindowStyle.None;
        this.Topmost = true;

        this.Loaded += OnLoaded;
    }        

    private void OnLoaded(object sender, RoutedEventArgs e)
    {
        //calculate it manually since CenterScreen substracts  
        //taskbar's height from available area
        this.Left = (SystemParameters.PrimaryScreenWidth - this.Width) / 2;
        this.Top = (SystemParameters.PrimaryScreenHeight - this.Height) / 2;
    }
}

Creating pixel-perfect static splash screen image
Creating pixel-perfect static image can be error prone, maintenance hell if you start using external applications like Photoshop – each changed in dynamic version would have to be reflected in static image manually.

DynamicSplashScreen has another feature: capturing static splash screen image. It used build-in WPF functionality to get Control’s  “screen shot” dynamically.

You can use this method to generate image and add it to Visual Studio project with build action set to SplashScreen. This still requires some manual work, but it’s not possible (and reasonably) to generate splash screen at runtime.
public class DynamicSplashScreen:Window
{
    //.... see implementation above ..

    public void Capture(string filePath)
    {
        this.Capture(filePath, new PngBitmapEncoder());
    }

    public void Capture(string filePath, BitmapEncoder encoder)
    {
        RenderTargetBitmap bmp = new RenderTargetBitmap( 
                 (int)this.Width, (int)this.Height,  
                 96, 96, PixelFormats.Pbgra32);
        bmp.Render(this);


        encoder.Frames.Add(BitmapFrame.Create(bmp));

        using (Stream stm = File.Create(filePath))
        {
            encoder.Save(stm);
        }
    }
}

Usage
public partial class App : Application
{
    protected override void OnStartup(StartupEventArgs e)
    {
        // MyDynamicSplashScreen derives from DynamicSplashScreen and
        // defines layout and custom properties (like Licensee, Plugins, Message)
        MyDynamicSplashScreen splashScreen = new MyDynamicSplashScreen();
        splashScreen.Show();

        //set basic dynamic data on splash screen
        splashScreen.AvailablePlugins = new[] {"Plugin 1", "Plugin 2"};
        splashScreen.Licensee = "Mirosław Jedynak";

        //use during development to generate image and embed it in application
        //splashScreen.Capture(@"c:\StaticSplashScreen.png");

        var startupTask=new Task(() =>
            {
                //Load plugins in non-UI thread - may be time consuming
                for (int i = 0; i < 3; i++)
                {
                    //set custom message on screen
                    splashScreen.Dispatcher.BeginInvoke(
                        (Action) (()=>splashScreen.Message = "Loading:  Plugin " + i));
                            
                    Thread.Sleep(100);
                }
            });

        //when plugin loading finished, show main window
        startupTask.ContinueWith(t =>
            {
                MainWindow mainWindow = new MainWindow();

                //when main windows is loaded close splash screen
                mainWindow.Loaded += (sender, args) => splashScreen.Close();

                //set application main window;
                this.MainWindow = mainWindow;

                //and finally show it
                mainWindow.Show();
            }, TaskScheduler.FromCurrentSynchronizationContext());

        startupTask.Start();
    }       
}


Sample Dynamic splash screen
<Startup:DynamicSplashScreen x:Class="DynamicSplashScreenDemo.Startup.SplashScreen"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"  
        xmlns:Startup="clr-namespace:DynamicSplashScreenDemo.Startup"  
        Title="My application"   x:Name="spashScreen"  
        Width="500" Height="300">
    <Border BorderThickness="1" BorderBrush="Black">
        <Grid >
            <Rectangle>
                <Rectangle.Fill>
                    <LinearGradientBrush EndPoint="0,0" StartPoint="0.5,1.3">
                        <GradientStop Color="#FF07254E" Offset="1"/>
                        <GradientStop Color="White" Offset="0.369"/>
                        <GradientStop Color="White"/>
                    </LinearGradientBrush>
                </Rectangle.Fill>
            </Rectangle>
            
            <TextBlock  FontFamily="Verdana" FontWeight="Bold" FontSize="50"  
                VerticalAlignment="Top" HorizontalAlignment="Center"  
                TextAlignment="Center"  Margin="0,40,0,0" >
                <Run Text="Dynamic" Foreground="#FF006AB3" />
                <LineBreak/>
                <Run Text="splash screen" Foreground="#FF006AB3"/>
            </TextBlock>

            <TextBlock Text="{Binding ElementName=spashScreen, Path=Message}"  
             VerticalAlignment="Bottom"  Margin="10"/>
            <StackPanel VerticalAlignment="Bottom" HorizontalAlignment="Right"  
                 Margin="10">
                <ItemsControl  Margin="0,0,0,10" HorizontalAlignment="Right"
                ItemsSource="{Binding ElementName=spashScreen, Path=AvailablePlugins}"/>
                <StackPanel Orientation="Horizontal">
                    <TextBlock Text="Licensed to: "/>
                    <TextBlock Text="{Binding ElementName=spashScreen, Path=Licensee}"/>
                </StackPanel>
            </StackPanel>
        </Grid>
    </Border>
</Startup:DynamicSplashScreen>

Source: DynamicSplashScreenDemo_source.zip

Shout it
kick it on DotNetKicks.com