Click here to Skip to main content
15,867,330 members
Articles / Programming Languages / C# 4.0

Silverlight MVVM Lib and FileUploader Using HttpHandler

Rate me:
Please Sign up or sign in to vote.
4.94/5 (38 votes)
9 Jan 2010CPOL19 min read 189.6K   2.4K   89   64
A demo app that shows how to upload a file using Silverlight/HttpHandler/MVVM, and includes Silverlight MVVM Library and Helpers.

Contents

Introduction

Well, it's now a couple of days after Christmas here, so it's back to work for us. Now for those that know me, I normally write about WPF and related technologies. I like WPF, but it has been a while since I played with its smaller brother Silverlight. I think the last time I played with Silverlight was way back in 2007, when I wrote this article (good in its day I suppose): Silverlight 1.1 Fun and Games.

So I thought it was about time that I had a look at why Silverlight was getting so much Microsoft press and community attention lately, the future of Silverlight, and all that. I say the future is doing cool work, and that is whatever technology that happens to be. But whatever.

This article started off as a small part of a larger SL4 application that I am making to understand and play with RIA Services, but it kinda morphed a bit, and has now taken on a life all of its own. Well, actually, it is kind of two sentient beings now:

Sentient Being 1: A mini Silverlight class library that aids in MVVM development.

Sentient Being 2: What I was supposed to be doing all along, which was playing with the new SL4 bits. To do this, I have created a SL4 uploader that can upload a file to a folder on a web server, and will optionally show a SL3/4 ChildWindow of the uploaded file if it is an image file.

Prerequisites

The attached demo code targets SL4, and was developed as a VS2010 BETA 2 solution, so you will need the following bits and pieces:

All of which I am using for this article... I make no apologies for this, the new stuff is the good stuff.

What the Demo App Does

As I say, the demo app is just one part of the attached demo code, and basically does this: it is a SL4 application that contains a SL4 control that can upload a file to a folder on a web server, and will optionally show a SL3/4 ChildWindow of the uploaded file if it is an image file.

It looks like this when run:

The user picks a file:

Image 1

The file is uploaded (asynchronously), and makes use of an ASP.NET Handler. The BusyIndicator (Silverlight Toolkit) is shown in a SL3/SL4 ChildWindow.

Image 2

And if the uploaded file is an image, it is shown in a Modal ChildWindow (which the ChildWindow does not do out of the box), as shown here:

Image 3

This involves lots of neat stuff, like services, asynchronous delegates, dispatcher contexts, building requests/responses, and using ASP.NET Handlers.

As I say though, the demo app is just one part, the second part (which was a bi-product of the demo code, really) is a small Silverlight MVVM framework that aids in the development of creating Silverlight applications the MVVM way. I have knocked all this up in a matter of days (probably 1 or 2 full days work), so it's not a complete MVVM framework, it does not provide all the functions of my Cinch WPF MVVM Framework, but it is a very good start.

Before I dive into how the demo app (the file uploader) works, I will talk about the mini Silverlight MVVM class library that forms part of the attached demo code.

The SL Mini MVVM Helper Library

As I say, I have written what I think is a pretty OK MVVM framework for WPF (Cinch WPF MVVM Framework), and there may be some of you who are like why did he not just add to the Cinch WPF MVVM Framework and make it work for both SL and WPF. Well, for me, there is enough difference between the two to warrant a different framework for each. For example, when I wrote the Cinch WPF MVVM Framework, SL did not have any IDataErrorInfo support and neither did it have loads of other stuff. It could not even open popup windows.

So there are enough differences to my mind to have two dedicated frameworks. To this end, I have just done the minimum amount of work at creating a workable Silverlight MVVM framework as I thought would be required.

Who knows, in time, I may join the two together, but for now, never the twain shall meet I'm afraid.

So what is covered? Well, that would be the following:

  • Commanding
  • Messaging
  • Services
  • Threading Helpers

We will now go through all of these areas in turn, but before we do that, I would just like to say thanks and give credit to the following members of the WPF Disciples:

  1. Josh Smith / Marlon Grech: For their excellent Mediator code
  2. Laurent Bugnion / Josh Smith: For the RelayCommand/RelayCommand<T> code
  3. Daniel Vaughan: For his Silverlight UISynchronizationContext idea

Thanks lads.

Commanding

I am basically using Laurent Bugnion's modified versions (see Laurent's MVVMLight project) of Josh Smith's RelayCommand and RelayCommand<T> for this.

A simple example of how to use these in your ViewModel may be as follows:

Step 1: Declare the ICommand property in the ViewModel.

C#
public ICommand UploadCommand
{
    get;
    private set;
}

Step 2: Hook up the RelayCommand / RelayCommand<T>.

C#
public ImageUploadViewModel()
{
    UploadCommand = new RelayCommand(ExecuteUploadCommand, CanExecuteUploadCommand);
}
...
...
private void ExecuteUploadCommand()
{
    //do stuff here
}

private Boolean CanExecuteUploadCommand()
{
    return true;
}

Step 3: Use the ICommand property exposed from the ViewModel.

XML
<Button Content="Upload File" Command="{Binding UploadCommand}" />

Messaging

I am re-using Marlon Grech's / Josh Smith's excellent Mediator which is also what my Cinch WPF MVVM Framework uses. In case you did not read my Cinch article series, here is what I said about the Mediator then, and this still applies directly to the attached Silverlight MVVM framework.

Now, I do not know about you, but generally, when I working with the MVVM framework, I do not have a single ViewModel managing the whole shooting match. I actually have a number of them (in fact, we have loads). One thing that is an issue using the standard MVVM pattern is cross ViewModel communication. After all, the ViewModels that form an application may all be disparate unconnected objects that know nothing about each other. However, they need to know about certain actions that a user performs. Here is a concrete example.

Say you have two Views: one with customers and one with orders for a customer. Let's say the orders view was using a OrdersViewModel and that the customers view was using a CustomersViewModel, and when a customer's order is updated, deleted, or added, the Customer view should show some sort of visual trigger to alert the user that some order detail of the customer changed.

Sounds simple enough, right? However, we have two independent views run by two independent ViewModels, with no link, but clearly, there needs to be some sort of connection from the OrdersViewModel to the CustomersViewModel, some sort of messaging perhaps.

This is exactly what the Mediator pattern is all about. It is a simple light weight messaging system. I wrote about this some time ago on my blog, which in turn got made a ton better by Josh Smith / Marlon Grech (as an atomic pair), who came up with the Mediator implementation you will see in Cinch.

So how does the Mediator work?

This diagram may help:

Image 4

The idea is a simple one: the Mediator listens for incoming messages, sees who is interested in a particular message, and calls each of those that are subscribed against a given message. The messages are usually strings.

Basically, what happens is that there is a single instance of the Mediator sitting somewhere (usually exposed as a static property in the ViewModelBase class) that is waiting for objects to subscribe to it either using:

  • An entire object reference. Then any Mediator message method that has been marked up with the MediatorMessageSinkAttribute attribute will be located on the registered object (using Reflection) and will have a callback delegate automatically created.
  • An actual Lambda callback delegate.

In either case, the Mediator maintains a list of WeakAction callback delegates. Where each WeakAction is a delegate which uses an internal WeakReference class to check the WeakReference.Target for null, before calling back the delegate. This caters for the fact that the target of the callback delegate may no longer be alive as it may have been Garbage Collected. Any instances of WeakAction callback delegates that point to objects that are no longer alive are removed from the list of Mediator WeakAction callback delegates.

When a callback delegate is obtained, either the original callback delegate is called, or the Mediator message methods that have been marked up with the MediatorMessageSinkAttribute attribute will be called.

Here is an example of how to use the Mediator in all the different possible manners.

Registering for Messages

Using an explicit callback delegate (this is not my preferred option though)

We simply create the correct type of delegate and register a callback for a message notification with the Mediator.

C#
public delegate void DummyDelegate(Boolean dummy);
...

Mediator.Register("AddCustomerMessage", new DummyDelegate((x) =>
{
    AddCustomerCommand.Execute(null);
}));
Register an entire object, and use the MediatorMessageSinkAttribute attribute

This is my favorite approach, and is the simplest approach in my opinion. You just need to register an entire object with the Mediator and attribute up some message hook methods.

To register, this is done for you in Cinch, you don't have to do anything. Simply inherit from ViewModelBase, job done. If you are wondering how this is done, the ViewModelBase class in Cinch simply registers itself with the Mediator like this.

C#
//Register all decorated methods to the Mediator
Mediator.Register(this);

So any method that is marked up with the MediatorMessageSinkAttribute attribute will be located on the registered object (using Reflection), and will have a callback delegate automatically created. Here is an example:

C#
/// <summary>
/// Mediator callback from StartPageViewModel
/// </summary>
/// <param name="dummy">Dummy not needed</param>

[MediatorMessageSink("AddCustomerMessage", ParameterType = typeof(Boolean))]
private void AddCustomerMessageSink(Boolean dummy)
{
    AddCustomerCommand.Execute(null);
}

How about notification of messages?

Message Notification

That is very easy to do, we simply use the Mediator.NotifyCollegues() method as follows:

C#
//Use the Mediator to send a Message to MainWindowViewModel to add a new 
//Workspace item
Mediator.NotifyColleagues<Boolean>("AddCustomerMessage", true);

Any object that is subscribed to this message will now be called back by the list of Mediator WeakAction callback delegates.

Services

I guess since I wrote so many UI Services for my Cinch WPF MVVM Framework, I did not find it that hard to write some more for Silverlight. The attached demo code makes use of some Silverlight specific UI services which we will get onto in just a minute.

But first, we need to know how to add the in-use services. For this, I am borrowing Marlon Grech's ServiceLocator implementation which is really as simple as having a ServiceLocator instance in the ViewModelBase class, that allows new UI services to be added and removed. Here is the attached SL MVVM ViewModelBase class code:

C#
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Configuration;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using GalaSoft.MvvmLight.Command;

namespace SLMiniMVVM
{

    public abstract class ViewModelBase : INotifyPropertyChanged, IDisposable
    {

        private static ServiceLocator serviceLocator = new ServiceLocator();

        /// <summary>
        /// Gets the service locator 
        /// </summary>
        public static ServiceLocator ServiceLocator
        {
            get
            {
                return serviceLocator;
            }
        }

        /// <summary>
        /// Gets a service from the service locator
        /// </summary>
        /// <typeparam name="T">The type of service to return</typeparam>
        /// <returns>Returns a service that was registered with the Type T</returns>
        public T GetService<T>()
        {
            return serviceLocator.GetService<T>();
        }
    }
}

And this is how the services are setup in the attached demo code:

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using SLMiniMVVM;

namespace TestSL4
{
    public partial class App : Application
    {

        public App()
        {
            this.Startup += this.Application_Startup;
            InitializeComponent();
        }

        private void Application_Startup(object sender, StartupEventArgs e)
        {
            //setup services
            ViewModelBase.ServiceLocator.
            RegisterService<ISLOpenFileService>(
                new SLOpenFileService(), true);

            ViewModelBase.ServiceLocator.
            RegisterService<ISLChildWindowService>(
                new SLChildWindowService(), true);

            ViewModelBase.ServiceLocator.
            RegisterService<ISLMessageBoxService>(
                new SLMessageBoxService(), true);

            //now register any popups
            ISLChildWindowService popupVisualizer = 
        ViewModelBase.ServiceLocator.GetService<ISLChildWindowService>();
            popupVisualizer.Register("ImagePopup", typeof(ImagePopup));
        }
    }
}

As this is the real code. The UI services are setup on App.Startup, but if we were using a Unit Test to test the ViewModels, we could do something like this in the [Setup] section (assuming NUnit here) of the Unit Test:

C#
[Setup]
private void setup()
{
    //setup services
    ViewModelBase.ServiceLocator.
    RegisterService<ISLOpenFileService>(
        new TestOpenFileService(), true);

    ViewModelBase.ServiceLocator.
    RegisterService<ISLChildWindowService>(
        new TestChildWindowService(), true);

    ViewModelBase.ServiceLocator.
    RegisterService<ISLMessageBoxService>(
        new TestMessageBoxService(), true);

}

So exactly what services are provided in the attached Silverlight MVVM library? Well, the following:

Message Box Service

This works pretty much as described and shown in my Cinch WPF MVVM Framework code / How to use the MessageBox service, and this other Cinch article talks about how to setup unit testing.

It should be noted that due to some current Silverlight restrictions, the WPF service provided by Cinch and this Silverlight MVVM library are different, but the idea is practically the same. Read the attached code comments/example and the links above, and all should be clear enough.

Here is what the attached Silverlight MVVM MessageBoxService API looks like:

C#
/// <summary>
/// This interface defines a interface that will allow 
/// a ViewModel to show a messagebox
/// </summary> 
public interface ISLMessageBoxService
{
    /// <summary>
    /// Shows an error message
    /// </summary>
    /// <param name="message">The error message</param>
    void ShowError(string message);

    /// <summary>
    /// Shows an information message
    /// </summary>
    /// <param name="message">The information message</param>
    void ShowInformation(string message);

    /// <summary>
    /// Shows an warning message
    /// </summary>
    /// <param name="message">The warning message</param>
    void ShowWarning(string message);

    /// <summary>
    /// Displays a Yes/No dialog and returns the user input.
    /// </summary>
    /// <param name="message">The message to be displayed.</param>
    /// <returns>User selection.</returns>
    CustomDialogResults ShowYesNo(string message);

    /// <summary>
    /// Displays a Yes/No/Cancel dialog and returns the user input.
    /// </summary>
    /// <param name="message">The message to be displayed.</param>
    /// <returns>User selection.</returns>
    CustomDialogResults ShowYesNoCancel(string message);

    /// <summary>
    /// Displays a OK/Cancel dialog and returns the user input.
    /// </summary>
    /// <param name="message">The message to be displayed.</param>
    /// <returns>User selection.</returns>
    CustomDialogResults ShowOkCancel(string message);

}

Open File Service

This works pretty much as described and shown in my Cinch WPF MVVM Framework code / How to use the Open File service, and this other Cinch article talks about how to setup unit testing.

It should be noted that due to some current Silverlight restrictions, the WPF service provided by Cinch and this Silverlight MVVM library are different, but the idea is practically the same. Read the attached code comments/example and the links above, and all should be clear enough.

Here is what the attached Silverlight MVVM OpenFileService API looks like:

C#
/// <summary>
/// This interface defines a interface that will allow 
/// a ViewModel to open a file
///
/// This is based on my WPF MVVM library called Cinch, which is available at
/// http://cinch.codeplex.com/
/// </summary>  
public interface ISLOpenFileService
{
    /// <summary>
    /// File
    /// </summary>
    FileInfo File { get; set; }

    /// <summary>
    /// Files
    /// </summary>
    IEnumerable<FileInfo> Files { get; set; }

    /// <summary>
    /// Filter
    /// </summary>
    String Filter { get; set; }

    /// <summary>
    /// FilterIndex
    /// </summary>
    Int32 FilterIndex { get; set; }

    /// <summary>
    /// FilterIndex
    /// </summary>
    Boolean Multiselect { get; set; }

    /// <summary>
    /// This method should show a window that allows a file to be selected
    /// </summary>
    /// <param name="owner">The owner window of the dialog</param>
    /// <returns>A bool from the ShowDialog call</returns>
    bool? ShowDialog();
}

Child Window Service

As I stated right at the start of this article, it has been a while since I used Silverlight, and all sorts of fancy/shiny things are now available. One such thing is the ChildWindow, which sounds cool. Don't know what I am talking about? Well, have a look at this post, it is quite a good introduction to ChildWindow: http://www.wintellect.com/CS/blogs/jprosise/archive/2009/04/29/silverlight-3-s-new-child-windows.aspx.

Now, that is all well and good, but we are good software devs, and we do not like code-behind as it is not testable. So we need to think about that, and see if we can achieve a UI service that allows us to show ChildWindow based Silverlight windows, and also allows us to get a DialogResult back from it, and also allows us to mimic this behaviour in a unit testing environment.

Now if you go and read the ChildWindow API documentation and lookup the Show() method, you will notice that MSDN states this: "Opens a ChildWindow and returns without waiting for the ChildWindow to close."

Hmmm, so not only do we have to come up with a cool service to deal with the ChildWindow, but we also have to deal with the fact that it is non-modal and therefore non-blocking.

Now I don't know about you, but I quite like my popup windows (which, let's face it, the ChildWindow is, even though it will also get used as a fancy progress indicator (as I do in the attached demo app)) to be Modal, so that I know when a user clicks OK, and I apply the changes they made, and if they click Cancel, I discard their work.

Does that sound do-able with the out of the box ChildWindow? Well, I don't think so. Is there anything we can do about it? The answer, hell yeah.

So what I am about to show you is a UI Service for dealing with ChildWindow, that can work as a Modeless (non-blocking service), but will also work as a Modal (blocking) popup window with just a little bit of pixie dust.

Here is what the attached Silverlight MVVM OpenFileService API looks like:

C#
/// <summary>
/// This interface defines a UI controller which can be used to display dialogs
/// in either modal or modaless form from a ViewModel.
///
/// This is based on my WPF MVVM library called Cinch, which is available at
/// http://cinch.codeplex.com/
/// </summary>   
public interface ISLChildWindowService
{
    /// <summary>
    /// Registers a type through a key.
    /// </summary>
    /// <param name="key">Key for the UI dialog</param>
    /// <param name="winType">Type which implements dialog</param>
    void Register(string key, Type winType);

    /// <summary>
    /// This unregisters a type and removes it from the mapping
    /// </summary>
    /// <param name="key">Key to remove</param>
    /// <returns>True/False success</returns>
    bool Unregister(string key);

    /// <summary>
    /// This method displays a modaless dialog associated with the given key.
    /// </summary>
    /// <param name="key">Key previously
    ///         registered with the UI controller.</param>
    /// <param name="state">Object state to associate with the dialog</param>
    /// <param name="completedProc">Callback used when UI closes (may be null), 
    /// which will have the Dialog state and result in it </param>
    void Show(string key, object state,
        EventHandler<UICompletedEventArgs> completedProc);

    /// <summary>
    /// This method displays a modal dialog associated with the given key.
    /// </summary>
    /// <param name="key">Key previously registered with the UI controller.</param>
    /// <param name="are">Wait handle to allow the popup to be shown Modally, and signal the
    /// blocked Waiting using code to continue</param>
    /// <param name="state">Object state to associate with the dialog</param>
    /// <param name="completedProc">Callback used when UI closes (may be null), 
    /// which will have the Dialog state and result in it </param>
    void ShowModally(string key, object state, AutoResetEvent are,
        EventHandler<UICompletedEventArgs> completedProc);
}

Where the actual Silverlight implementation of the service looks like this:

C#
public class SLChildWindowService : ISLChildWindowService
{
    #region Data
    private readonly Dictionary<string, Type> _registeredWindows;
    #endregion

    #region Ctor
    /// <summary>
    /// Constructor
    /// </summary>
    public SLChildWindowService()
    {
        _registeredWindows = new Dictionary<string, Type>();
    }
    #endregion

    #region Public Methods
    /// <summary>
    /// Registers a collection of entries
    /// </summary>
    /// <param name="startupData"></param>
    public void Register(Dictionary<string, Type> startupData)
    {
        foreach (var entry in startupData)
            Register(entry.Key, entry.Value);
    }

    /// <summary>
    /// Registers a type through a key.
    /// </summary>
    /// <param name="key">Key for the UI dialog</param>
    /// <param name="winType">Type which implements dialog</param>
    public void Register(string key, Type winType)
    {
        if (string.IsNullOrEmpty(key))
            throw new ArgumentNullException("key");
        if (winType == null)
            throw new ArgumentNullException("winType");
        if (!typeof(ChildWindow).IsAssignableFrom(winType))
            throw new ArgumentException("winType must be of type Window");

        lock (_registeredWindows)
        {
            _registeredWindows.Add(key, winType);
        }
    }

    /// <summary>
    /// This unregisters a type and removes it from the mapping
    /// </summary>
    /// <param name="key">Key to remove</param>
    /// <returns>True/False success</returns>
    public bool Unregister(string key)
    {
        if (string.IsNullOrEmpty(key))
            throw new ArgumentNullException("key");

        lock (_registeredWindows)
        {
            return _registeredWindows.Remove(key);
        }
    }

    /// <summary>
    /// This method displays a modaless dialog associated with the given key.
    /// </summary>
    /// <param name="key">Key previously registered with the UI controller.</param>
    /// <param name="state">Object state to associate with the dialog</param>
    /// <param name="completedProc">Callback used when UI closes (may be null), 
    /// which will have the Dialog state and result in it </param>
    public void Show(string key, object state,
        EventHandler<UICompletedEventArgs> completedProc)
    {
        ChildWindow win = CreateWindow(key, state,false,null, completedProc);
        if (win != null)
        {
            win.Show();
        }
    }

    /// <summary>
    /// This method displays a modal dialog associated with the given key.
    /// </summary>
    /// <param name="key">Key previously registered with the UI controller.</param>
    /// <param name="are">Wait handle to allow the popup to be shown Modally, and signal the
    /// blocked Waiting using code to continue</param>
    /// <param name="state">Object state to associate with the dialog</param>
    /// <param name="completedProc">Callback used when UI closes (may be null), 
    /// which will have the Dialog state and result in it </param>
    public void ShowModally(string key, object state, AutoResetEvent are,
        EventHandler<UICompletedEventArgs> completedProc)
    {
        ChildWindow win = CreateWindow(key, state, true,are, completedProc);
        if (win != null)
        {
            win.Show();
        }
    }

    #endregion

    #region Private Methods

    private void DoCallback(ChildWindow sender, 
        EventHandler<UICompletedEventArgs> completedProc)
    {
        if (completedProc != null)
        {
            completedProc(sender, 
               new UICompletedEventArgs(sender.DataContext, sender.DialogResult));
        }
    }

    /// <summary>
    /// This creates the WPF window from a key.
    /// </summary>
    /// <param name="key">Key</param>
    /// <param name="dataContext">DataContext (state) object</param>
    /// <param name="isModal">True if the ChildWindow
    /// needs to be created modally</param>
    /// <param name="are">Wait handle to allow
    /// the popup to be shown Modally, and signal the
    /// blocked Waiting using code to continue</param>
    /// <param name="completedProc">Callback</param>
    /// <returns>Success code</returns>
    private ChildWindow CreateWindow(string key, object dataContext, 
        Boolean isModal, AutoResetEvent are,
        EventHandler<UICompletedEventArgs> completedProc)
    {

        if (string.IsNullOrEmpty(key))
            throw new ArgumentNullException("key");

        Type winType;
        lock (_registeredWindows)
        {
            if (!_registeredWindows.TryGetValue(key, out winType))
                return null;
        }

        var win = (ChildWindow)Activator.CreateInstance(winType);
        win.DataContext = dataContext;

        if (dataContext != null)
        {
            var bvm = dataContext as ViewModelBase;
            if (bvm != null)
            {
                bvm.CloseRequest += ((s, e) => win.Close());
            }
        }

        //if there is a callback, call it on Closed
        if (completedProc != null)
        {
            win.Closed +=
                (s, e) =>
                {
                    //if modal and there is a wait handle associated signal it to continue
                    if (isModal)
                    {
                        if (are != null)
                        {
                            DoCallback(win, completedProc);
                            are.Set();
                        }
                        else
                        {
                            DoCallback(win, completedProc);
                        }
                    }
                    else
                    {
                        DoCallback(win, completedProc);
                    }
                };

        }

        return win;
    }
    #endregion
}

So first, the easy case. Oh, and for each of these, the following is assumed:

  1. You have a subclass of ChildWindow called ImagePopup
  2. The Silverlight MVVM OpenFileService has been setup in App.Startup as follows:
C#
ViewModelBase.ServiceLocator.RegisterService<ISLChildWindowService>(
                             new SLChildWindowService(), true);
ISLChildWindowService popupVisualizer = 
   ViewModelBase.ServiceLocator.GetService<ISLChildWindowService>();
popupVisualizer.Register("ImagePopup", typeof(ImagePopup));

Non-Modal (Non-blocking)

Showing the ChildWindow subclass is as easy as this, where we show the popup and setup a callback delegate, where the callback delegate will be called when the ChildWindow subclass' Closed event happens, as seen in the Silverlight implementation shown above:

C#
synContext.InvokeSynchronously(delegate()
{
    popupVisualizer.Show("ImagePopup", this, (s, e) =>
    {
        if (!e.Result.Value)
        {
            msgbox.ShowInformation(
                "You clicked Cancel. So this could be used " + 
                "to delete the File from the WebServer.\r\n" +
                "Possibly on a new thread, Instead of showing " + 
                "this blocking Modal MessageBox");
        }
    });
});

And to setup the ISLChildWindowService service in a Unit Test (assuming services are setup as described previously):

C#
[Setup]
private void setup()
{
    //setup services
    ViewModelBase.ServiceLocator.
    RegisterService<ISLOpenFileService>(
        new TestOpenFileService(), true);

    ViewModelBase.ServiceLocator.
    RegisterService<ISLChildWindowService>(
        new TestChildWindowService(), true);

    ViewModelBase.ServiceLocator.
    RegisterService<ISLMessageBoxService>(
        new TestMessageBoxService(), true);

}

Here is how we might set it up to mimic the user in a unit test:

C#
TestChildWindowService testChildWindowService =
  (TestChildWindowService)
    ViewModelBase.ServiceProvider.Resolve<ISLChildWindowService>();

//Queue up the response we expect for our given TestChildWindowService
//for a given ICommand/Method call within the test ViewModel
testChildWindowService.ShowDialogResultResponders.Enqueue
 (() =>
    {
        PopupDataContext context = new  PopupDataContext();
        context.SomeValue = 42;
        context.SomeOtherValue = "yes mate"
        return new UICompletedEventArgs(context, true);
    }
 );

And just for completeness, here is what the TestChildWindowService code looks like, which works much the same as the TestMessageBoxService when you get down to it. It just uses the callback Func<T> to allow the unit test to setup callbacks to supply the required user mocked responses.

C#
public class TestChildWindowService : ISLChildWindowService
{
    #region Data

    /// <summary>
    /// Queue of callback delegates for the Show methods expected
    /// for the item under test
    /// </summary>
    public Queue<Func<UICompletedEventArgs>> ShowResultResponders { get; set; }

    /// <summary>
    /// Queue of callback delegates for the ShowModally methods expected
    /// for the item under test
    /// </summary>
    public Queue<Func<UICompletedEventArgs>> ShowModallyResultResponders { get; set; }

    #endregion

    #region Ctor
    /// <summary>
    /// Ctor
    /// </summary>
    public TestChildWindowService()
    {
        ShowResultResponders = new Queue<Func<UICompletedEventArgs>>();
        ShowModallyResultResponders = new Queue<Func<UICompletedEventArgs>>();
    }
    #endregion

    #region ISLChildWindowService Members

    /// <summary>
    /// Registers a type through a key.
    /// </summary>
    /// <param name="key">Key for the UI dialog</param>
    /// <param name="winType">Type which implements dialog</param>
    public void Register(string key, Type winType)
    {
        //Nothing to do, as there will never be a UI
        //as we are testing the VMs
    }

    /// <summary>
    /// Does nothing, as nothing required for testing
    /// </summary>
    /// <param name="key">Key to remove</param>
    /// <returns>True/False success</returns>
    public bool Unregister(string key)
    {
        //Nothing to do, as there will never be a UI
        //as we are testing the VMs, simple return true
        return true;
    }

    /// <summary>
    /// This method displays a modaless dialog associated with the given key.
    /// </summary>
    /// <param name="key">Key previously registered with the UI controller.</param>
    /// <param name="state">Object state to associate with the dialog</param>
    /// <param name="completedProc">Callback used when UI closes (may be null), 
    /// which will have the Dialog state and result in it </param>
    public void Show(string key, object state,
        EventHandler<UICompletedEventArgs> completedProc)
    {

        if (ShowResultResponders.Count == 0)
            throw new Exception(
                "TestChildWindowService Show method expects " + 
                "a Func<UICompletedEventArgs> callback \r\n" +
                "delegate to be enqueued for each Show call");
        else
        {
            //Get and fire callback (defined in UnitTest)
            Func<UICompletedEventArgs> responder = ShowResultResponders.Dequeue();
            if (completedProc != null)
            {
                completedProc(null, responder());
            }
        }
    }

    /// <summary>
    /// This method displays a modal dialog associated with the given key.
    /// </summary>
    /// <param name="key">Key previously registered with the UI controller.</param>
    /// <param name="are">Wait handle to allow the popup to be shown Modally, and signal the
    /// blocked Waiting using code to continue</param>
    /// <param name="state">Object state to associate with the dialog</param>
    /// <param name="completedProc">Callback used when UI closes (may be null), 
    /// which will have the Dialog state and result in it </param>
    public void ShowModally(string key, object state, AutoResetEvent are,
        EventHandler<UICompletedEventArgs> completedProc)
    {
        if (ShowModallyResultResponders.Count == 0)
            throw new Exception(
                "TestChildWindowService Show method expects " + 
                "a Func<UICompletedEventArgs> callback \r\n" +
                "delegate to be enqueued for each Show call");
        else
        {
            //Get and fire callback (defined in UnitTest)
            Func<UICompletedEventArgs> responder = 
                           ShowModallyResultResponders.Dequeue();
            if (completedProc != null)
            {
                completedProc(null, responder());

                //As its Modal, signal the WaitHandle which
                //should be blocked waiting in calling code.
                //Tell it that it can now continue
                if (are != null)
                {
                    are.Set();
                }
            }
        }
    }
    #endregion
}

Modal (Blocking)

As I stated above, the default behaviour for ChildWindow in Silverlight is non-blocking (presumably as its main intention is to show status information while some long running operation happens... but people will be people, and use it differently, I am and would). So we need to do something a little differently if we want to make the UI block waiting for the ChildWindow.

It is however still very easy using the attached SLChildWindowService, and all you have to supply is one more bit of information.

Showing the ChildWindow subclass is as easy as this, where we show the popup and setup a callback delegate, where the callback delegate will be called when the ChildWindow subclass' Closed event happens, as seen in the Silverlight implementation shown above.

But also note that this time, we provide a AutoResetEvent WaitHandle to allow the calling code to block waiting until the ChildWindow subclass signals to this exact AutoResetEvent WaitHandle that it may proceed.

C#
AutoResetEvent are = new AutoResetEvent(false);
synContext.InvokeSynchronously(delegate()
{
    popupVisualizer.ShowModally("ImagePopup", this, are, (s, e) =>
    {
        if (!e.Result.Value)
        {
            msgbox.ShowInformation(
                "You clicked Cancel. So this could be used to " + 
                "delete the File from the WebServer.\r\n" +
                "Possibly on a new thread, Instead " + 
                "of showing this blocking Modal MessageBox");
        }
    });
});
are.WaitOne();

Let's have another look at the relevant section of the actual Silverlight ISLChildWindowService implementation to see how this AutoResetEvent WaitHandle gets signaled.

C#
//if there is a callback, call it on Closed
if (completedProc != null)
{
    win.Closed +=
        (s, e) =>
        {
            //if modal and there is a wait handle
            //associated signal it to continue
            if (isModal)
            {
                if (are != null)
                {
                    DoCallback(win, completedProc);
                    are.Set();
                }
                else
                {
                    DoCallback(win, completedProc);
                }
            }
            else
            {
                DoCallback(win, completedProc);
            }
        };
}

Testing Setup Differences Between Non-Modal / Modal

There is no difference in how you would setup a test version of this service for Non-Modal and Modal.

Other People's Work

I should point out that after I finished writing up this code and article text, I did a quick hunt around using Google to see if this has been a problem for anyone else, and found this: A ChildWindow management service for MVVM applications, which is great if you are using PRISM/Unity. As I say, I found that after I was done, and my service is very different, and does not rely on any IOC or PRISM for that matter, but if you use that combo, that article is quite interesting. Check it out.

Threading Helpers

I have included several threading helpers.

Dispatcher Extension Methods

These simple Dispatcher extension methods provide convenient syntax when working with the Dispatcher:

C#
public static class DispatcherExtensions
{

#if !SILVERLIGHT
   /// <summary>
   /// A simple threading extension method, to invoke a delegate
   /// on the correct thread if it is not currently on the correct thread
   /// which can be used with DispatcherObject types.
   /// </summary>
   /// <param name="dispatcher">The Dispatcher object on which to
   /// perform the Invoke</param>
   /// <param name="action">The delegate to run</param>
   /// <param name="priority">The DispatcherPriority for the invoke.</param>
   public static void InvokeIfRequired(this Dispatcher dispatcher,
           Action action, DispatcherPriority priority)
   {
       if (!dispatcher.CheckAccess())
       {
            dispatcher.Invoke(priority, action);
       }
       else
       {
            action();
       }
   }
#endif

    /// <summary>
    /// A simple threading extension method, to invoke a delegate
    /// on the correct thread if it is not currently on the correct thread
    /// which can be used with DispatcherObject types.
    /// </summary>
    /// <param name="dispatcher">The Dispatcher object on which to
    /// perform the Invoke</param>
    /// <param name="action">The delegate to run</param>
    public static void InvokeIfRequired(this Dispatcher dispatcher, Action action)
    {
        if (!dispatcher.CheckAccess())
        {
#if SILVERLIGHT
            dispatcher.BeginInvoke(action);
#else
            dispatcher.Invoke(DispatcherPriority.Normal, action);
#endif
        }
        else
        {
            action();
        }
    }

    /// <summary>
    /// A simple threading extension method, to invoke a delegate
    /// on the correct thread if it is not currently on the correct thread
    /// which can be used with DispatcherObject types.
    /// </summary>
    /// <param name="dispatcher">The Dispatcher object on which to
    /// perform the Invoke</param>
    /// <param name="action">The delegate to run</param>
    public static void InvokeInBackgroundIfRequired(this Dispatcher dispatcher, 
                                                    Action action)
    {
        if (!dispatcher.CheckAccess())
        {
#if SILVERLIGHT
            dispatcher.BeginInvoke(action);
#else
            dispatcher.Invoke(DispatcherPriority.Background, action);
#endif
        }
        else
        {
            action();
        }
    }

    /// <summary>
    /// A simple threading extension method, to invoke a delegate
    /// on the correct thread asynchronously if it is not currently on the correct thread
    /// which can be used with DispatcherObject types.
    /// </summary>
    /// <param name="dispatcher">The Dispatcher object on which to
    /// perform the Invoke</param>
    /// <param name="action">The delegate to run</param>
    public static void InvokeAsynchronouslyInBackground(this Dispatcher dispatcher, 
                                                        Action action)
    {
#if SILVERLIGHT
        dispatcher.BeginInvoke(action);
#else
                   dispatcher.BeginInvoke(DispatcherPriority.Background, action);
#endif
    }
}

Which allows you to do something like this:

C#
uiDispatcher.InvokeIfRequired(()=>
{
     //do something on UI
});

