Property Copier

/// <summary>
    /// A method of aliasing two different property names to have the same content when copied
    /// </summary>
    public class TypeCopyPropertyAliasAttribute : Attribute
    {
        public TypeCopyPropertyAliasAttribute(string typeAlias)
        {
            this.Name = typeAlias;
        }

        public string Name { get; set; }
    }

    public abstract class PropertyCopier
    {
        private PropertyCopier()
        {
        }

		/// <summary>
		/// Return a list of items of a specified type by copying all the properties of each sub-item in a collection
		/// </summary>
		/// <typeparam name="TOutput">The output type</typeparam>
		/// <typeparam name="TInput">The input type</typeparam>
		/// <param name="input">The collection to convert</param>
		/// <returns>The converted collection</returns>
		public static ICollection<TOutput> ConvertCollectionToViewModelList<TOutput, TInput>(ICollection<TInput> input)
			where TOutput : new()
		{
			List<TOutput> returnCollection = new List<TOutput>();
			foreach (TInput sourceItem in input)
			{
				returnCollection.Add(PropertyCopier.CreateFrom<TOutput, TInput>(sourceItem));
			}
			return returnCollection.ToList();
		}


		/// <summary>
		/// Creates an instance of an output generic type and fills it based on the input object
		/// </summary>
		/// <typeparam name="TOutputType"></typeparam>
		/// <typeparam name="TInputType"></typeparam>
		/// <param name="sourceItem"></param>
		/// <returns></returns>
		public static TOutputType CreateFrom<TOutputType, TInputType>(TInputType sourceItem) where TOutputType : new()
		{
			TOutputType output = new TOutputType();
			Copy(sourceItem, output);
			return output;
		}

		/// <summary>
		/// Clones an instance of an output generic type and fills it based on the input object
		/// </summary>
		/// <typeparam name="TInputType"></typeparam>
		/// <param name="sourceItem"></param>
		/// <returns></returns>
		public static TInputType CreateFrom<TInputType>(TInputType sourceItem) where TInputType : new()
		{
			TInputType output = new TInputType();
			Copy(sourceItem, output);
			return output;
		}

		/// <summary>
		/// Clones an instance of an output generic type and fills it based on the input object using only basic data types
		/// </summary>
		/// <typeparam name="TInputType"></typeparam>
		/// <param name="sourceItem"></param>
		/// <returns></returns>
		public static TInputType CreateFromBasicPublicOnly<TInputType>(TInputType sourceItem) where TInputType : new()
		{
			TInputType output = new TInputType();
			Copy(sourceItem, output, publicOnly: false, systemTypesOnly: true, basicTypesOnly: true);
			return output;
		}

		/// <summary>
		/// Copies all the matching property contents from the input to the output object, if no fields
		/// match it will throw an exception
		/// </summary>
		/// <remarks>
		/// This is Simon's way of lazily copying properties between two objects with similar, 
		/// but not exactly the same purpose - for example the registration page to a User and a Driver
		/// </remarks>
		/// <param name="input">The input object</param>
		/// <param name="output">The output object</param>
		public static void Copy(object input, object output, bool publicOnly = false, bool systemTypesOnly = false, bool basicTypesOnly = false)
        {
            Type inputType = input.GetType();
            Type outputType = output.GetType();

			PropertyInfo[] inProps;
			if (publicOnly)
			{
				inProps = inputType.GetProperties(BindingFlags.Public);
			}
			else
			{
				inProps = inputType.GetProperties();
			}
            PropertyInfo[] outProps = outputType.GetProperties();

            bool matchedOne = false;

            foreach (PropertyInfo inProp in inProps)
            {
                List<string> inAliases = getAliasesForProperty(inProp);

                foreach (PropertyInfo outProp in outProps)
                {
                    List<string> outAliases = getAliasesForProperty(outProp);
                    if ((inProp.Name == outProp.Name 
                        || inAliases.Contains(outProp.Name)
                        || outAliases.Contains(inProp.Name)
                        )
                        && inProp.CanRead
                        && outProp.CanWrite)
                    {
						if (systemTypesOnly)
						{
							if (!inProp.PropertyType.AssemblyQualifiedName.ToLower().Contains("system"))
								break;
						}

						if (basicTypesOnly)
						{
							//we only take things like string, int and DateTime here; basically anything in System is fine plus structs
							if (inProp.PropertyType.IsClass && inProp.PropertyType.Namespace!="System")
								break;
						}

                        object inputValue = inProp.GetValue(input, null);
						if (inputValue == null) //can't use .Equals on null, so we handle it as a special case
						{
							outProp.SetValue(output, null, null);
						}
						else
						{

							//if we can read the destination property, we check it's different and only update if 
							//it is (makes notification of changes work properly)
							if (outProp.CanRead)
							{
								object outputExistingValue = outProp.GetValue(output, null);
								if (!inputValue.Equals(outputExistingValue))
								{
									outProp.SetValue(output, inputValue, null);
								}
							}
							else
							{
								outProp.SetValue(output, inputValue, null);
							}
						}
                        matchedOne = true;
                    }
                }
            }

            if (!matchedOne)
            {
                throw new Exception("Unable to match any properties between those two types");
            }
        }

		public static List<string> GetPropertiesNamesOrAliases(object item)
		{
			List<string> propertyNames = new List<string>();

			Type inputType = item.GetType();
            
            PropertyInfo[] inProps = inputType.GetProperties();
            
			foreach (PropertyInfo inProp in inProps)
			{
				List<string> inAliases = getAliasesForProperty(inProp);
				if (inAliases.Count > 0)
				{
					propertyNames.AddRange(inAliases);
				}
				else
				{
					propertyNames.Add(inProp.Name);
				}
			}
			return propertyNames.ToList();
		}

		public static string GetPropertyStringValueByNameOrAlias(object item, string propertyNameOrAlias)
		{
			Type inputType = item.GetType();
			var propType = inputType.GetProperty(propertyNameOrAlias);
			if (propType!=null)
			{
				object data = propType.GetValue(item, null);
				return data!=null ? data.ToString() : "";
			}
			else
			{
				PropertyInfo[] inProps = inputType.GetProperties();

				foreach (PropertyInfo inProp in inProps)
				{
					List<string> inAliases = getAliasesForProperty(inProp);
					if (inAliases.Contains(propertyNameOrAlias))
					{
						object data = inProp.GetValue(item, null);
						return data != null ? data.ToString() : "";
					}
				}
			}

			return "";
		}

        private static List<string> getAliasesForProperty(PropertyInfo prop)
        {
            List<string> aliases = new List<string>();
            object[] attributes = prop.GetCustomAttributes(typeof(TypeCopyPropertyAliasAttribute), true);
            foreach (object o in attributes)
            {
                TypeCopyPropertyAliasAttribute alias = o as TypeCopyPropertyAliasAttribute;
                if (o != null)
                {
                    aliases.Add(alias.Name);
                }
            }
            return aliases;
        }
    }