using System; using System.Linq; using System.Runtime.Remoting.Contexts; using DelftTools.Utils.Editing; using PostSharp.Aspects; namespace DelftTools.Utils.Aop { /// /// Used to mark methods which can modify object state. Changes may include multiple property and/or collection changes. /// This attribute allows to skips these methods in some performance and consistency critical situations (e.g. save, load, undo/redo). /// [Serializable] [Synchronization] public class EditActionAttribute : MethodInterceptionAspect { public static event Action BeforeEdit; public static event Action AfterEdit; /// /// Fired before any CollectionChanging or PropertyChanging events occur. /// public static Action BeforeEventCall; /// /// Fired after any CollectionChanged or PropertyChanged events occur. When collection Changing is cancelled - this event is still fired. /// public static Action AfterEventCall; [ThreadStatic] private static int editActionsInProgress; private readonly Type editActionType; public EditActionAttribute() {} public EditActionAttribute(Type editActionType) { if (!(typeof(IEditAction).IsAssignableFrom(editActionType))) { throw new ArgumentException(string.Format("Type {0} is not of type IEditAction", editActionType)); } this.editActionType = editActionType; } public static void FireBeforeEventCall(object sender, bool isPropertyChange) { if (BeforeEventCall != null) { BeforeEventCall(sender, isPropertyChange); } } /// /// /// /// /// /// For example, when exception occurs during set/add/... public static void FireAfterEventCall(object sender, bool isPropertyChange, bool isCancelled) { if (AfterEventCall != null) { AfterEventCall(sender, isPropertyChange, isCancelled); } } public override sealed void OnInvoke(MethodInterceptionArgs eventArgs) { if (EditActionSettings.Disabled && !(EditActionSettings.AllowRestoreActions && editActionType != null)) { return; } IEditAction editAction = null; try { editActionsInProgress++; if (EventSettings.EnableLogging) { //log.DebugFormat(Indent + ">> Entering edit action {0} (enabled:{1}) {2}", // (eventArgs.Instance != null ? eventArgs.Instance.GetType().Name : "static") + "." + eventArgs.Method.Name, !Disabled, editActionsInProgress); } if (BeforeEdit != null) { BeforeEdit(eventArgs); } var editableObject = eventArgs.Instance as IEditableObject; if (EditActionSettings.SupportEditableObject && editActionType != null) { if (editableObject == null) { throw new InvalidOperationException("Cannot apply EditAction attribute with EditActionType if target is not IEditableObject"); } editAction = EditActionBase.Create(editActionType); if (editAction.HandlesRestore) { editAction.Instance = eventArgs.Instance; editAction.Arguments = eventArgs.Arguments.ToArray(); editAction.BeforeChanges(); } editableObject.BeginEdit(editAction); } var exception = false; try { eventArgs.Proceed(); } catch (Exception) { exception = true; throw; } finally { if (EditActionSettings.SupportEditableObject && editAction != null) { if (exception) { editableObject.CancelEdit(); } else { if (editAction.HandlesRestore) { editAction.ReturnValue = eventArgs.ReturnValue; } editableObject.EndEdit(); } } if (AfterEdit != null) { AfterEdit(eventArgs); } if (EventSettings.EnableLogging) { //log.DebugFormat(Indent + "<< Exiting edit action {0} {1}", // (eventArgs.Instance != null ? eventArgs.Instance.GetType().Name : "static") + "." + eventArgs.Method.Name, editActionsInProgress); } } } finally { editActionsInProgress--; } } internal static string Indent { get { return GetIndent(editActionsInProgress); } } private static string GetIndent(int num) { return Enumerable.Repeat(" ", Math.Max(0, num)).Aggregate("", (s1, s2) => s1 + s2); } } /// /// These settings are in a seperate class so that we don't need a reference to PostSharp at callers /// public static class EditActionSettings { public static bool Disabled; public static bool AllowRestoreActions; public static bool SupportEditableObject; } }