Dispatcher SynchronizationContext

When you use Windows Forms, there is a SynchronizationContext which may be used to send and post delegates on to. Which I discuss in this threading article. It is possible to create one for WPF, which is exactly what my good friend Daniel Vaughan did once, and I have included that code within this mini SL MVVM library. Basically, it boils down to this one UISynchronizationContext class. Cheers Daniel.

C#
public class UISynchronizationContext : ISynchronizationContext
{
    #region Data
    private DispatcherSynchronizationContext context;
    private Dispatcher dispatcher;
    private readonly object initializationLock = new object();
    #endregion

    #region Singleton implementation

    static readonly UISynchronizationContext instance = new UISynchronizationContext();

    /// <summary>
    /// Gets the singleton instance.
    /// </summary>
    /// <value>The singleton instance.</value>
    public static ISynchronizationContext Instance
    {
        get
        {
            return instance;
        }
    }

    #endregion

    #region Public Methods
    public void Initialize()
    {
        EnsureInitialized();
    }
    #endregion

    #region ISynchronizationContext Members

    public void Initialize(Dispatcher dispatcher)
    {
        ArgumentHelper.AssertNotNull(dispatcher, "dispatcher");

        lock (initializationLock)
        {
            this.dispatcher = dispatcher;
            context = new DispatcherSynchronizationContext(dispatcher);
        }
    }

    public void InvokeAsynchronously(SendOrPostCallback callback, object state)
    {
        ArgumentHelper.AssertNotNull(callback, "callback");
        EnsureInitialized();

        context.Post(callback, state);
    }

    public void InvokeAsynchronously(Action action)
    {
        ArgumentHelper.AssertNotNull(action, "action");
        EnsureInitialized();

        if (dispatcher.CheckAccess())
        {
            action();
        }
        else
        {
            dispatcher.BeginInvoke(action);
        }
    }

    public void InvokeSynchronously(SendOrPostCallback callback, object state)
    {
        ArgumentHelper.AssertNotNull(callback, "callback");
        EnsureInitialized();

        context.Send(callback, state);
    }

    public void InvokeSynchronously(Action action)
    {
        ArgumentHelper.AssertNotNull(action, "action");
        EnsureInitialized();

        if (dispatcher.CheckAccess())
        {
            action();
        }
        else
        {
            context.Send(delegate { action(); }, null);
        }
    }

    public bool InvokeRequired
    {
        get
        {
            EnsureInitialized();
            return !dispatcher.CheckAccess();
        }
    }

    #endregion

    #region Private Methods
    private void EnsureInitialized()
    {
        if (dispatcher != null && context != null)
        {
            return;
        }

        lock (initializationLock)
        {
            if (dispatcher != null && context != null)
            {
                return;
            }

            try
            {
        dispatcher = System.Windows.Deployment.Current.Dispatcher;
                context = new DispatcherSynchronizationContext(dispatcher);
            }
            catch (InvalidOperationException)
            {
                throw new Exception("Initialised called from non-UI thread.");
            }
        }
    }
    #endregion
}

Which we can then use to do all our Dispatcher related thread marshalling with, such as this:

C#
synContext.InvokeSynchronously(delegate()
{
    FileMessage = "Total file size: " + FileSize + " Uploading: " +
        string.Format("{0:###.00}%", (double)dataSent / (double)dataLength * 100);
});

Where the FileMessage property is being bound in the XAML, and the FileMessage property exists on the ViewModel, and this is inside a threaded context, so we must ensure that the FileMessage property is updated on the UI's Dispatcher thread.

What it Doesn't Do

There is no support for any of the following:

  • IEditableObject support for ViewModels
  • IDataErrorInfo / Validation / Business Rules
  • DataWrappers, as found in my Cinch WPF MVVM Framework
  • Running a ICommand in a ViewModel in response to a UI event such as MouseDown. This looks to be standard stuff in Silverlight using Microsoft.Windows.Interactivity.dll, and then making use of the TargetedTriggerAction<T>. I discuss this on my blog http://sachabarber.net/?p=510 (albeit that it is for WPF, which allows us to inherit from ICommandSource, where as Silverlight does not). I am sure someone can make it work just fine, and there is probably someone out there that has already done this.

All of which are covered by my Cinch WPF MVVM Framework, but this app took like two days to write, and my Cinch WPF MVVM Framework took ages to write. So one has to make allowances.

These could be added easily enough, now that the IEditableObject/IDataErrorInfo are part of the Silverlight SDK (IDataErrorInfo was missing in the past).

The Demo App Explained

Right, so now that I have explained the small Silverlight MVVM framework that is included in this code demo app.

Special Thanks / Credits

I should point out that this code makes use of some code that I found over at a one John Mendezes' blog: File Upload in Silverlight 3, which John states he grabbed from an Open Source CodePlex project: Silverlight Multi File Uploader v3.0.

All I wanted to do was mess around with Silverlight 4 and also try and do it in a good MVVM way, but I also needed to upload images as part of something bigger I am planning, so I started with John Mendezes' code.

Back to the Program

I suppose we need to steer back to the actual demo app, which if you recall, allows a user to upload a file from their local file system to a remote web server, and if the file is an image, it will be shown in a ChildWindow.

Well, believe it or not, now that we have covered the small Silverlight MVVM framework attached, it really boils down to a very simple object model.

We have a View (FileUpload) that makes use of a ViewModel (ImageUploadViewModel). John's original code was all code-behind, so the original exercise I set myself was to MVVM it.

Let's have a look at the MVVM-ed code:

Doing the File Upload

As stated, there is a ImageUploadViewModel which has a ICommand exposed which the Silverlight View FileUpload uses, as follows:

XML
<Button Content="Upload File" Command="{Binding UploadCommand}" />

And when the user clicks this button, the following logic is run in the ImageUploadViewModel; note the use of the ISLOpenFileService service to show an open file dialog.

C#
public ImageUploadViewModel()
{
    ofd = GetService<ISLOpenFileService>();
}

private void ExecuteUploadCommand()
{
    ofd.Multiselect = false;

    if (ofd.ShowDialog() == true)
    {
        if (ofd.File != null)
            fileToUpload = ofd.File;

        IsBusy = true;
        this.UploadFile();
    }
}

When the user picks a file, the process of uploading the file happens, which calls the UploadFile() method which looks like this:

C#
private void UploadFile()
{
    FileSize = this.GetFileSize(fileToUpload.Length);
    this.StartUpload(fileToUpload);
}

Which in turn calls the StartUpload() method, which looks like this, where this will be called numerous times to upload the file in chunks:

C#
private void StartUpload(FileInfo file)
{
    uploadedFile = file;
    fileStream = uploadedFile.OpenRead();
    dataLength = fileStream.Length;

    long dataToSend = dataLength - dataSent;
    bool isLastChunk = dataToSend <= ChunkSize;
    bool isFirstChunk = dataSent == 0;
    docType = imageFileExtensions.Contains(
                uploadedFile.Extension.ToLower()) ? 
                documentType.Image : documentType.Document;

    UriBuilder httpHandlerUrlBuilder = 
    new UriBuilder(string.Format("{0}/FileUpload.ashx",baseUri));
    httpHandlerUrlBuilder.Query = 
    string.Format("{5}file={0}&offset={1}&last={2}&first={3}&docType={4}", 
            uploadedFile.Name, dataSent, isLastChunk, isFirstChunk, docType, 
            string.IsNullOrEmpty(httpHandlerUrlBuilder.Query) ? "" : 
            httpHandlerUrlBuilder.Query.Remove(0, 1) + "&");

    HttpWebRequest webRequest = 
    (HttpWebRequest)WebRequest.Create(httpHandlerUrlBuilder.Uri);
    webRequest.Method = "POST";
    webRequest.BeginGetRequestStream(
    new AsyncCallback(WriteToStreamCallback), webRequest);
}

Where this upload is being directed to a standard HttpHandler (FileUpload.ashx) in the Silverlight hosting web application (TestSL4.Web) that actually saves the file to the remote webserver's virtual directory. This call to the HttpHandler (FileUpload.ashx) is made using Asynchronous Delegates (BeginInvoke/EndInvoke), so there is a AsyncCallback delegate provided to point to the WriteToStreamCallback() method.

Here is that HttpHandler code:

C#
<%@ WebHandler Language="C#" Class="FileUpload" %>

using System;
using System.Web;
using System.IO;
using System.Web.Hosting;
using System.Diagnostics;

/// <summary>
/// Saves the posted content as a file to the local web file system. The request
/// is expected to come from the <see cref="TestSL4.ImageUploadViewModel">
/// ImageUploadViewModel</see>
/// ViewModel
/// </summary>
public class FileUpload : IHttpHandler {

    private HttpContext _httpContext;
    private string _tempExtension = "_temp";
    private string _fileName;
    private string _docType;
    private bool _lastChunk;
    private bool _firstChunk;
    private long _startByte;
    private StreamWriter _debugFileStreamWriter;
    private TextWriterTraceListener _debugListener;

    public void ProcessRequest(HttpContext context)
    {
        _httpContext = context;

        if (context.Request.InputStream.Length == 0)
            throw new ArgumentException("No file input");

        try
        {
            GetQueryStringParameters();

            string uploadFolder = GetUploadFolder();
            string tempFileName = _fileName + _tempExtension;

            if (_firstChunk)
            {
                if (!Directory.Exists(
                        @HostingEnvironment.ApplicationPhysicalPath + "/" + uploadFolder))
                    Directory.CreateDirectory(
                        @HostingEnvironment.ApplicationPhysicalPath + "/" + uploadFolder);
                
                
                //Delete temp file
                if (File.Exists(@HostingEnvironment.ApplicationPhysicalPath + 
                    "/" + uploadFolder + "/" + tempFileName))
                    File.Delete(@HostingEnvironment.ApplicationPhysicalPath + 
                        "/" + uploadFolder + "/" + tempFileName);

                //Delete target file
                if (File.Exists(@HostingEnvironment.ApplicationPhysicalPath + 
                    "/" + uploadFolder + "/" + _fileName))
                    File.Delete(@HostingEnvironment.ApplicationPhysicalPath + 
                        "/" + uploadFolder + "/" + _fileName);

            }

            using (FileStream fs = File.Open(@HostingEnvironment.ApplicationPhysicalPath + 
                "/" + uploadFolder + "/" + tempFileName, FileMode.Append))
            {
                SaveFile(context.Request.InputStream, fs);
                fs.Close();
            }

            if (_lastChunk)
            {
                File.Move(HostingEnvironment.ApplicationPhysicalPath + "/" + uploadFolder + 
                    "/" + tempFileName, HostingEnvironment.ApplicationPhysicalPath + "/" + 
                    uploadFolder + "/" + _fileName);
            }
        }
        catch (Exception e)
        {
            throw;
        }
    }

    private void GetQueryStringParameters()
    {
        _fileName = _httpContext.Request.QueryString["file"];
        _docType = _httpContext.Request.QueryString["docType"];
        
        _lastChunk = string.IsNullOrEmpty(_httpContext.Request.QueryString["last"]) 
            ? true : bool.Parse(_httpContext.Request.QueryString["last"]);
        
        _firstChunk = string.IsNullOrEmpty(_httpContext.Request.QueryString["first"]) 
            ? true : bool.Parse(_httpContext.Request.QueryString["first"]);
        
        _startByte = string.IsNullOrEmpty(_httpContext.Request.QueryString["offset"]) 
            ? 0 : long.Parse(_httpContext.Request.QueryString["offset"]); ;
    }

    
    private void SaveFile(Stream stream, FileStream fs)
    {
        byte[] buffer = new byte[4096];
        int bytesRead;
        while ((bytesRead = stream.Read(buffer, 0, buffer.Length)) != 0)
        {
            fs.Write(buffer, 0, bytesRead);
        }
    }


    protected string GetUploadFolder()
    {
        string folder = ""; 

        switch (_docType)
        {
            case "Document":
                folder = "documents/uploads";
                break;
            case "Image":
                folder = "documents/images";
                break;
            default:
                folder = "documents";
                break;
        }
        
        if (string.IsNullOrEmpty(folder))
            folder = "documents";

        return folder;
    }

    public bool IsReusable {
        get {
            return false;
        }
    }
}

You can also notice that there is a AsyncCallback delegate provided to point to the WriteToStreamCallback() method.

C#
private void WriteToStreamCallback(IAsyncResult asynchronousResult)
{
    HttpWebRequest webRequest = (HttpWebRequest)asynchronousResult.AsyncState;
    Stream requestStream = webRequest.EndGetRequestStream(asynchronousResult);

    byte[] buffer = new Byte[4096];
    int bytesRead = 0;
    int tempTotal = 0;

    //Set the start position
    fileStream.Position = dataSent;

    //Read the next chunk
    while ((bytesRead = fileStream.Read(buffer, 0, buffer.Length)) 
    != 0 && tempTotal + bytesRead < ChunkSize)
    {
        requestStream.Write(buffer, 0, bytesRead);
        requestStream.Flush();

        dataSent += bytesRead;
        tempTotal += bytesRead;

        ////Show the progress change
        UpdateShowProgress(false);
    }

    requestStream.Close();

    //Get the response from the HttpHandler
    webRequest.BeginGetResponse(
    new AsyncCallback(ReadHttpResponseCallback), webRequest);

}

Showing the ChildWindow When the File has Completely Been Uploaded

As we want to know whether the file upload succeeded or not, the HttpHandler's response is also used within a AsyncCallback delegate provided to point to the ReadHttpResponseCallback() method. It is within the ReadHttpResponseCallback() method that the UI status is updated by calling the UpdateShowProgress() method, and the results of the HttpHandler (FileUpload.ashx) stream are examined, and if the entire original file bytes have not yet been sent, a new call to StartUpload() will be made to upload the next chunk.

