Click here to Skip to main content
15,893,508 members
Articles / Desktop Programming / XAML

Building Modular Silverlight Applications

,
Rate me:
Please Sign up or sign in to vote.
4.90/5 (12 votes)
5 Feb 2009CDDL16 min read 107.2K   1.4K   88  
This article presents a flexible and practical reusable control that is essential to modular Silverlight applications. It helps to improve a large Silverlight application's composite structure and run time performance.
using System;
using System.Net;
using System.Windows;
using System.ComponentModel;
using System.Windows.Resources;
using System.Reflection;
using System.IO;
using System.Windows.Controls;

namespace SilverModule
{


	/// <summary>
	/// SilverModule Model and Controller
	/// </summary>
	public class ModuleMaker : INotifyPropertyChanged
	{
		/// <summary>
		/// data binding event handler
		/// </summary>
		public event PropertyChangedEventHandler PropertyChanged = delegate { };

		/// <summary>
		/// module content is instantiated and ready to render after download
		/// </summary>
		public event EventHandler<ModuleReadyEventArgs> ModuleContentReady = delegate { };

		/// <summary>
		/// internal WebClient instance to communicate with server for module downloading
		/// </summary>
		private WebClient _webClient = null;
		
		/// <summary>
		/// Data bind field to loading progress prompt or error message display
		/// </summary>
		private string _statusMessage = "";
		public string StatusMessage 
		{
			get { return _statusMessage; }
			set { _statusMessage = value; PropertyChanged(this, new PropertyChangedEventArgs("StatusMessage"));	}
		}

		/// <summary>
		/// The assembly name only, no extension (.xap, or .dll), MUST be set before using other properties
		/// Also assuming the package name is [ModuleName].xap and it locates at the same folder as where the loading Silverlight app (main xap) is
		/// Also assuming the UserControl Type name implemented inside [ModuleName] is also the same as [ModuleName]
		/// </summary>
		private string _moduleName;
		public string ModuleName 
		{
			get
			{
				return _moduleName;
			}
			set
			{
				if (String.IsNullOrEmpty(value))
					throw new ArgumentNullException("ModuleName should never be empty or null!");
				
				if (value != _moduleName)
				{//new module name is set, needs to start download
					_moduleName = value;
					StartToDownloadModule();
				}
			}
		}

		private string _moduleAssemblyName;
		public string ModuleAssemblyName 
		{
			get
			{//if the assemply name is not set explicitly, use the ModuleName appended .DLL as default one
				return (String.IsNullOrEmpty(this._moduleAssemblyName)) ? this.ModuleName + ".dll" : this._moduleAssemblyName;
			}
			set 
			{
				this._moduleAssemblyName = value;
			}
		}


		/// <summary>
		/// optional property
		/// to be loaded module path relative to the main XAP, start with "/" but NOT ended with "/"
		/// </summary>
		public string ModuleRelativePath { get; set; }

		/// <summary>
		/// optional property
		/// if not set, will use ModuleName as Module Type Name
		/// </summary>
		private string _moduleTypeName;
		public string ModuleTypeName 
		{
			get
			{//assuming the type name will be namespaceName[same as module name].className[same as module name]
				return String.IsNullOrEmpty(_moduleTypeName) ? ModuleName + "." + ModuleName : _moduleTypeName;
			}
			set
			{
				_moduleTypeName = value;
			}
		}

		/// <summary>
		/// internal property
		/// the downloading URL for the module, ready-only
		/// </summary>
		public string  ModuleURL 
		{ 
			get
			{
				if (String.IsNullOrEmpty(ModuleName))
					throw new ArgumentNullException("ModuleName needs to be set before get ModuleURL");
				
				//if relative path is not set, assuming the module will be at the same folder as where main app.xap is
				string moduleFolder = String.IsNullOrEmpty(ModuleRelativePath) ? "" : ModuleRelativePath + "/";
				
				return moduleFolder + ModuleName + ".xap";
			}
 		}

		/// <summary>
		/// Write-only loading progress value, will update data bound field value
		/// </summary>
		public int LoadingProcess
		{
			set
			{
				if (value <= 2)
					this.StatusMessage = "Start loading...";
				else if (value >= 99)
					this.StatusMessage = String.Format("Loading {0} done.", ModuleName);
				else
					this.StatusMessage = String.Format("Loading {0}: {1}%...", ModuleName, value);
			}
		}

		/// <summary>
		/// Write-only error message that will be shown
		/// </summary>
		public string  Error 
		{
			set
			{
				this.StatusMessage = String.Format("Failed to load {0}: {1}", ModuleName, value);
			}
		}

		/// <summary>
		/// private helper functions
		/// </summary>
		private void StartToDownloadModule()
		{
			if (null == _webClient)
			{//initialize the downloader
				_webClient = new WebClient();
				_webClient.DownloadProgressChanged += new DownloadProgressChangedEventHandler(onDownloadProgressChanged);
				_webClient.OpenReadCompleted += new OpenReadCompletedEventHandler(onOpenReadCompleted);
			}

			if (_webClient.IsBusy)
			{//needs to cancel the previous loading
				this.StatusMessage = "Cancelling previous downloading...";
				_webClient.CancelAsync();
				return;
			}

			Uri xapUrl = new Uri(this.ModuleURL, UriKind.RelativeOrAbsolute);
			_webClient.OpenReadAsync(xapUrl);
		}

		private void onDownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e)
		{
			LoadingProcess = e.ProgressPercentage;
		}

		private void onOpenReadCompleted(object sender, OpenReadCompletedEventArgs e)
		{
			if (null != e.Error)
			{//report the download error
				this.Error = e.Error.Message;
				return;
			}
			
			if (e.Cancelled)
			{// cancelled previous downloading, needs to re-start the loading
				StartToDownloadModule();
				return;
			}

			// Load a particular assembly from XAP
			Assembly aDLL = GetAssemblyFromPackage(this.ModuleAssemblyName, e.Result);
			if (null == aDLL)
			{//report the assembly extracting error
				this.Error = "Module downloaded but failed to extract the assembly. Please check the assembly name.";
				return;
			}

			// Get an instance of the XAML object
			UserControl content = aDLL.CreateInstance(this.ModuleTypeName) as UserControl;
			if (null == content)
			{//report the type instnatiating error
				this.Error = "Module downloaded and the assembly extracted but failed to instantiate the custome type. Please check the type name.";
				return;
			}

			//tell the event handler it's ready to display
			ModuleContentReady(this, new ModuleReadyEventArgs() { ModuleContent = content } );
		}

		private Assembly GetAssemblyFromPackage(string assemblyName, Stream xapStream)
		{
			Assembly aDLL = null;

			// Initialize
			Uri assemblyUri = new Uri(assemblyName, UriKind.Relative);
			StreamResourceInfo resPackage = new StreamResourceInfo(xapStream, null);
			if (null == resPackage)
				return aDLL;

			StreamResourceInfo resAssembly = Application.GetResourceStream(resPackage, assemblyUri);
			if (null == resAssembly)
				return aDLL;

			// Extract an assembly 
			AssemblyPart part = new AssemblyPart();
			aDLL = part.Load(resAssembly.Stream);

			return aDLL;
		}

	}

}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Common Development and Distribution License (CDDL)


Written By
Technical Lead
United States United States
https://github.com/modesty

https://www.linkedin.com/in/modesty-zhang-9a43771

https://twitter.com/modestyqz

Written By
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions