//===============================================================================
// Microsoft Exception Management Application Block for .NET
// http://msdn.microsoft.com/library/en-us/dnbda/html/emab-rm.asp
//
// ExceptionManager.cs
// This file contains the ExceptionManager class, which manages all publishing 
// of exceptions, and the default publisher class, which publishes exception 
// information to the Event Log.
//
// For more information see the Implementing the Exception Manager Component
// section of the Exception Management Application Block Implementation Overview. 
//===============================================================================
// Copyright (C) 2000-2001 Microsoft Corporation
// All rights reserved.
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY
// OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT
// LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR
// FITNESS FOR A PARTICULAR PURPOSE.
//===============================================================================

using System;
using System.Resources;
using System.Reflection;
using System.Collections;
using System.Configuration;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Xml;
using System.Xml.Serialization;
using System.Runtime.Serialization;
using System.Threading;
using System.Collections.Specialized;
using System.Security;
using System.Security.Principal;
using System.Security.Permissions;

namespace Microsoft.ApplicationBlocks.ExceptionManagement
{
	#region ExceptionManager Class
	/// <summary>
	/// The Exception Manager class manages the publishing of exception information based on settings in the configuration file.
	/// </summary>
	public sealed class ExceptionManager
	{
		/// <summary>
		/// Private constructor to restrict an instance of this class from being created.
		/// </summary>
		private ExceptionManager()
		{
		}

		// Member variable declarations
		private const string EXCEPTIONMANAGEMENT_CONFIG_SECTION = "exceptionManagement";
		private readonly static string EXCEPTIONMANAGER_NAME = typeof(ExceptionManager).Name;

		// Resource Manager for localized text.
		private static ResourceManager resourceManager = new ResourceManager(typeof(ExceptionManager).Namespace + ".ExceptionManagerText",Assembly.GetAssembly(typeof(ExceptionManager)));
				
		/// <summary>
		/// Static method to publish the exception information.
		/// </summary>
		/// <param name="exception">The exception object whose information should be published.</param>
		public static void Publish(Exception exception)
		{			
			ExceptionManager.Publish(exception, null);					
		}

		/// <summary>
		/// Static method to publish the exception information and any additional information.
		/// </summary>
		/// <param name="exception">The exception object whose information should be published.</param>
		/// <param name="additionalInfo">A collection of additional data that should be published along with the exception information.</param>
		public static void Publish(Exception exception, NameValueCollection additionalInfo)
		{
			try
			{
				#region Load the AdditionalInformation Collection with environment data.
				// Create the Additional Information collection if it does not exist.
				if (null == additionalInfo) additionalInfo = new NameValueCollection();

				// Add environment information to the information collection.
				try
				{
					additionalInfo.Add(EXCEPTIONMANAGER_NAME + ".MachineName", Environment.MachineName);
				}
				catch(SecurityException)
				{
					additionalInfo.Add(EXCEPTIONMANAGER_NAME + ".MachineName", resourceManager.GetString("RES_EXCEPTIONMANAGEMENT_PERMISSION_DENIED"));
				}
				catch
				{
					additionalInfo.Add(EXCEPTIONMANAGER_NAME + ".MachineName", resourceManager.GetString("RES_EXCEPTIONMANAGEMENT_INFOACCESS_EXCEPTION"));
				}
					
				try
				{
					additionalInfo.Add(EXCEPTIONMANAGER_NAME + ".TimeStamp", DateTime.Now.ToString());
				}
				catch(SecurityException)
				{					
					additionalInfo.Add(EXCEPTIONMANAGER_NAME + ".TimeStamp",resourceManager.GetString("RES_EXCEPTIONMANAGEMENT_PERMISSION_DENIED"));
				}
				catch
				{
					additionalInfo.Add(EXCEPTIONMANAGER_NAME + ".TimeStamp", resourceManager.GetString("RES_EXCEPTIONMANAGEMENT_INFOACCESS_EXCEPTION"));
				}					
									
				try
				{
					additionalInfo.Add(EXCEPTIONMANAGER_NAME + ".FullName", Assembly.GetExecutingAssembly().FullName);
				}
				catch(SecurityException)
				{
					additionalInfo.Add(EXCEPTIONMANAGER_NAME + ".FullName", resourceManager.GetString("RES_EXCEPTIONMANAGEMENT_PERMISSION_DENIED"));
				}	
				catch
				{
					additionalInfo.Add(EXCEPTIONMANAGER_NAME + ".FullName", resourceManager.GetString("RES_EXCEPTIONMANAGEMENT_INFOACCESS_EXCEPTION"));
				}
					
				try
				{
					additionalInfo.Add(EXCEPTIONMANAGER_NAME + ".AppDomainName", AppDomain.CurrentDomain.FriendlyName);
				}
				catch(SecurityException)
				{
					additionalInfo.Add(EXCEPTIONMANAGER_NAME + ".AppDomainName", resourceManager.GetString("RES_EXCEPTIONMANAGEMENT_PERMISSION_DENIED"));
				}
				catch
				{
					additionalInfo.Add(EXCEPTIONMANAGER_NAME + ".AppDomainName", resourceManager.GetString("RES_EXCEPTIONMANAGEMENT_INFOACCESS_EXCEPTION"));
				}
						
				try
				{
					additionalInfo.Add(EXCEPTIONMANAGER_NAME + ".ThreadIdentity", Thread.CurrentPrincipal.Identity.Name);
				}
				catch(SecurityException)
				{
					additionalInfo.Add(EXCEPTIONMANAGER_NAME + ".ThreadIdentity", resourceManager.GetString("RES_EXCEPTIONMANAGEMENT_PERMISSION_DENIED"));
				}
				catch
				{
					additionalInfo.Add(EXCEPTIONMANAGER_NAME + ".ThreadIdentity", resourceManager.GetString("RES_EXCEPTIONMANAGEMENT_INFOACCESS_EXCEPTION"));
				}
				
				try
				{
					additionalInfo.Add(EXCEPTIONMANAGER_NAME + ".WindowsIdentity", WindowsIdentity.GetCurrent().Name);
				}
				catch(SecurityException)
				{
					additionalInfo.Add(EXCEPTIONMANAGER_NAME + ".WindowsIdentity", resourceManager.GetString("RES_EXCEPTIONMANAGEMENT_PERMISSION_DENIED"));
				}
				catch
				{
					additionalInfo.Add(EXCEPTIONMANAGER_NAME + ".WindowsIdentity", resourceManager.GetString("RES_EXCEPTIONMANAGEMENT_INFOACCESS_EXCEPTION"));
				}
											
				#endregion

				#region Publish the exception based on Configuration Settings
				// Check for any settings in config file.
				
				if (ConfigurationSettings.GetConfig(EXCEPTIONMANAGEMENT_CONFIG_SECTION) == null)
				{
					// Publish the exception and additional information to the default publisher if no settings are present.
					PublishToDefaultPublisher(exception, additionalInfo);
				}
				else
				{
					// Get settings from config file
					ExceptionManagementSettings config = (ExceptionManagementSettings)ConfigurationSettings.GetConfig(EXCEPTIONMANAGEMENT_CONFIG_SECTION);

					// If the mode is not equal to "off" call the Publishers, otherwise do nothing.
					if (config.Mode == ExceptionManagementMode.On)
					{
						// If no publishers are specified, use the default publisher.
						if (config.Publishers == null || config.Publishers.Count == 0)
						{
							// Publish the exception and additional information to the default publisher if no mode is specified.
							PublishToDefaultPublisher(exception, additionalInfo);
						}
						else
						{
							#region Iterate through the publishers
							// Loop through the publisher components specified in the config file.
							foreach(PublisherSettings Publisher in config.Publishers)
							{
								// Call the Publisher component specified in the config file.
								try
								{
									// Verify the publishers mode is not set to "OFF".
									// This publisher will be called even if the mode is not specified.  
									// The mode must explicitly be set to OFF to not be called.
									if (Publisher.Mode == PublisherMode.On)
									{
										if (exception == null || !Publisher.IsExceptionFiltered(exception.GetType()))
										{
											// Publish the exception and any additional information
											PublishToCustomPublisher(exception, additionalInfo, Publisher);
										}
									} 
								} 
								// Catches any failure to call a custom publisher.
								catch(Exception e)
								{
									// Publish the exception thrown when trying to call the custom publisher.
									PublishInternalException(e,null);

									// Publish the original exception and additional information to the default publisher.
									PublishToDefaultPublisher(exception, additionalInfo);

								} // End Catch block.

							} // End foreach loop through publishers.
#endregion
						} // End else statement when custom publishers are in the config settings.

					} // End else statement where config settings are not set to "OFF"

				} // End else statement when config settings are provided.
				#endregion
			}
			catch(Exception e)
			{
				// Publish the exception thrown within the ExceptionManager.
				PublishInternalException(e,null);

				// Publish the original exception and additional information to the default publisher.
				PublishToDefaultPublisher(exception, additionalInfo);
			}
		} // End Publish(Exception exception, NameValueCollection AdditionalInfo)

		/// <summary>
		/// Private static helper method to publish the exception information to a custom publisher.
		/// </summary>
		/// <param name="exception">The exception object whose information should be published.</param>
		/// <param name="additionalInfo">A collection of additional data that should be published along with the exception information.</param>
		/// <param name="publisher">The PublisherSettings that contains the values of the publishers configuration.</param>
		private static void PublishToCustomPublisher(Exception exception, NameValueCollection additionalInfo, PublisherSettings publisher)
		{
			try
			{
				// Check if the exception format is "xml".
				if (publisher.ExceptionFormat == PublisherFormat.Xml)
				{
					// If it is load the IExceptionXmlPublisher interface on the custom publisher.
 
					// Instantiate the class
					IExceptionXmlPublisher XMLPublisher = (IExceptionXmlPublisher)Activate(publisher.AssemblyName, publisher.TypeName);

					// Publish the exception and any additional information
					XMLPublisher.Publish(SerializeToXml(exception, additionalInfo),publisher.OtherAttributes);
				}
				// Otherwise load the IExceptionPublisher interface on the custom publisher.
				else
				{
					// Instantiate the class
					IExceptionPublisher Publisher = (IExceptionPublisher)Activate(publisher.AssemblyName, publisher.TypeName);

					// Publish the exception and any additional information
					Publisher.Publish(exception, additionalInfo, publisher.OtherAttributes);
				}
			}
			catch(Exception e)
			{
				CustomPublisherException publisherException = new CustomPublisherException(resourceManager.GetString("RES_CUSTOM_PUBLISHER_FAILURE_MESSAGE"), publisher.AssemblyName, publisher.TypeName, publisher.ExceptionFormat, e);
                publisherException.AdditionalInformation.Add(publisher.OtherAttributes);

				throw(publisherException);
			}
		}

		/// <summary>
		/// Private static helper method to publish the exception information to the default publisher.
		/// </summary>
		/// <param name="exception">The exception object whose information should be published.</param>
		/// <param name="additionalInfo">A collection of additional data that should be published along with the exception information.</param>
		private static void PublishToDefaultPublisher(Exception exception, NameValueCollection additionalInfo)
		{
			// Get the Default Publisher
			DefaultPublisher Publisher = new DefaultPublisher();
				
			// Publish the exception and any additional information
			Publisher.Publish(exception, additionalInfo, null);
		}

		/// <summary>
		/// Private static helper method to publish the exception information to the default publisher.
		/// </summary>
		/// <param name="exception">The exception object whose information should be published.</param>
		/// <param name="additionalInfo">A collection of additional data that should be published along with the exception information.</param>
		internal static void PublishInternalException(Exception exception, NameValueCollection additionalInfo)
		{
			// Get the Default Publisher
			DefaultPublisher Publisher = new DefaultPublisher("Application", resourceManager.GetString("RES_EXCEPTIONMANAGER_INTERNAL_EXCEPTIONS"));
				
			// Publish the exception and any additional information
			Publisher.Publish(exception, additionalInfo, null);
		}

		/// <summary>
		/// Private helper function to assist in run-time activations. Returns
		/// an object from the specified assembly and type.
		/// </summary>
		/// <param name="assembly">Name of the assembly file (w/out extension)</param>
		/// <param name="typeName">Name of the type to create</param>
		/// <returns>Instance of the type specified in the input parameters.</returns>
		private static object Activate(string assembly, string typeName)
		{
			return AppDomain.CurrentDomain.CreateInstanceAndUnwrap(assembly, typeName);
		}

		/// <summary>
		/// Public static helper method to serialize the exception information into XML.
		/// </summary>
		/// <param name="exception">The exception object whose information should be published.</param>
		/// <param name="additionalInfo">A collection of additional data that should be published along with the exception information.</param>
		public static XmlDocument SerializeToXml(Exception exception, NameValueCollection additionalInfo)
		{
			try
			{
				// Variables representing the XmlElement names.
				
				string ROOT = resourceManager.GetString("RES_XML_ROOT");
				string ADDITIONAL_INFORMATION = resourceManager.GetString("RES_XML_ADDITIONAL_INFORMATION");
				string EXCEPTION = resourceManager.GetString("RES_XML_EXCEPTION");
				string STACK_TRACE = resourceManager.GetString("RES_XML_STACK_TRACE");
								
				// Create a new XmlDocument.
				XmlDocument xmlDoc = new XmlDocument();

				// Create the root node.
				XmlElement root = xmlDoc.CreateElement(ROOT);
				xmlDoc.AppendChild(root);

				// Variables to hold values while looping through the exception chain.
				XmlElement element;
				XmlElement exceptionAddInfoElement;
				XmlElement stackTraceElement;
				XmlText stackTraceText;
				XmlAttribute attribute;

			#region Add values from AdditionalInfo to the XML Doc
				// Check if the collection has values.
				if (additionalInfo != null && additionalInfo.Count > 0)
				{
					// Create the element for the collection.
					element = xmlDoc.CreateElement(ADDITIONAL_INFORMATION);
				
					// Loop through the collection and add the values as attributes on the element.
					foreach (string i in additionalInfo)
					{
						attribute = xmlDoc.CreateAttribute(i.Replace(" ", "_"));
						attribute.Value = additionalInfo.Get(i);
						element.Attributes.Append(attribute);
					}

					// Add the element to the root.
					root.AppendChild(element);
				}
			#endregion

				if (exception == null)
				{
					// Create an empty exception element.
					element = xmlDoc.CreateElement(EXCEPTION);

					// Append to the root node.
					root.AppendChild(element);
				}
				else
				{
			#region Loop through each exception class in the chain of exception objects and record its information
					// Loop through each exception class in the chain of exception objects.
					Exception currentException = exception;	// Temp variable to hold InnerException object during the loop.
					XmlElement parentElement = null;	// Temp variable to hold the parent exception node during the loop.
					do
					{
						// Create the exception element.
						element = xmlDoc.CreateElement(EXCEPTION);

						// Add the exceptionType as an attribute.
						attribute = xmlDoc.CreateAttribute("ExceptionType");
						attribute.Value = currentException.GetType().FullName;
						element.Attributes.Append(attribute);
				
				#region Loop through the public properties of the exception object and record their value
						// Loop through the public properties of the exception object and record their value.
						PropertyInfo[] aryPublicProperties = currentException.GetType().GetProperties();
						NameValueCollection currentAdditionalInfo;
						foreach (PropertyInfo p in aryPublicProperties)
						{
							// Do not log information for the InnerException or StackTrace. This information is 
							// captured later in the process.
							if (p.Name != "InnerException" && p.Name != "StackTrace")
							{
								// Only record properties whose value is not null.
								if (p.GetValue(currentException,null) != null)
								{
									// Check if the property is AdditionalInformation and the exception type is a BaseApplicationException.
									if (p.Name == "AdditionalInformation" && currentException is BaseApplicationException)
									{
										// Verify the collection is not null.
										if (p.GetValue(currentException,null) != null)
										{
											// Cast the collection into a local variable.
											currentAdditionalInfo = (NameValueCollection)p.GetValue(currentException,null);

											// Verify the collection has values.
											if (currentAdditionalInfo.Count > 0)
											{
												// Create element.
												exceptionAddInfoElement = xmlDoc.CreateElement(ADDITIONAL_INFORMATION);

												// Loop through the collection and add values as attributes.
												foreach (string i in currentAdditionalInfo)
												{
													attribute = xmlDoc.CreateAttribute(i.Replace(" ", "_"));
													attribute.Value = currentAdditionalInfo.Get(i);
													exceptionAddInfoElement.Attributes.Append(attribute);
												}

												element.AppendChild(exceptionAddInfoElement);
											}
										}
									}
										// Otherwise just add the ToString() value of the property as an attribute.
									else
									{
										attribute = xmlDoc.CreateAttribute(p.Name);
										attribute.Value = p.GetValue(currentException,null).ToString();
										element.Attributes.Append(attribute);
									}
								}
							}
						}
				#endregion

				#region Record the exception StackTrace
						// Record the StackTrace within a separate element.
						if (currentException.StackTrace != null)
						{
							// Create Stack Trace Element.
							stackTraceElement = xmlDoc.CreateElement(STACK_TRACE);

							stackTraceText = xmlDoc.CreateTextNode(currentException.StackTrace.ToString());

							stackTraceElement.AppendChild(stackTraceText);

							element.AppendChild(stackTraceElement);
						}
				#endregion

						// Check if this is the first exception in the chain.
						if (parentElement == null)
						{
							// Append to the root node.
							root.AppendChild(element);
						}
						else
						{
							// Append to the parent exception object in the exception chain.
							parentElement.AppendChild(element);
						}
				
						// Reset the temp variables.
						parentElement = element;
						currentException = currentException.InnerException;

						// Continue looping until we reach the end of the exception chain.
					} while (currentException != null);
				#endregion
				}
				// Return the XmlDocument.
				return xmlDoc;
			}
			catch(Exception e)
			{
				throw new SerializationException(resourceManager.GetString("RES_EXCEPTIONMANAGEMENT_XMLSERIALIZATION_EXCEPTION"),e);
			}
		}
	}
	#endregion
    
