Permanently Redirecting a Page in ASP.NET 4

Rather that using Response.Redirect("newpage.aspx"), ASP.NET 4 introduces the RedirectPermanent method.

To signal that the redirect is of a permanent nature (such as a url to a page changing permanently) you can now call:

Response.RedirectPermanent("newpage.aspx");

This will issue a http response HTTP 301 (Moved Permanently) rather than the normal HTTP 302 (Found). This can potentially remove a round trip to the server.

From msdn.com: "Search engines and other user agents that recognize permanent redirects will store the new URL that is associated with the content, which eliminates the unnecessary round trip made by the browser for temporary redirects."

Notification Window "toast" in Silverlight 4

To create "toast" notifications (small popup messages that appear at the bottom right on Windows) the NotificationWindow class can be used.

NotificationWindow will only work in out-of-browser mode, if you try to show a notification when running in-browser a System.InvalidOperationException is thrown.

The default size of the toast is 400 x 100, the size can be altered by setting NotificationWindow's Width & Height, but the max size is 400 x 100;

Example 1 - Creating Some Simple Content

The code below shows a notification that contains a  TextBlock with the text "hello" and show it for 2 seconds:

var toast = new NotificationWindow();

// Create some content to show
var tb = new TextBlock();
tb.Text = "hello";

// Set content of the toast before we show it
toast.Content = tb;

// Show notification for 2 seconds
toast.Show(2000);

Example 2 - Using a UserControl as Content

This example shows how a UserControl defined elsewhere in XAML can be used as the content of the notification:

var toast = new NotificationWindow();

// Create an instance of a UserControl called SampleToast
// that is defined in XAML
var toastContent = new SampleToast();

// Set the content of the notification window to that of the UserControl
toast.Content = toastContent;

// Optionally set size to be different from the
// default of 400 x 100
toast.Width = 200;
toast.Height = 50;

// Show notification for 2 seconds
toast.Show(5000);

MSDN Docs

 

Hosting HTML Content in Silverlight 4

In Silverlight 4 we can present HTML content to the user.

The WebBrowser Control

For example to display the content of DontCodeTired.com we can set the Source property in XAML:

<WebBrowser Name="webBrowser" Source="http://www.dontcodetired.com" />

Or we can use the Navigate method of WebBrowser:

webBrowser.Navigate(new Uri(@"http://www.dontcodetired.com"));

The user can navigate to links, right-click and open in new tab (which opens up tab in a full web browser), view source, etc.

To use the WebBrowser the Silverlight app must be running out of browser (OOB), if you view when embedded in a web page (i.e. not OOB) the control displays a message saying that it can only be used in Out-of-Browser mode.

WebBrowser control running in-browser

WebBrowserBrush

Using the new WebBrowserBrush (previously HtmlBrush in beta 4) we can use the contents of a WebBrowser to fill another element.

For example, to fill an Ellipse with the content of a WebBrowser named "webBrowser" we define a WebBrowserBrush and sets its SourceName:

<Ellipse>
    <Ellipse.Fill>
        <WebBrowserBrush SourceName="webBrowser"  />
    </Ellipse.Fill>
</Ellipse>

The following example creates an ellipse which is filled with a blurred version of the content of a WebBrowser. One thing to note is that we hook into the WebBrowser's LoadCompleted event to ensure the ellipse is updated whenever a page has been loaded into the WebBrowser. When run it looks like the image below: 



XAML:

<UserControl x:Class="SL4HTMLHosting.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"
    mc:Ignorable="d"
    d:DesignHeight="300" d:DesignWidth="400">

    <Grid x:Name="LayoutRoot" Background="White">
        <Grid.RowDefinitions>
            <RowDefinition Height="1*" />
            <RowDefinition Height="1*" />
        </Grid.RowDefinitions>
       
        <WebBrowser Name="webBrowser"
                    Source="http://www.dontcodetired.com"
                    Grid.Row="0"
                    LoadCompleted="webBrowser_LoadCompleted">
        </WebBrowser>
       
        <Ellipse HorizontalAlignment="Stretch"
                    VerticalAlignment="Stretch"
                    Grid.Row="1">
            <Ellipse.Effect>
                <BlurEffect Radius="5" />
            </Ellipse.Effect>
            <Ellipse.Fill>
                <WebBrowserBrush x:Name="browserBrush"
                                    SourceName="webBrowser"  />
            </Ellipse.Fill>
        </Ellipse>
          
    </Grid>
</UserControl>

Code Behind:

using System.Windows.Controls;

namespace SL4HTMLHosting
{
    public partial class MainPage : UserControl
    {
        public MainPage()
        {
            InitializeComponent();
        }

        private void webBrowser_LoadCompleted(object sender, System.Windows.Navigation.NavigationEventArgs e)
        {
            browserBrush.Redraw();
        }
    }
}

Interacting With WebBrowser Content

The following example uses WebBrowser.NavigateToString which allows a string of HTML to be rendered rather than a page at some Uri. The example creates a simple html button which uses window.external.notify to pass a message to the WebBroswer control via it's ScriptNotify event.

XAML:

<Grid x:Name="LayoutRoot" Background="White">            
    <WebBrowser Name="webBrowser"
                ScriptNotify="webBrowser_ScriptNotify">                  
    </WebBrowser>          
</Grid>

Code Behind:

using System.Windows.Controls;
using System.Text;
using System.Windows;

namespace SL4HTMLHosting
{
    public partial class MainPage : UserControl
    {
        public MainPage()
        {
            InitializeComponent();
           
            // Create some basic HTML on the fly containing a simple button,
            // that when clicked passes a message to the containing WebBrowser
            var html = new StringBuilder();
            html.Append("<html>");
            html.Append("   <body>");
            html.Append(@"       <input type=""button"" value=""Show Message Box"" onclick=""window.external.notify('button click');"" /> ");
            html.Append("   </body>");
            html.Append("</html>");

            webBrowser.NavigateToString(html.ToString());
        }



        private void webBrowser_ScriptNotify(object sender, NotifyEventArgs e)
        {
            MessageBox.Show(string.Format("Message from web page: '{0}'.",e.Value));
        }
    }
}
 

Right Click and Context Menu Support in Silverlight 4

Silverlight 4 allows us access to right-click functionality, in earlier versions of Silverlight right-clicking would bring up the standard SL popup menu.

Simple Right Click

To capture a right-click the MouseRightButtonDown event can be subscribed to:

MouseRightButtonDown="btnPopup_MouseRightButtonDown"

To prevent the standard SL popup from appearing, in the event handler we set Handled to true, this stops the right-click event from bubbling up to Silverlight:

private void btnPopup_MouseRightButtonDown(object sender, MouseButtonEventArgs e)
{   
    // Prevent the standard Silverlight popup          
    e.Handled = true;

    MessageBox.Show("Right button clicked");
}

Context Menu

The ContextMenu is available in the Silverlight Toolkit.

A popup context menu can easily be added to a control (e.g. a button), when the control is right-clicked the context menu is automatically shown

The XAML below declares a simple popup menu with 2 menu items: Open & Save that when clicked both fire the same event handler.

<Button Name="btnContextMenu"
        Content="ContextMenu Right Click"
        MouseRightButtonDown="btnContextMenu_MouseRightButtonDown">
    <toolkit:ContextMenuService.ContextMenu>
        <toolkit:ContextMenu>
            <toolkit:MenuItem Header="Open" Click="ContextMenuItem_Click"/>
            <toolkit:MenuItem Header="Save" Click="ContextMenuItem_Click"/>
        </toolkit:ContextMenu>                   
    </toolkit:ContextMenuService.ContextMenu>     
</Button>

The namespace (xmlns:toolkit="http://schemas.microsoft.com/winfx/2006/xaml/presentation/toolkit") must be declared - if you drag and drop something from the Toolkit the namespace will be automatically added.

Full listing (simple popup & context menu)

XAML:

<UserControl x:Class="SL4RClickPopup.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:toolkit="http://schemas.microsoft.com/winfx/2006/xaml/presentation/toolkit"
    mc:Ignorable="d"
    d:DesignHeight="300" d:DesignWidth="400" >  
   
    <StackPanel>
       
        <Button Name="btnPopup"
                Content="Simple Right Click"
                MouseRightButtonDown="btnPopup_MouseRightButtonDown" />
       
        <Button Name="btnContextMenu"
                Content="ContextMenu Right Click"
                MouseRightButtonDown="btnContextMenu_MouseRightButtonDown">
           
            <toolkit:ContextMenuService.ContextMenu>
                <toolkit:ContextMenu>
                    <toolkit:MenuItem Header="Open" Click="ContextMenuItem_Click"/>
                    <toolkit:MenuItem Header="Save" Click="ContextMenuItem_Click"/>
                </toolkit:ContextMenu>
            </toolkit:ContextMenuService.ContextMenu>
           
        </Button>
         
    </StackPanel>
   
</UserControl>

Code Behind:

using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;

namespace SL4RClickPopup
{
    public partial class MainPage : UserControl
    {
        public MainPage()
        {
            InitializeComponent();
        }