C#
private void ReadHttpResponseCallback(IAsyncResult asynchronousResult)
{
    try
    {
        HttpWebRequest webRequest = 
                 (HttpWebRequest)asynchronousResult.AsyncState;
        HttpWebResponse webResponse = 
                 (HttpWebResponse)webRequest.EndGetResponse(asynchronousResult);
        StreamReader reader = new StreamReader(webResponse.GetResponseStream());

        string responsestring = reader.ReadToEnd();
        reader.Close();
    }
    catch
    {
        synContext.InvokeSynchronously(delegate()
        {
            ShowError();
        });
    }

    if (dataSent < dataLength)
    {
        //continue uploading the rest of the file in chunks
        StartUpload(uploadedFile);

        //Show the progress change
        UpdateShowProgress(false);
    }
    else
    {
        fileStream.Close();
        fileStream.Dispose();

        //Show the progress change
        UpdateShowProgress(true);
    }

}

If the full file contents have been uploaded, the file is shown in a ChildWindow as shown below:

C#
private void UpdateShowProgress(bool complete)
{
    if (complete)
    {
        synContext.InvokeSynchronously(delegate()
        {
            FileMessage = "complete";
        });

        if (docType == documentType.Image)
        {
            AutoResetEvent are = new AutoResetEvent(false);
            synContext.InvokeSynchronously(delegate()
            {
                FinalFilePath = String.Format(@"{0}/{1}/{2}", 
                   baseUri, GetUploadFolder(), uploadedFile.Name);
                uploadedFile = null;
                IsBusy = false;

                popupVisualizer.ShowModally("ImagePopup", this, are, (s, e) =>
                {
                    if (!e.Result.Value)
                    {
                        msgbox.ShowInformation(
                            "You clicked Cancel. So this could be used " + 
                            "to delete the File from the WebServer.\r\n" +
                            "Possibly on a new thread, " + 
                            "Instead of showing this blocking Modal MessageBox");
                    }
                });
            });
            are.WaitOne();
        }
        else
        {
            synContext.InvokeSynchronously(delegate()
            {
                uploadedFile = null;
                IsBusy = false;
            });
        }

        //RESET ALL VALUES TO THEY CAN UPLOAD AGAIN
        fileToUpload = null;
        dataSent = 0;
        dataLength = 0;
        fileStream = null;
        FileSize = "";
    }
    else
    {
        synContext.InvokeSynchronously(delegate()
        {
            FileMessage = "Total file size: " + FileSize + 
                " Uploading: " +
                string.Format("{0:###.00}%", 
                   (double)dataSent / (double)dataLength * 100);
        });
    }
}

That's It. Hope You Liked It

As always, votes / comments are welcome.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
United Kingdom United Kingdom
I currently hold the following qualifications (amongst others, I also studied Music Technology and Electronics, for my sins)

- MSc (Passed with distinctions), in Information Technology for E-Commerce
- BSc Hons (1st class) in Computer Science & Artificial Intelligence

Both of these at Sussex University UK.

Award(s)

I am lucky enough to have won a few awards for Zany Crazy code articles over the years

  • Microsoft C# MVP 2016
  • Codeproject MVP 2016
  • Microsoft C# MVP 2015
  • Codeproject MVP 2015
  • Microsoft C# MVP 2014
  • Codeproject MVP 2014
  • Microsoft C# MVP 2013
  • Codeproject MVP 2013
  • Microsoft C# MVP 2012
  • Codeproject MVP 2012
  • Microsoft C# MVP 2011
  • Codeproject MVP 2011
  • Microsoft C# MVP 2010
  • Codeproject MVP 2010
  • Microsoft C# MVP 2009
  • Codeproject MVP 2009
  • Microsoft C# MVP 2008
  • Codeproject MVP 2008
  • And numerous codeproject awards which you can see over at my blog

Comments and Discussions

 
QuestionBITS? Pin
Andreas Saurwein28-Jan-11 5:26
Andreas Saurwein28-Jan-11 5:26 
AnswerRe: BITS? Pin
Sacha Barber28-Jan-11 6:45
Sacha Barber28-Jan-11 6:45 
QuestionModal? Pin
James in London8-Apr-10 1:35
James in London8-Apr-10 1:35 
AnswerRe: Modal? Pin
Sacha Barber8-Apr-10 1:50
Sacha Barber8-Apr-10 1:50 
GeneralGreat article! Pin
Abhinav S13-Feb-10 8:05
Abhinav S13-Feb-10 8:05 
GeneralRe: Great article! Pin
Sacha Barber14-Feb-10 19:59
Sacha Barber14-Feb-10 19:59 
GeneralNice ! Pin
Nematjon Rahmanov12-Feb-10 23:22
Nematjon Rahmanov12-Feb-10 23:22 
GeneralRe: Nice ! Pin
Sacha Barber14-Feb-10 19:59
Sacha Barber14-Feb-10 19:59 
QuestionIs this normal ? Pin
Xmen Real 16-Jan-10 22:25
professional Xmen Real 16-Jan-10 22:25 
AnswerRe: Is this normal ? Pin
Sacha Barber17-Jan-10 2:01
Sacha Barber17-Jan-10 2:01 
GeneralRe: Is this normal ? Pin
Xmen Real 17-Jan-10 2:11
professional Xmen Real 17-Jan-10 2:11 
GeneralRe: Is this normal ? Pin
Sacha Barber17-Jan-10 2:18
Sacha Barber17-Jan-10 2:18 
GeneralRe: Is this normal ? Pin
Xmen Real 17-Jan-10 2:23
professional Xmen Real 17-Jan-10 2:23 
GeneralDammit man... Pin
lepipele15-Jan-10 13:15
lepipele15-Jan-10 13:15 
GeneralRe: Dammit man... Pin
Sacha Barber17-Jan-10 1:57
Sacha Barber17-Jan-10 1:57 
GeneralRe: Dammit man... Pin
lepipele18-Jan-10 4:49
lepipele18-Jan-10 4:49 
GeneralRe: Dammit man... Pin
Sacha Barber18-Jan-10 4:57
Sacha Barber18-Jan-10 4:57 
GeneralAwesome Pin
Xmen Real 14-Jan-10 18:41
professional Xmen Real 14-Jan-10 18:41 
GeneralRe: Awesome Pin
Sacha Barber14-Jan-10 21:41
Sacha Barber14-Jan-10 21:41 
GeneralBug in the code Pin
Xmen Real 14-Jan-10 18:20
professional Xmen Real 14-Jan-10 18:20 
GeneralRe: Bug in the code Pin
Sacha Barber14-Jan-10 21:40
Sacha Barber14-Jan-10 21:40 
GeneralRe: Bug in the code Pin
Xmen Real 14-Jan-10 22:10
professional Xmen Real 14-Jan-10 22:10 
GeneralRe: Bug in the code Pin
Sacha Barber14-Jan-10 22:41
Sacha Barber14-Jan-10 22:41 
Didn't expect what from me, I am saying this works for me.

What are you talking about.

Sacha Barber
  • Microsoft Visual C# MVP 2008/2009
  • Codeproject MVP 2008/2009
Your best friend is you.
I'm my best friend too. We share the same views, and hardly ever argue

My Blog : sachabarber.net

GeneralRe: Bug in the code Pin
Xmen Real 14-Jan-10 22:47
professional Xmen Real 14-Jan-10 22:47 
GeneralRe: Bug in the code Pin
Sacha Barber14-Jan-10 23:33
Sacha Barber14-Jan-10 23:33 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.