Click here to Skip to main content
15,896,201 members
Articles / DevOps / Testing

Composite Application Reloaded

Rate me:
Please Sign up or sign in to vote.
4.88/5 (38 votes)
11 May 2011CPOL12 min read 118.4K   1.5K   95  
A much simpler composite application library.
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.Linq;
using System.Reflection;
using System.Windows;
using System.ComponentModel.Composition.Primitives;
using System.Diagnostics;
using System.ComponentModel;
using System.Text;

namespace Galador.Applications
{
	/// <summary>
	/// This is a centralized class to register assembly for the MEF catalogs 
	/// and View for model, using <seealso cref="ExportAttribute"/> and <see cref="DataViewAttribute"/>
	/// </summary>
	public static class Composition
	{
		#region Compose()

		/// <summary>
		/// Call upon MEF to fill all <see cref="ImportAttribute"/> of an object.
		/// </summary>
		/// <param name="o"></param>
		public static void Compose(object o)
		{
			Compose(null, o);
		}
		public static void Compose(CompositionContainer cc, object o)
		{
			if (o == null)
				return;
			if (cc == null)
				cc = Container;

			var batch = new CompositionBatch();
			batch.AddPart(o);
			cc.Compose(batch);
		}

		#endregion

		#region GetInstance()

		/// <summary>
		/// Ask MEF for a given exported object. It's a unique shared object.
		/// </summary>
		public static T GetInstance<T>(CompositionContainer cc = null)
		{
			if (cc == null)
				cc = Container;
			var result = cc.GetExportedValue<T>();
			if (result == null)
				Debug.WriteLine(string.Format("Composition.GetInstance<{0}>()", typeof(T).FullName), "Composition Failure");
			return result;
		}

		#endregion

		#region GetView()

		/// <summary>
		/// Get the view for an object. A UI element would be returned verbatim.
		/// For other, the API try to find the appropriate view tagged with the type 
		/// of the data as its <see cref="DataViewAttribute.DataType"/>.
		/// </summary>
		/// <remarks>It will se the data as the <see cref="FrameworkElement.DataContext"/>
		/// and <see cref="Compose(object)"/> the view</remarks>.
		public static object GetView(object data, object location = null)
		{
			return GetView(null, data, location);
		}

		public static object GetView(this CompositionContainer cc, object data, object location = null)
		{
			if (data == null)
				return null;

			if (data is UIElement)
				return (UIElement)data;

			var tt = data.GetType();
			foreach (var item in ContextCatalog.Views)
			{
				if (!item.Item3.Candidate(tt, location))
					continue;
				object result = item.Item1.CreatePart().GetExportedValue(item.Item2);
				var ui = result as FrameworkElement;
				if (ui != null)
				{
					ui.DataContext = data;
					Compose(cc, ui);
					return ui;
				}
			}

#if DEBUG
			var sb = new StringBuilder();
			sb.AppendFormat("Composition.GetView({0}, {1})", data.GetType().FullName, location);
			var possibles = (
				from item in ContextCatalog.Views
				where item.Item3.DataType.IsAssignableFrom(tt)
				select item.Item3.Location
				).ToList();
			if (possibles.Count > 0)
			{
				sb.Append(" returns null, locations found (");
				for (int i = 0; i < possibles.Count; i++)
				{
					if (i > 0)
						sb.Append(", ");
					sb.Append(possibles[i]);
				}
				sb.Append(")");
			}
			Debug.WriteLine(sb.ToString(), "Composition Failure");
#endif
			return data;
		}

		#endregion

		#region AP: DesignerDataContext

		public static Type GetDesignerDataContext(DependencyObject obj)
		{
			return (Type)obj.GetValue(DesignerDataContextProperty);
		}

		public static void SetDesignerDataContext(DependencyObject obj, Type value)
		{
			obj.SetValue(DesignerDataContextProperty, value);
		}

		public static readonly DependencyProperty DesignerDataContextProperty = DependencyProperty.RegisterAttached(
			"DesignerDataContext",
			typeof(Type),
			typeof(Composition),
			new PropertyMetadata(DesignerDataContextChangedCallback));

		private static void DesignerDataContextChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
		{
			if (!DesignerProperties.GetIsInDesignMode(d))
				return;
			CurrentContext = ServiceContext.DesignTime;

			var fe = d as FrameworkElement;
			if (fe == null)
				return;

			DesignRegister(d.GetType());

			var t = (Type)e.NewValue;
			DesignRegister(t);

			var get = typeof(Composition).GetMethod("GetInstance", BindingFlags.Public | BindingFlags.Static).MakeGenericMethod(t);
			var dc = get.Invoke(null, new object[] { Container });
			if (dc != null)
			{
				if (dc is IDesignTimeAware)
					((IDesignTimeAware)dc).DesignTimeInitialization();
				// if(fe.IsLoaded) is not supported on Silverlight
				fe.DataContext = dc;
				fe.Loaded += (sender, arg) => fe.DataContext = dc;
			}
		}

		private static void DesignRegister(Type t)
		{
			if (t == null)
				return;
			var ass = t.Assembly;
			var isThere = (
				from c in Catalog.Catalogs
				let ac = c as AssemblyCatalog
				where ac.Assembly == ass
				select ac
			).FirstOrDefault() != null;
			if (!isThere)
				Catalog.Catalogs.Add(new AssemblyCatalog(ass));
		}

		#endregion

		#region Catalog, Container, CurrentContext, Register(), Reset()

		/// <summary>
		/// Used to identify which <see cref="ExportService"/> should be loaded.
		/// It is <see cref="ServiceContext.Runtime"/> by default, automatically set
		/// set <see cref="ServiceContext.DesignTime"/> by <see cref="DesignerDataContext"/> property.
		/// <see cref="ServiceContext.Test"/> could be set by code if needs be.
		/// </summary>
		public static ServiceContext CurrentContext
		{
			get { return ContextCatalog.CurrentContext; }
			set { ContextCatalog.CurrentContext = value; }
		}

		static object locker = new object();

		/// <summary>
		/// Register an assembly to be in the MEF catalog as well as one to look for
		/// view for model (<seealso cref="DataViewAttribute"/>)
		/// </summary>
		public static void Register(params Assembly[] assemblies)
		{
			if (assemblies == null)
				return;
			foreach (var a in assemblies)
				if (a != null)
					Catalog.Catalogs.Add(new AssemblyCatalog(a));
		}

		public static void Register(params Type[] types)
		{
			if (types == null || types.Length == 0)
				return;
			Catalog.Catalogs.Add(new TypeCatalog(types));
		}

		/// <summary>
		/// Empty the catalog and container, in effect all recomposition further will use new objects!
		/// </summary>
		public static void Reset()
		{
			lock (locker)
			{
				catalog = null;
				container = null;
				contextCatalog = null;
			}
		}

		public static AggregateCatalog Catalog
		{
			get
			{
				if (catalog == null)
					lock (locker)
						if (catalog == null)
							catalog = new AggregateCatalog();
				return catalog;
			}
		}
		static AggregateCatalog catalog;

		static ContextCatalog ContextCatalog
		{
			get
			{
				if (contextCatalog == null)
					lock (locker)
						if (contextCatalog == null)
							contextCatalog = new ContextCatalog(Catalog);
				return contextCatalog;
			}
		}
		static ContextCatalog contextCatalog;

		public static CompositionContainer Container
		{
			get
			{
				if (container == null)
					lock (locker)
						if (container == null)
							container = new CompositionContainer(ContextCatalog, true);
				return container;
			}
		}
		static CompositionContainer container;

		#endregion
	}
}

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 Code Project Open License (CPOL)


Written By
Software Developer (Senior) http://www.ansibleww.com.au
Australia Australia
The Australia born French man who went back to Australia later in life...
Finally got over life long (and mostly hopeless usually, yay!) chronic sicknesses.
Worked in Sydney, Brisbane, Darwin, Billinudgel, Darwin and Melbourne.

Comments and Discussions