        private void btnPopup_MouseRightButtonDown(object sender, MouseButtonEventArgs e)
        {
            // Prevent the standard Silverlight popup          
            e.Handled = true;

            MessageBox.Show("Right button clicked");
        }



        private void btnContextMenu_MouseRightButtonDown(object sender, MouseButtonEventArgs e)
        {
            // Because we have declare a ContextMenu in Xaml we don't
            // need to manually set e.Handled = true;
        }



        private void ContextMenuItem_Click(object sender, RoutedEventArgs e)
        {
            MessageBox.Show("ContextMenu item clicked");
        }
    }
}
 

Drag and Drop in Silverlight 4

To enable drag and drop support on a Silverlight UIElement the AllowDrop property needs to be set to True (it is false by default). The Drop event can now be subscribed to, in which the dropped item(s) can be processed.

The example below allows a .jpg file to be dragged (from the desktop, Windows Explorer, etc.) and dropped onto the application. When the user drops a .jpg, the image is loaded and displayed.

<Grid x:Name="LayoutRoot"
        Background="Black"
        AllowDrop="True"
        Drop="LayoutRoot_Drop">
       
    <Image Name="imgPresenter"
            Stretch="UniformToFill"
            IsHitTestVisible="False" />
       
    <TextBlock HorizontalAlignment="Center"
                VerticalAlignment="Center"
                IsHitTestVisible="False"
                Foreground="#FFCBC3C3"
                FontSize="32"
                Opacity="0.33"
                Text="drag photo here"/>       
       
</Grid>

 

Code behind:

private void LayoutRoot_Drop(object sender, DragEventArgs e)
{
    if (e.Data != null)
    {
        // Get a list of the file(s) that the user has dragged & dropped
        FileInfo[] droppedFiles = e.Data.GetData(DataFormats.FileDrop) as FileInfo[];

        // If there is at least 1 file that has been dropped
        if (droppedFiles != null && droppedFiles.Length > 0)
        {
            LoadImage(droppedFiles.First());
        }               
    }
}



private void LoadImage(FileInfo imageFileInfo)
{
    if (imageFileInfo.Extension.ToUpper() != ".JPG")
    {
        MessageBox.Show("Only .jpg files are supported...");
    }
    else
    {
        // Load the image from disk
        BitmapImage source = new BitmapImage();
        source.SetSource(imageFileInfo.OpenRead());

        // Set the image being displayed to the new dropped one
        imgPresenter.Source = source;
    }
}

Copy and Paste in Silverlight 4

Silverlight 4 added the static Clipboard class with the clearly-named SetText, GetText and ContainsText methods.

If running in-browser or out-of-browser without elevated trust the Clipboard methods can only be called as a result of user actions (such as Click, KeyDown). The first time a method is called, a security prompt is presented to the user asking them to grant the app access to the clipboard.



If the user selects 'No' a SecurityException is thrown.

 

If the app is running out-of-browser with elevated trust, the above dialog is not shown and the Clipboard methods can be called even if not in response to user initiated events such as in your page constructor, in a DispatcherTimer.Tick event, etc.

Example Code

<Button x:Name="btnCopy" Click="btnCopy_Click">Copy</Button>
<Button x:Name="btnPaste" Click="btnPaste_Click">Paste</Button>
<TextBox x:Name="txtBox"></TextBox>

 

private void btnCopy_Click(object sender, RoutedEventArgs e)
{
    // Assumes we want to prevent the user from copy 'nothing' to the clipboard.
    // Without this check, whatever text is currently in the clipboard will be replaced
    // with nothing.
    if (!string.IsNullOrEmpty(txtBox.Text))
    {
        try
        {
            Clipboard.SetText(txtBox.Text);
        }
        catch (SecurityException ex)
        {
            // If the user does not grant access for the app to access the clipboard
            // a SecurityException is thrown
            MessageBox.Show("Clipboard access is required to use this feature.");
        }
    }
}


private void btnPaste_Click(object sender, RoutedEventArgs e)
{
    try
    {
        txtBox.Text = Clipboard.GetText();
    }
    catch (SecurityException ex)
    {
        // If the user does not grant access for the app to access the clipboard
        // a SecurityException is thrown
        MessageBox.Show("Clipboard access is required to use this feature.");
    }
}

FallbackValue, TargetNullValue & StringFormat in Silverlight 4

Silverlight 4 introduces some further databinding functionality including:

FallbackValue

If there is a problem with performing the binding, for example an incorrect path to a property value, the fallback value will be used in it's place. This allows some meaningful display, rather that the default behaviour of showing nothing.

TargetNullValue

Allows a value to be used if the bound property is null.

StringFormat

Allows formatting of the bound value using standard formatting expressions.

Example

The following is the output of the XAML below:

 

 

<Grid x:Name="LayoutRoot" Background="WhiteSmoke" >
    <Grid.ColumnDefinitions>
        <ColumnDefinition></ColumnDefinition>
        <ColumnDefinition></ColumnDefinition>
    </Grid.ColumnDefinitions>
    <Grid.RowDefinitions>
        <RowDefinition Height="auto"></RowDefinition>
        <RowDefinition Height="auto"></RowDefinition>
        <RowDefinition Height="auto"></RowDefinition>
        <RowDefinition Height="auto"></RowDefinition>
        <RowDefinition Height="auto"></RowDefinition>
        <RowDefinition Height="auto"></RowDefinition>
    </Grid.RowDefinitions>

    <TextBlock TextWrapping="Wrap">Invalid binding path with FallbackValue set</TextBlock>
    <TextBlock Text="{Binding Path=badPath, FallbackValue='this is a fallback value'}" Grid.Column="1"></TextBlock>

    <TextBlock TextWrapping="Wrap" Grid.Row="1">Invalid binding path withOUT FallbackValue set</TextBlock>
    <TextBlock Text="{Binding Path=badPath}" Grid.Row="1" Grid.Column="1"></TextBlock>

    <TextBlock TextWrapping="Wrap" Grid.Row="2">Bound property = null with TargetNullValue set</TextBlock>
    <TextBlock Text="{Binding Path=AnIntProperty, TargetNullValue='The int is null'}" Grid.Column="1" Grid.Row="2"></TextBlock>

    <TextBlock TextWrapping="Wrap" Grid.Row="3">Bound property = null withOUT TargetNullValue set</TextBlock>
    <TextBlock Text="{Binding Path=AnIntProperty}" Grid.Column="1" Grid.Row="3"></TextBlock>

    <TextBlock TextWrapping="Wrap" Grid.Row="4">Bound property with StringFormat</TextBlock>
    <TextBlock Text="{Binding Path=AnotherIntProperty, StringFormat='Age is {0} years old.'}" Grid.Column="1" Grid.Row="4"></TextBlock>

    <TextBlock TextWrapping="Wrap" Grid.Row="5">Bound property withOUT StringFormat</TextBlock>
    <TextBlock Text="{Binding Path=AnotherIntProperty}" Grid.Column="1" Grid.Row="5"></TextBlock>

</Grid>

The DataContext is set to an instance of the following class:

public class TestData : DependencyObject
{
    public int? AnIntProperty
    {
        get { return (int?)GetValue(AnIntPropertyProperty); }
        set { SetValue(AnIntPropertyProperty, value); }
    }

    // Using a DependencyProperty as the backing store for AnIntProperty.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty AnIntPropertyProperty =
        DependencyProperty.Register("AnIntProperty", typeof(int?), typeof(TestData),
            new PropertyMetadata(0));


    public int AnotherIntProperty
    {
        get { return (int)GetValue(AnotherIntPropertyProperty); }
        set { SetValue(AnotherIntPropertyProperty, value); }
    }

    // Using a DependencyProperty as the backing store for AnotherIntProperty.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty AnotherIntPropertyProperty =
        DependencyProperty.Register("AnotherIntProperty", typeof(int), typeof(TestData), new PropertyMetadata(0));


    public TestData()
    {
        AnIntProperty = null;
        AnotherIntProperty = 44;
    }

}

Printing Support in Silverlight 4

Basic printing can be accomplished through the PrintDocument class. For example, to print the entire contents of the LayoutRoot grid:

private void btnSimplePrint_Click(object sender, RoutedEventArgs e)
{
    string docName = "Test SL4 Print";

    PrintDocument doc = new PrintDocument();

    doc.PrintPage += (s, args) =>
    {
        // print the entire contents of our page (i.e. the
        // outer grid called LayoutRoot)
        args.PageVisual = LayoutRoot;
    };

    doc.Print(docName); // automatically shows a print dialog box
}

Various properties are avaible on the PrintPageEventArgs that are passed to the PrintPage event handle ('args' in the above example); args.HasMorePages allows you to control multi-page printing and args.PrintableArea allows you to get the printable page area you have available to you (which will depend on the settings chosen in the print dialog box that is shown automatically for you.

It is up to the developer to handle more complex scenarios such as overflowing contents, knowing if there are more pages required, etc. In an enterprise situation other PDF-scentric options may be a better choice, especially if there are complex report layouts required, etc. For example, using iText.NET, FOP, or an enterprise solution such as Thunderhead, xPression, etc. 

C# 4.0 Optional Parameters

C# 4.0 introduces the concepts of Optional Parameters and the related Named Parameters language features. Essentially by giving parameters a default value, callers can omit values for those parameters. The example code below produces the following output:

 name: Peter age: 44 height: 200
NAME: PETER AGE: 44 YEARS HEIGHT: 200 CM
NAME: PETER AGE: 44 YEARS HEIGHT: 200 CM
NAME: PETER AGE: 44 YEARS HEIGHT: 200 CM
NAME: PETER AGE: 44 HEIGHT: 200

The code that produces this output:

class Program
{
    static void Main(string[] args)
    {
        // Supply all parameters explicity (passing false)
        Console.WriteLine(CreateString("Peter", 44, 200, false, false));

        // Supply all parameters explicity (passing true)
        Console.WriteLine(CreateString("Peter", 44, 200, true, true));

        // Supply only minimum required parameters,
        // the defaults (true) for useUpperCase & appendUnitText will be used
        Console.WriteLine(CreateString("Peter", 44, 200));

        // Supply the first optional parameter only
        Console.WriteLine(CreateString("Peter", 44, 200, true));

        // If we want to use the default for the first optional parameter (useUpperCase),
        // but we want to supply a value for the second optional parameter we
        // can use Named Parameters using the appendUnitText:false syntax
        Console.WriteLine(CreateString("Peter", 44, 200, appendUnitText:false));

        Console.Read(); // wait for enter key to be pressed
    }

    static string CreateString(string name,
                                int age, int height,
                                bool useUpperCase = true,
                                bool appendUnitText = true)
    {
        StringBuilder sb = new StringBuilder();
           
        sb.AppendFormat("name: {0} ", name);

        sb.AppendFormat("age: {0} ", age);
        if (appendUnitText)
            sb.Append("years ");

        sb.AppendFormat("height: {0} ", height);
        if (appendUnitText)
            sb.Append("cm ");

        if (useUpperCase)
            return sb.ToString().ToUpper();
        else
            return sb.ToString();               
    }
}

Testing Events Using Rhino Mocks

There are possible more 'elegant' methods for the below but these are fairly well understandable by someone new to Rhino Mocks or mocking in general.

Testing Event Subscription

Sometimes we want to ensure that a given event has been subscribed to. The following example is essentially testing that when a new ViewModel is instantiated (the constructor accepts an IWorkPackageCoordinator as a dependency), that the events declared on the IWorkPackageCoordinator instance have been subscribed to (by the ViewModel instance). In other words: does the ViewModel subscribe to the IWorkPackageCoordinatorevents.

To test this we create a ViewModel and pass it a mock object that has been created for us by Rhino Mocks.

 

            MockRepository mocks = new MockRepository();
            IWorkPackageCoordinator mockWPC = (IWorkPackageCoordinator)mocks.StrictMock(typeof(IWorkPackageCoordinator));

            // Tell Rhino Mocks that we expect the following events to be subscriped to
            //  - we don't care about the actual delegate that is attached hence the
            // LastCall.IgnoreArguments();
            mockWPC.Downloading += null;
            LastCall.IgnoreArguments();

            mockWPC.Loading += null;   
            LastCall.IgnoreArguments();

            mockWPC.Processing += null;
            LastCall.IgnoreArguments();

            mockWPC.Saving += null;
            LastCall.IgnoreArguments();

            mocks.ReplayAll();

            new ViewModel(mockWPC);
           
            mocks.VerifyAll();  

 

Testing Event Raising\Handling

Another test that is useful is that the object we are testing performs some action when another object raises an event that it is subscribed to.

In the example below we want to test that when an IWorkPackageCoordinator raises it's Downloading event that the ViewModel in turn raises it's PropertyChanged event. We use a boolean eventRaised, and (using lambda syntax) create an anonymous event handler for the PropertyChanged event which sets eventRaised to true if the event is fired. We then tell out Rhino Mock object to raise the Downloading event (in this example with non specific event arguments 'EventArgs.Empty')

            var mockWPC = MockRepository.GenerateMock<IWorkPackageCoordinator>();
            var SUT = new ViewModel(mockWPC);

            bool eventRaised = false;

            SUT.PropertyChanged += (s, e) => { eventRaised = true; };

            // tell mock to raise event
            mockWPC.Raise(x => x.Downloading += null, this, EventArgs.Empty);

            Assert.IsTrue(eventRaised);