	#region DefaultPublisher Class
	/// <summary>
	/// Component used as the default publishing component if one is not specified in the config file.
	/// </summary>
	public sealed class DefaultPublisher : IExceptionPublisher
	{
		/// <summary>
		/// Default Constructor.
		/// </summary>
		public DefaultPublisher()
		{
		}

		/// <summary>
		/// Constructor allowing the log name and application names to be set.
		/// </summary>
		/// <param name="logName">The name of the log for the DefaultPublisher to use.</param>
		/// <param name="applicationName">The name of the application.  This is used as the Source name in the event log.</param>
		public DefaultPublisher(string logName, string applicationName)
		{
			this.logName = logName;
			this.applicationName = applicationName;
		}
		
		private static ResourceManager resourceManager = new ResourceManager(typeof(ExceptionManager).Namespace + ".ExceptionManagerText",Assembly.GetAssembly(typeof(ExceptionManager)));
		
		// Member variable declarations
		private string logName = "Application";
		private string applicationName = resourceManager.GetString("RES_EXCEPTIONMANAGER_PUBLISHED_EXCEPTIONS");
		private const string TEXT_SEPARATOR = "*********************************************";
		
		/// <summary>
		/// Method used to publish exception information and additional information.
		/// </summary>
		/// <param name="exception">The exception object whose information should be published.</param>
		/// <param name="additionalInfo">A collection of additional data that should be published along with the exception information.</param>
		/// <param name="configSettings">A collection of any additional attributes provided in the config settings for the custom publisher.</param>
		public void Publish(Exception exception, NameValueCollection additionalInfo, NameValueCollection configSettings)
		{
			// Load Config values if they are provided.
			if (configSettings != null)
			{
				if (configSettings["applicationName"] != null && configSettings["applicationName"].Length > 0) applicationName = configSettings["applicationName"];
				if (configSettings["logName"] != null && configSettings["logName"].Length > 0)  logName = configSettings["logName"];
			}

			// Verify that the Source exists before gathering exception information.
			VerifyValidSource();

			// Create StringBuilder to maintain publishing information.
			StringBuilder strInfo = new StringBuilder();

			#region Record the contents of the AdditionalInfo collection
			// Record the contents of the AdditionalInfo collection.
			if (additionalInfo != null)
			{
				// Record General information.
				strInfo.AppendFormat("{0}General Information {0}{1}{0}Additional Info:", Environment.NewLine, TEXT_SEPARATOR);

				foreach (string i in additionalInfo)
				{
					strInfo.AppendFormat("{0}{1}: {2}", Environment.NewLine, i, additionalInfo.Get(i));
				}
			}
			#endregion

			if (exception == null)
			{
				strInfo.AppendFormat("{0}{0}No Exception object has been provided.{0}", Environment.NewLine);
			}
			else
			{
			#region Loop through each exception class in the chain of exception objects
				// Loop through each exception class in the chain of exception objects.
				Exception currentException = exception;	// Temp variable to hold InnerException object during the loop.
				int intExceptionCount = 1;				// Count variable to track the number of exceptions in the chain.
				do
				{
					// Write title information for the exception object.
					strInfo.AppendFormat("{0}{0}{1}) Exception Information{0}{2}", Environment.NewLine, intExceptionCount.ToString(), TEXT_SEPARATOR);
					strInfo.AppendFormat("{0}Exception Type: {1}", Environment.NewLine, currentException.GetType().FullName);
				
				#region Loop through the public properties of the exception object and record their value
					// Loop through the public properties of the exception object and record their value.
					PropertyInfo[] aryPublicProperties = currentException.GetType().GetProperties();
					NameValueCollection currentAdditionalInfo;
					foreach (PropertyInfo p in aryPublicProperties)
					{
						// Do not log information for the InnerException or StackTrace. This information is 
						// captured later in the process.
						if (p.Name != "InnerException" && p.Name != "StackTrace")
						{
							if (p.GetValue(currentException,null) == null)
							{
								strInfo.AppendFormat("{0}{1}: NULL", Environment.NewLine, p.Name);
							}
							else
							{
								// Loop through the collection of AdditionalInformation if the exception type is a BaseApplicationException.
								if (p.Name == "AdditionalInformation" && currentException is BaseApplicationException)
								{
									// Verify the collection is not null.
									if (p.GetValue(currentException,null) != null)
									{
										// Cast the collection into a local variable.
										currentAdditionalInfo = (NameValueCollection)p.GetValue(currentException,null);

										// Check if the collection contains values.
										if (currentAdditionalInfo.Count > 0)
										{
											strInfo.AppendFormat("{0}AdditionalInformation:", Environment.NewLine);

											// Loop through the collection adding the information to the string builder.
											for (int i = 0; i < currentAdditionalInfo.Count; i++)
											{
												strInfo.AppendFormat("{0}{1}: {2}", Environment.NewLine, currentAdditionalInfo.GetKey(i), currentAdditionalInfo[i]);
											}
										}
									}
								}
									// Otherwise just write the ToString() value of the property.
								else
								{
									strInfo.AppendFormat("{0}{1}: {2}", Environment.NewLine, p.Name, p.GetValue(currentException,null));
								}
							}
						}
					}
					#endregion
				#region Record the Exception StackTrace
					// Record the StackTrace with separate label.
					if (currentException.StackTrace != null)
					{
						strInfo.AppendFormat("{0}{0}StackTrace Information{0}{1}", Environment.NewLine, TEXT_SEPARATOR);
						strInfo.AppendFormat("{0}{1}", Environment.NewLine, currentException.StackTrace);
					}
				#endregion

					// Reset the temp exception object and iterate the counter.
					currentException = currentException.InnerException;
					intExceptionCount++;
				} while (currentException != null);
			#endregion
			}

			// Write the entry to the event log.   
			WriteToLog(strInfo.ToString(), EventLogEntryType.Error);
		}

		/// <summary>
		/// Helper function to write an entry to the Event Log.
		/// </summary>
		/// <param name="entry">The entry to enter into the Event Log.</param>
		/// <param name="type">The EventLogEntryType to be used when the entry is logged to the Event Log.</param>
		private void WriteToLog(string entry, EventLogEntryType type)
		{
			try
			{
				// Write the entry to the Event Log.
				EventLog.WriteEntry(applicationName,entry,type);
			}
			catch(SecurityException e)
			{				
				throw new SecurityException(String.Format(resourceManager.GetString("RES_DEFAULTPUBLISHER_EVENTLOG_DENIED"), applicationName),e);
			}
		}

		private void VerifyValidSource()
		{
			try
			{
				if (!EventLog.SourceExists(applicationName))
				{
					EventLog.CreateEventSource(applicationName, logName);
				}
			}
			catch(SecurityException e)
			{
				throw new SecurityException(String.Format(resourceManager.GetString("RES_DEFAULTPUBLISHER_EVENTLOG_DENIED"), applicationName),e);
			}
		}
	}
	#endregion
}
