Hugoware

The product of a web developer with a little too much caffeine

Posts Tagged ‘Anonymous Methods

Work Sequences Using Lambdas

with 2 comments

Occasionally I’ll run into some code that I want to “undo” in case the entire thing doesn’t go as planned. Normally placing this code into a separate class and then performing each work item one at a time works pretty well. You have to keep track of your progress so you know where to rollback from but overall it isn’t a very complicated process.

I had pretty much the same scenario the other day, but this time I thought I’d approach it a little differently. Instead of writing the sequence management into the class I was using I decided to see if I could come up with the same concept using lambdas instead.

[Download Source Code: WorkSequence.cs] : Hosted on gist.github.

The idea is to make some reusable code that can be used within a class to handle sequences of code. Let’s look at a sample of how you might use this code – We’re going to pretend this code is part of a class responsible for moving some data around. If one transfer fails then all of the work should be reverted.

string path = Path.Combine(@"\\server\share\", this.FileName);

//creating a default sequence
WorkSequence work = new WorkSequence();

//when a rollback takes place
work.Error += (sequence, item, index) => {
    string name = item.GetPerformMethod().Name;
    Console.WriteLine("Failed item {0}", name);
};

//when the work finished
work.Complete += (sequence) => {
    Console.WriteLine("Finished work!");
};

//1. calling a method without a rollback step
work.Add(this.GenerateLibrary);

//2. calling a method that has an undo step
work.Add(this.CreatePackage, this.RemoveTempCacheFile);

//3. calling methods that have arguments
work.Add(() => this.Transfer(path), () => File.Delete(path));

//4. calling a method using different credentials
work.Add(this.Archive, new ImpersonationContext("domain\\name", "passw0rd"));

//5. starts executing the work
work.Perform();

//6. or an async call
work.PerformAsync((sequence) => {
    Console.WriteLine("Finished in sequence");
});

The general idea for this code is to set the work that you need to do and then an optional ‘rollback’ option in case there is a problem. You’ll notice that none of the methods accept arguments. Instead, you can provide an action that invokes the action and then provides the arguments (in #3).

As each item is fired an index is used to keep track of the position in the sequence. That way, if there is an error, only the work that has actually executed will have the rollback method fired.

You’ll also notice that I’m using the code I recently published so you can provide credentials to use for impersonation. This can be per method or applied as a ‘default’ for the entire sequence.

Finally, you can either invoke the process and wait for all of the work to complete or fire it up in an asynchronously. You can provide a callback method or assign an event to fire off once the work has completed.

Not sure if there is a built in class to handle this kind of work but I’ve found it useful so far – Enjoy!

Advertisements

Written by hugoware

April 19, 2010 at 11:29 pm

A Personable Form Of Impersonation

leave a comment »

Have you tried impersonating a different user when executing code in your ASP.NET sites? Yeah, you can use a bunch of options in the web and machine config files but those might not give you enough control. Whats more you still have to contend with the way IIS handles credentials with the worker process then connecting user… er… or was that connecting user then… ah… never mind…

Executing a block of code with a specific set of credentials is a very handy way to have precise control over your application. Recently, I was working on copying files from an ASP.NET website to a remote UNC path. I needed to perform the transfer using different credentials for different servers… yeah, it was ugly but required…

Below is the code I finally ended up with…

using System;
using System.Runtime.InteropServices;
using System.Security.Principal;
using System.Text.RegularExpressions;

namespace Samples {

    /// <summary>
    /// Allows you to execute code with an alternate set of credentials
    /// </summary>
    public class ImpersonationContext : IDisposable {

        #region Imported Methods

        [DllImport("kernel32.dll", CharSet = CharSet.Auto)]
        private static extern bool CloseHandle(IntPtr handle);

        [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        private static extern bool RevertToSelf();

        [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        private static extern int DuplicateToken(
            IntPtr token, 
            int impersonationLevel, 
            ref IntPtr newToken
            );

        [DllImport("advapi32.dll")]
        private static extern int LogonUserA(
            string username,
            string domain,
            string password,
            int logonType,
            int logonProvider,
            ref IntPtr token
            );

        #endregion

        #region Constants

        private const int INTERACTIVE_LOGON = 2;
        private const int DEFAULT_PROVIDER = 0;

        private const string REGEX_GROUP_USERNAME = "username";
        private const string REGEX_GROUP_DOMAIN = "domain";
        private const string REGEX_EXTRACT_USER_INFO =
            @"^(?<domain>[^\\]+)\\(?<username>.*)$|^(?<username>[^@]+)@(?<domain>.*)$";

        private const string EXCEPTION_COULD_NOT_IMPERSONATE = 
            "Could not impersonate user '{0}'.";
        private const string EXCEPTION_COULD_NOT_PARSE_USERNAME = 
            "Cannot determine username and domain from '{0}'";

        #endregion

        #region Constructors

        /// <summary>
        /// Creates a new Impersonation context
        /// </summary>
        public ImpersonationContext(string fullUsername, string password) {
            this.SetCredentials(fullUsername, password);
        }

        /// <summary>
        /// Creates a new Impersonation context
        /// </summary>
        public ImpersonationContext(string username, string domain, string password) {
            this.SetCredentials(username, domain, password);
        }

        #endregion

        #region Static Creation

        /// <summary>
        /// Executes a set of code using the credentials provided
        /// </summary>
        public static void Execute(string fullUsername, string password, Action action) {
            using (ImpersonationContext context = new ImpersonationContext(fullUsername, password)) {
                context.Execute(action);
            }
        }

        /// <summary>
        /// Executes a set of code using the credentials provided
        /// </summary>
        public static void Execute(string username, string domain, string password, Action action) {
            using (ImpersonationContext context = new ImpersonationContext(username, domain, password)) {
                context.Execute(action);
            }
        }

        #endregion

        #region Properties

        /// <summary>
        /// The username for this connection
        /// </summary>
        public string Username { get; private set; }

        /// <summary>
        /// The domain name for this user
        /// </summary>
        public string Domain { get; private set; }

        /// <summary>
        /// The identity of the executing account
        /// </summary>
        public WindowsIdentity Identity { get; private set; }

        //connection details
        private string _Password;
        private WindowsImpersonationContext _Context;

        #endregion

        #region Private Methods

        /// <summary>
        /// Begins to impersonate the provided credentials
        /// </summary>
        public bool BeginImpersonation() {

            //create the token containers
            IntPtr token = IntPtr.Zero;
            IntPtr tokenDuplicate = IntPtr.Zero;

            if (ImpersonationContext.RevertToSelf()) {

                //attempt the login
                int success = ImpersonationContext.LogonUserA(
                    this.Username,
                    this.Domain,
                    this._Password,
                    INTERACTIVE_LOGON,
                    DEFAULT_PROVIDER,
                    ref token
                    );

                //if this worked, perform the impersonation
                if (success != 0) {

                    int duplicate = ImpersonationContext.DuplicateToken(token, 2, ref tokenDuplicate);
                    if (duplicate != 0) {

                        //assign the identity to use
                        //this.Identity = new WindowsIdentity(tokenDuplicate);
                        this._Context = WindowsIdentity.Impersonate(tokenDuplicate);
                        if (this._Context != null) {
                            ImpersonationContext.CloseHandle(token);
                            ImpersonationContext.CloseHandle(tokenDuplicate);
                            return true;
                        }

                    }
                }
            }

            //close the tokens if required
            if (token != IntPtr.Zero) { ImpersonationContext.CloseHandle(token); }
            if (tokenDuplicate != IntPtr.Zero) { ImpersonationContext.CloseHandle(tokenDuplicate); }

            //return this failed
            return false;
        }

        /// <summary>
        /// Ends impersonating the current request
        /// </summary>
        public void EndImpersonation() {
            if (this._Context is WindowsImpersonationContext) { this._Context.Undo(); }
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// Accepts a full domain and assigns it for this connection
        /// </summary>
        public void SetCredentials(string fullUsername, string password) {

            //extract the user information
            Match user = Regex.Match(fullUsername, REGEX_EXTRACT_USER_INFO);
            if (!user.Success) {
                string message = string.Format(EXCEPTION_COULD_NOT_PARSE_USERNAME, fullUsername);
                throw new ArgumentException(message);
            }

            //extract the values
            string username = user.Groups[REGEX_GROUP_USERNAME].Value;
            string domain = user.Groups[REGEX_GROUP_DOMAIN].Value;

            //update the credentials
            this.SetCredentials(username, domain, password);

        }

        /// <summary>
        /// Changes the credentials for this connection to use
        /// </summary>
        public void SetCredentials(string username, string domain, string password) {
            this.Username = username;
            this.Domain = domain;
            this._Password = password;
        }

        /// <summary>
        /// Executes the action using the credentials provided
        /// </summary>
        public void Execute(Action action) {

            //perform the requested action
            if (this.BeginImpersonation()) {
                try {
                    action();
                }
                finally {
                    this.EndImpersonation();
                }
            }
            //since this couldn't login, give up
            else {
                string message = string.Format(EXCEPTION_COULD_NOT_IMPERSONATE, this.Username);
                throw new OperationCanceledException(message);
            }
        }

        #endregion

        #region IDisposable Members

        /// <summary>
        /// Performs any cleanup work
        /// </summary>
        public void Dispose() {
            this.EndImpersonation();
            if (this._Context is WindowsImpersonationContext) { this._Context.Dispose(); }
        }

        #endregion

    }

}

Nothing like a lot of code to help fill in the blanks of a blog post, huh?

After reading a dozen blog post, tutorials and wikis, this is what I finally came up with – a nice simple way to execute a block of code without needing to worry about a lot of setup options. How about a few examples?

//who is running the code now
string who = WindowsIdentity.GetCurrent().Name;

//the credentials to use 
string username = "domain\\hugoware";
string password = "passw0rd";

//create a context inside of a using statement
//or simply create it and dispose it yourself
using(ImpersonationContext context = new ImpersonationContext(username, password)) {
    
    //execute using a anonymous method
    context.Execute(() => {
        who = WindowsIdentity.GetCurrent().Name;
    });

    //or manage it yourself
    context.BeginImpersonation();
    who = WindowsIdentity.GetCurrent().Name;
    context.EndImpersonation(); //optional if inside of a 'using'

}

//or use static methods
ImpersonationContext.Execute(username, password, () => {
    who = WindowsIdentity.GetCurrent().Name;
});

At this point I’m not exactly sure what this means for code that isn’t exactly straight forward, for example Threading or executing code when the credentials don’t have any access to the machine. I’ll follow up later as I find out more.

Written by hugoware

March 25, 2010 at 9:38 pm

Code Performance Measuring With Enclosures

with 2 comments

I’m sure that there are many libraries available to help measure the execution of time inside of an application but I spent some time the other day writing a quick class to help me time segments of code.

The idea was to use an Anonymous Method to wrap a section of code with a Stopwatch to check the total time of execution. Additionally, since methods could be Anonymous, you could nest the measurements to see how long specific sections would take.

Anyways, here is some source code to play around with.

/// <summary>
/// Simple measuring of sections of code
/// </summary>
public class CodeBlockTimer {

    #region Private Fields

    //holds the current time for this measurement
    private Stopwatch _OverallTime;

    //container for the measured code sections
    private List<object> _Blocks = new List<object>();

    //the nested level for the code block
    private int _Level = 0;

    #endregion

    #region Methods

    /// <summary>
    /// Stops this code block timer
    /// </summary>
    public void Stop() {
        this._OverallTime.Stop();
    }

    /// <summary>
    /// Adds a block of code to measure
    /// </summary>
    public void Measure(Action block) {
        this.Measure(block.Method.Name, block);
    }

    /// <summary>
    /// Adds a block of code to measure with a defined name
    /// </summary>
    public void Measure(string identity, Action block) {

        //start the overall timer if needed
        if (this._OverallTime == null) {
            this._OverallTime = Stopwatch.StartNew();
        }
        //check and make sure this timer wasn't already stopped
        else if (!this._OverallTime.IsRunning) {
            throw new InvalidOperationException(
                "This CodeBlockTimer has already been stopped and cannot measure additional code blocks."
                );
        }

        //create the container
        _StartBlock start = new _StartBlock(identity, this._Level++);
        _EndBlock end = new _EndBlock(start);

        //run this section of code
        this._Blocks.Add(start);
        start.Timer = Stopwatch.StartNew();
        block();
        start.Timer.Stop();
        this._Blocks.Add(end);

        //and revert the level
        this._Level--;

    }

    /// <summary>
    /// Writes the results to the Console
    /// </summary>
    public void ToConsole() {

        //make sure all of the jobs have finished
        if (this._Level > 0) {
            throw new InvalidOperationException(
                "Cannot report the final summary until all code blocks have completed."
                );
        }

        //stop the overall timer
        if (this._OverallTime.IsRunning) {
            this._OverallTime.Stop();
        }

        //showing code groups
        Func<int, string> treeLine = (count) => {
            string start = string.Empty;
            for (var i = 0; i < count; i++) {
                start = string.Concat(start, "|  ");
            }
            return start;
        };

        //display each code block
        foreach (object item in this._Blocks) {

            //if this is a starting block, show the work has started
            if (item is _StartBlock) {
                _StartBlock block = item as _StartBlock;
                Console.WriteLine(
                    "{0}[Begin] {1}: {2}",
                    treeLine(block.Level),
                    block.Identity,
                    block.Level
                    );
            }
            //if this is the closing block, display the timer result
            else if (item is _EndBlock) {
                _EndBlock block = item as _EndBlock;
                Console.WriteLine(
                    "{0}[End] {1}: {2} ({3})",
                    treeLine(block.StartingBlock.Level),
                    block.StartingBlock.Identity,
                    block.StartingBlock.Level,
                    block.StartingBlock.ToTimeString()
                    );
            }

        }

        //and display the summary
        Console.WriteLine(
            "\nTotal Blocks: {0}\nTotal Time: {1}", 
            this._Blocks.Where(item => item is _StartBlock).Count(),
            CodeBlockTimer._ToTimeString(this._OverallTime.ElapsedMilliseconds)
            );
    }

    #endregion

    #region Static Methods

    //creates a meaningful time string from some milliseconds
    private static string _ToTimeString(long ms) {
        if (ms > 60000) {
            return string.Format("{0:0.0}min", (double)ms / 60000d);
        }
        else if (ms > 1000) {
            return string.Format("{0:0.0}sec", (double)ms / 1000d);
        }
        else {
            return string.Format("{0}ms", ms);
        }
    }

    #endregion

    #region Timing Classes

    //the marker for the start of a code block
    private class _StartBlock {

        //starts a new block to check the starting time
        public _StartBlock(string identity, int level) {
            this.Identity = identity;
            this.Level = level;
        }

        //the identifying name for this code block
        public string Identity { get; private set; }

        //the level this starts at
        public int Level { get; private set; }

        //the timer for this code block
        public Stopwatch Timer { get; set; }
        
        //provides a meaningful time value
        public string ToTimeString() {
            return CodeBlockTimer._ToTimeString(this.Timer.ElapsedMilliseconds);
        }
    }

    //marker for the finishing of a code block
    private class _EndBlock {

        //starts a new block to check the ending time time
        public _EndBlock(_StartBlock start) {
            this.StartingBlock = start;
        }

        //the open block this code was started in
        public _StartBlock StartingBlock { get; private set; }
    }

    #endregion

}

You can use the code inline with existing code to measure sections of the code. If you are using an existing method then the name will automatically appear in the final report. Otherwise you can use the default name or provide your own identity (which can help provide meaningful names for Anonymous Methods).

//create a monitor
CodeBlockTimer monitor = new CodeBlockTimer();

//start measuring a block
monitor.Measure("First Method", () => {
    
    //simulate execution time
    Thread.Sleep(300);

    //measure a block without an identity
    monitor.Measure(() => {
        Thread.Sleep(500);

        //measure a defined method
        monitor.Measure(DoSomething);
    });

    //provide an identity for an anonymoure method
    monitor.Measure("Quick Call", () => {
        Thread.Sleep(250);
    });

    //more simulated execution time
    Thread.Sleep(300);

});

//stop measuring the timer (or simply call ToConsole())
monitor.Stop();

//Display the results of the method to the console
monitor.ToConsole();

And the results…


[Begin] First Method: 0
| [Begin] <Main>b__1: 1
| | [Begin] DoSomething: 2
| | [End] DoSomething: 2 (1000ms)
| [End] <Main>b__1: 1 (1.5sec)
| [Begin] Quick Call: 1
| [End] Quick Call: 1 (250ms)
[End] First Method: 0 (2.4sec)

Total Blocks: 4
Total Time: 2.4sec

Anyways, I’m sure there is a hundred other ways you could get better results — but this might be good enough for small quick measurements.

Written by hugoware

November 18, 2009 at 11:39 pm