Skip to content

Instantly share code, notes, and snippets.

@hugoware
Created April 20, 2010 03:29
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save hugoware/371990 to your computer and use it in GitHub Desktop.
Save hugoware/371990 to your computer and use it in GitHub Desktop.
Use lambdas to set a series of methods to perform and then rollback if an exception takes place
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
//Requires ImpersonationContext that can be found
//at http://gist.github.com/357819
using Hugoware.Security;
namespace Hugoware {
/// <summary>
/// Creates a
/// </summary>
public class WorkSequence {
#region Constructors
/// <summary>
/// Creates a new WorkSequence
/// </summary>
public WorkSequence()
: this(null) {
}
/// <summary>
/// Creates a new WorkSequence with a default ImpersonationContext to use
/// </summary>
public WorkSequence(ImpersonationContext defaultContext) {
this._Work = new List<WorkItem>();
this.DefaultContext = defaultContext;
this.IgnoreRollbackErrors = true;
}
#endregion
#region Properties
/// <summary>
/// The context to default when no context is assigned
/// </summary>
public ImpersonationContext DefaultContext { get; set; }
/// <summary>
/// Gets or sets if errors when rolling back should be ignored
/// </summary>
public bool IgnoreRollbackErrors { get; set; }
//holds a list of work items
public IEnumerable<WorkItem> Items {
get { return this._Work.AsEnumerable(); }
}
private List<WorkItem> _Work;
#endregion
#region Events
/// <summary>
/// Raised when a work item cannot complete successfully
/// </summary>
public event Action<WorkSequence, WorkItem, int> Error;
/// <summary>
/// Raised when the work has finished
/// </summary>
public event Action<WorkSequence> Complete;
#endregion
#region Public Methods
/// <summary>
/// Adds a new work item to the sequence
/// </summary>
public void Add(Action perform) {
this.Add(perform, null, null);
}
/// <summary>
/// Adds a new work item to the sequence
/// </summary>
public void Add(Action perform, Action rollback) {
this.Add(perform, rollback, null);
}
/// <summary>
/// Adds a new work item to the sequence
/// </summary>
public void Add(Action perform, ImpersonationContext context) {
this.Add(perform, null, context);
}
/// <summary>
/// Adds a new work item to the sequence
/// </summary>
public void Add(Action perform, Action rollback, ImpersonationContext context) {
WorkItem item = new WorkItem(perform, rollback, context ?? this.DefaultContext);
this._Work.Add(item);
}
/// <summary>
/// Performs the work and waits for the items to complete
/// </summary>
public void Perform() {
WorkItem[] items = this._Work.ToArray();
//start trying to perform the work first
int index;
for (index = 0; index < items.Length; index++) {
//try and perform the work for this item
try {
items[index].Perform();
}
//any exceptions should fail
catch {
break;
}
}
//if this failed to complete all the work, rollback
if (index < items.Length) {
if (this.Error is Action<WorkSequence, WorkItem, int>) {
this.Error(this, items[index], index);
}
//start rolling back and performing undo work in reverse
for (; index-- > 0; ) {
try {
items[index].Rollback();
}
//check if we are ignoring errors
catch {
if (!this.IgnoreRollbackErrors) {
throw;
}
}
}
}
//finally, let everyone know this has finished
if(this.Complete is Action<WorkSequence>) {
this.Complete(this);
}
}
/// <summary>
/// Performs the work in the sequence
/// </summary>
public void PerformAsync(AsyncCallback callback) {
Action perform = this.Perform;
perform.BeginInvoke(callback, this);
}
#endregion
#region Helper Classes
//holds individual work items for a queue
public class WorkItem {
#region Constructors
//creates a work item that relies on an
//exception to know when it has failed
public WorkItem(Action perform, Action rollback, ImpersonationContext context) {
this._Context = context;
this._Perform = perform;
this._Rollback = rollback;
}
#endregion
#region Members
//contains the work to perform
Action _Perform;
Action _Rollback;
//contains an optional context to use
ImpersonationContext _Context;
#endregion
#region Public Methods
/// <summary>
/// Returns information about the work to perform
/// </summary>
public MethodInfo GetPerformMethod() {
if (this._Perform == null) { return null; }
return this._Perform.Method;
}
/// <summary>
/// Returns information about the rollback method to use
/// </summary>
public MethodInfo GetRollbackMethod() {
if (this._Rollback == null) { return null; }
return this._Rollback.Method;
}
//attempts to call the Perform method
public void Perform() {
this._Work(this._Perform);
}
//
public void Rollback() {
this._Work(this._Rollback);
}
#endregion
#region Private Methods
//performs the correct function
private void _Work(Action work) {
//make sure there was work to begin with
if (work == null) { return; }
//check for any credentials to impersonate
if (this._Context is ImpersonationContext) {
this._Context.Execute(work);
}
//just perform the work normally
else {
work();
}
}
#endregion
}
#endregion
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment