Software Is Hardwork

ISimplicityAffinative: The endless pursuit of anti-complexity.
The technology-centric blog of D. P. Bullington.

Email D. P. Bullington View D. P. Bullington\ Follow D. P. Bullington on Twitter Get Software Is Hardwork code on CodePlex

Blog Post(s)

The Right Way to Impersonate Credentials in .NET
Monday, July 6, 2009

I recently discussed the wrong way to impersonate credentials in .NET. All of the code examples I have encountered have introduced a fatal design flaw: restoring an impersonated principal when impersonating yet another principal as in ASP.NET with impersonate=true. The following illustrates correct code based on my current understanding.

using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Security.Principal;

namespace SoftwareIsHardwork.Samples
{
    public class ImpersonationScope : IDisposable
    {
        #region Constructors/Destructors

        public ImpersonationScope(string userName, string domainName, string password, LogonType logonType, LogonProvider logonProvider)
        {
            IntPtr logonToken = IntPtr.Zero;
            IntPtr logonTokenDuplicate = IntPtr.Zero;

            Debug.WriteLine("Enter impersonation scope");

            if ((object)userName == null)
                throw new ArgumentNullException("userName");

            if ((object)domainName == null)
                throw new ArgumentNullException("domainName");

            if ((object)password == null)
                throw new ArgumentNullException("password");

            if (userName == "")
                throw new ArgumentOutOfRangeException("userName");

            if (domainName == "")
                throw new ArgumentOutOfRangeException("domainName");

            if (password == "")
                throw new ArgumentOutOfRangeException("password");

            try
            {
                Debug.WriteLine("Incoming identity: " + WindowsIdentity.GetCurrent().Name);

                this.processWindowsImpersonationContext = WindowsIdentity.Impersonate(IntPtr.Zero);

                Debug.WriteLine("Process-impersonated identity: " + WindowsIdentity.GetCurrent().Name);

                //if (Win32NativeMethods.RevertToSelf())
                {
                    if (LogonUser(userName,
                                  domainName,
                                  password,
                                  (int)logonType,
                                  (int)logonProvider,
                                  ref logonToken) != 0)
                    {
                        if (DuplicateToken(logonToken, (int)ImpersonationLevel.SecurityImpersonation, ref logonTokenDuplicate) != 0)
                        {
                            this.impersonatedWindowsIdentity = new WindowsIdentity(logonTokenDuplicate);
                            this.threadWindowsImpersonationContext = this.impersonatedWindowsIdentity.Impersonate();

                            Debug.WriteLine("Thread-impersonated identity: " + WindowsIdentity.GetCurrent().Name);
                        }
                        else
                            throw new Win32Exception(Marshal.GetLastWin32Error());
                    }
                    else
                        throw new Win32Exception(Marshal.GetLastWin32Error());
                }
            }
            catch
            {
                this.Dispose();
                throw;
            }
            finally
            {
                if (logonToken != IntPtr.Zero)
                {
                    CloseHandle(logonToken);
                    logonToken = IntPtr.Zero;
                }

                if (logonTokenDuplicate != IntPtr.Zero)
                {
                    CloseHandle(logonTokenDuplicate);
                    logonTokenDuplicate = IntPtr.Zero;
                }

                Debug.WriteLine("Leave constructor");
            }
        }

        #endregion

        #region Fields/Constants

        private readonly WindowsIdentity impersonatedWindowsIdentity;
        private readonly WindowsImpersonationContext processWindowsImpersonationContext;
        private readonly WindowsImpersonationContext threadWindowsImpersonationContext;
        private bool disposed;

        #endregion

        #region Methods/Operators

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

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

        [DllImport("advapi32.dll", SetLastError = true)]
        private static extern int LogonUser(
            string lpszUserName,
            string lpszDomain,
            string lpszPassword,
            int dwLogonType,
            int dwLogonProvider,
            ref IntPtr phToken);

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

        public void Dispose()
        {
            if (this.disposed)
                return;

            try
            {
                Debug.WriteLine("Thread-impersonated identity: " + WindowsIdentity.GetCurrent().Name);

                if ((object)this.threadWindowsImpersonationContext != null)
                {
                    this.threadWindowsImpersonationContext.Undo();
                    this.threadWindowsImpersonationContext.Dispose();
                }

                if ((object)this.impersonatedWindowsIdentity != null)
                    this.impersonatedWindowsIdentity.Dispose();

                Debug.WriteLine("Process-impersonated identity: " + WindowsIdentity.GetCurrent().Name);

                if ((object)this.processWindowsImpersonationContext != null)
                {
                    this.processWindowsImpersonationContext.Undo();
                    this.processWindowsImpersonationContext.Dispose();
                }

                Debug.WriteLine("Outgoing identity: " + WindowsIdentity.GetCurrent().Name);
            }
            finally
            {
                this.disposed = true;
                GC.SuppressFinalize(this);

                Debug.WriteLine("Leave impersonation scope");
            }
        }

        #endregion

        #region Classes/Structs/Interfaces/Enums/Delegates

        public enum ImpersonationLevel
        {
            SecurityAnonymous = 0,
            SecurityIdentification = 1,
            SecurityImpersonation = 2,
            SecurityDelegation = 3
        }

        public enum LogonProvider
        {
            LOGON32_PROVIDER_DEFAULT = 0,
            LOGON32_PROVIDER_WINNT35 = 1,
            LOGON32_PROVIDER_WINNT40 = 2,
            LOGON32_PROVIDER_WINNT50 = 3
        }

        public enum LogonType
        {
            LOGON32_LOGON_INTERACTIVE = 2,
            LOGON32_LOGON_NETWORK = 3,
            LOGON32_LOGON_BATCH = 4,
            LOGON32_LOGON_SERVICE = 5,
            LOGON32_LOGON_UNLOCK = 7,
            LOGON32_LOGON_NETWORK_CLEARTEXT = 8, // Win2K or higher
            LOGON32_LOGON_NEW_CREDENTIALS = 9 // Win2K or higher
        }

        #endregion
    }
}

Sample usage scenario:

using (scope = new ImpersonationScope(username, domain, password, ImpersonationScope.LogonType.LOGON32_LOGON_INTERACTIVE, ImpersonationScope.LogonProvider.LOGON32_PROVIDER_DEFAULT))
{
  // code under impersonation
}

Your Organization's Version Control System is a Production Environment
Thursday, July 2, 2009

If you use version control, work item tracking, defect tracking, project management, or any other system which stores data related to software development activities, you must be consider these systems production environments, and treat them as such. Even if only developers access these systems, they should be subject to the same maintenance and scrutiny as your LOB money makers. Failure to heed this advice can and will cost your organization money and time eventually - not if but when.

Inside the .NET Framework v3.5 GAC File System
Tuesday, June 30, 2009

The GAC or global assembly cache is a system-wide store of shared assemblies. It is meant to solve issues such as DLL Hell but a word of caution: avoid storing your assemblies in the GAC unless you have a solid and compelling reason to do so. I digress; this post is not meant to be a when you should versus when you should not store assemblies in the GAC, rather it will serve as a peek under the hood.

The GAC is implemented by the .NET Framework as a set of directories under %SystemRoot%\assemblies. The layout is as such:

\GAC v1.1 assemblies
\GAC_32 v2.0 assemblies with 32-bit affinity
\GAC_64 v2.0 assemblies with 64-bit affinity
\GAC_MSIL v2.0 assemblies without bitness affinity
\NativeImages1_v1.1.4322 ngen of v1.1 assemblies
\NativeImages_v2.0.50727_32 ngen of v1.1 assemblies with 32-bit affinity
\NativeImages_v2.0.50727_64 ngen of v1.1 assemblies with 64-bit affinity
\temp temporary storage
\tmp temporary storage

Under the GACxxx directories live a subdirectory for each assembly added to the GAC (in the format: NameOfAssembly; e.g. System.Transactions).

Under these directories live a directory for each version + strong name combination (in the format: AssemblyVersion__PublicKeyToken e.g. 2.0.0.0__b77a5c561934e089).

Under these directories lives the actual assembly files (e.g. System.Transactions.dll).

Not so bad of a monster is it?

Observation: SharePoint x86 and x64 MSIL Assemblies Are Not Equal

I needed to analyze some SharePoint/MOSS 2007 SP2 assemblies and discovered that the SharePoint MSIL assemblies have different MVIDs:

Microsoft.SharePoint (x86): 3212133f-6dd8-4a28-b6bd-c624a9fbb11e
Microsoft.SharePoint (x64): d0b5a3f1-d8b8-400e-88d8-8a7c47b5dd18

This is the case for all of the assemblies packaged with MOSS 2007 SP2. I am not sure if this is just a case of clean build per platform or actual API differences between platform. Note that I am only referring to MSIL assemblies and not true x86/x64 specific platform targeted assemblies.

The Wrong Way to Impersonation Credentials in .NET
Friday, June 26, 2009

Impersonation sample code exists on the Internet for .NET. Recently, I needed to impersonate a service user in SharePoint and/or ASP.NET. Taken from MSDN, consider the following typical impersonation code:

 

public const int LOGON32_LOGON_INTERACTIVE = 2;
public const int LOGON32_PROVIDER_DEFAULT = 0;

WindowsImpersonationContext impersonationContext; 

[DllImport("advapi32.dll")]
public static extern int LogonUserA(String lpszUserName, 
    String lpszDomain,
    String lpszPassword,
    int dwLogonType, 
    int dwLogonProvider,
    ref IntPtr phToken);
[DllImport("advapi32.dll", CharSet=CharSet.Auto, SetLastError=true)]
public static extern int DuplicateToken(IntPtr hToken, 
    int impersonationLevel,  
    ref IntPtr hNewToken);
                          
[DllImport("advapi32.dll", CharSet=CharSet.Auto, SetLastError=true)]
public static extern bool RevertToSelf();

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

public void Page_Load(Object s, EventArgs e)
{
    if(impersonateValidUser("username", "domain", "password"))
    {
        //Insert your code that runs under the security context of a specific user here.
        undoImpersonation();
    }
    else
    {
        //Your impersonation failed. Therefore, include a fail-safe mechanism here.
    }
}

private bool impersonateValidUser(String userName, String domain, String password)
{
    WindowsIdentity tempWindowsIdentity;
    IntPtr token = IntPtr.Zero;
    IntPtr tokenDuplicate = IntPtr.Zero;

    if(RevertToSelf())
    {
        if(LogonUserA(userName, domain, password, LOGON32_LOGON_INTERACTIVE, 
            LOGON32_PROVIDER_DEFAULT, ref token) != 0)
        {
            if(DuplicateToken(token, 2, ref tokenDuplicate) != 0) 
            {
                tempWindowsIdentity = new WindowsIdentity(tokenDuplicate);
                impersonationContext = tempWindowsIdentity.Impersonate();
                if (impersonationContext != null)
                {
                    CloseHandle(token);
                    CloseHandle(tokenDuplicate);
                    return true;
                }
            }
        } 
    }
    if(token!= IntPtr.Zero)
        CloseHandle(token);
    if(tokenDuplicate!=IntPtr.Zero)
        CloseHandle(tokenDuplicate);
    return false;
}

private void undoImpersonation()
{
    impersonationContext.Undo();
}

The above code suffers from a subtle bug as do all of the other samples I have seen: impersonating while impersonating. The above code works fine if you are not impersonating a user. What if you are, such as in ASP.NET when impersonate=true in the web.config? The above code will impersonate the new user but when impersonation is supposedly undone, you have reverted completely to square one which is not good. So you cannot just revert to self before impersonation...and you also cannot revert to self after impersonation either...

A little background might help. In Windows (not specific to .NET code), there exists a process security token and a thread security token. When a process is started, the process security token is set to that of what the operating system considers the running identity. A thread can override the the process security token by impersonating another identity. A thread which is impersonating can undo impersonation by "reverting to self". This reestablishes the process security token as the truth. Rule: You must revert to self before impersonation on a thread.

Back to the .NET world, if you attempt to impersonate another identity while you are impersonating on the current thread, things fall apart. A call to WindowsIdentity.GetCurrent() will fail with a SecurityException because of this scenario (see rule above). So just how in the hell do you impersonate if you are impersonating which is not an unreasonable request?

Next time, correct impersonation code for .NET...

Custom Zero Friction DI/IoC Framework

I recently needed to leverage dependency injection (DI)/inversion of control (IoC) in some code but I wanted to keep it simple. Sure, I could have used any one of a number of DI/IoC frameworks but I wanted zero friction:

  • Absolutely no XML configuration
  • .NET 2.0+ and generic support
  • Context and key features
  • Fast performance and small code base
  • Easy for other developers to grasp and use

No framework in the above list met every one of my requirements, so in around 200 lines of code, I rolled my own. I will integrate this into the next release of the Software is Hardwork Library under the Core.Dynamic namespace…For now, the code appears below.

 

/*
	Copyright ©2002-2009 D. P. Bullington
	Distributed under the MIT license: http://www.opensource.org/licenses/mit-license.php
*/

// DependencyManager.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace SoftwareIsHardwork.Samples
{
    // TODO: Add interface
    public sealed class DependencyManager
    {
        private DependencyManager()
        {
        }

        static DependencyManager()
        {
            DependencyManager.instance.EnterContext(); // global, app domain
        }
        
        private static readonly DependencyManager instance = new DependencyManager();
        private readonly Stack<Dictionary<KeyValuePair<Type, string>, Delegate>> dependencyContextStack = new Stack<Dictionary<KeyValuePair<Type, string>, Delegate>>();

        public static DependencyManager Instance
        {
            get
            {
                return DependencyManager.instance;
            }
        }
        
        public IDisposable EnterContext()
        {
            this.dependencyContextStack.Push(new Dictionary<KeyValuePair<Type, string>, Delegate>());

            return new ActionUsingScope(() =>
            {
            }, () => this.LeaveContext());
        }

        public void LeaveContext()
        {
            Dictionary<KeyValuePair<Type, string>, Delegate> dependencyContext;

            if (this.dependencyContextStack.Count <= 1) // prevent leaving global dependency context
                throw new InvalidOperationException("Cannot leave dependency context.");

            dependencyContext = this.dependencyContextStack.Peek();

            if ((object)dependencyContext == null)
                throw new InvalidOperationException("dependencyResolutionMethod");
            
            dependencyContext.Clear();

            this.dependencyContextStack.Pop();
        }

        public void AddResolution<TInterface>(string key, Func<TInterface> dependencyResolutionMethod)
            where TInterface : class
        {
            Dictionary<KeyValuePair<Type, string>, Delegate> dependencyContext;
            Type type;

            if ((object)key == null)
                throw new ArgumentNullException("key");
            
            if ((object)dependencyResolutionMethod == null)
                throw new ArgumentNullException("dependencyResolutionMethod");

            if (this.dependencyContextStack.Count < 1)
                throw new InvalidOperationException("Cannot add resolution method to current dependency context.");

            dependencyContext = this.dependencyContextStack.Peek();
                
            if ((object)dependencyContext == null)
                throw new InvalidOperationException("dependencyResolutionMethod");

            type = typeof(TInterface);

            if (!type.IsInterface)
                throw new InvalidOperationException(type.FullName);

            dependencyContext.Add(new KeyValuePair<Type, string>(type, key), dependencyResolutionMethod);
        }

        public void ClearResolutions()
        {
            Dictionary<KeyValuePair<Type, string>, Delegate> dependencyContext;

            if (this.dependencyContextStack.Count < 1)
                throw new InvalidOperationException("Cannot clear resolution methods from current dependency context.");

            dependencyContext = this.dependencyContextStack.Peek();

            if ((object)dependencyContext == null)
                throw new InvalidOperationException("dependencyResolutionMethod");

            dependencyContext.Clear();
        }

        public TInterface ResolveDependency<TInterface>(string key)
            where TInterface : class
        {
            Type type;
            TInterface value;

            if ((object)key == null)
                throw new ArgumentNullException("key");

            type = typeof(TInterface);

            if (!type.IsInterface)
                throw new InvalidOperationException(type.FullName);

            if (!this.TryResolveDependency<TInterface>(key, out value))
                throw new InvalidOperationException(string.Format("Dependency resolution in current dependency context failed to match for type {0} and key {1}.", type.FullName, key));
            
            return value;
        }

        public bool TryResolveDependency<TInterface>(string key, out TInterface value)
            where TInterface : class
        {            
            Dictionary<KeyValuePair<Type, string>, Delegate> dependencyContext;
            KeyValuePair<Type, string> trait;
            Delegate method;
            Func<TInterface> dependencyResolutionMethod;
            Type type;

            value = null;

            if ((object)key == null)
                throw new ArgumentNullException("key");

            if (this.dependencyContextStack.Count < 1)
                throw new InvalidOperationException("Cannot resolve dependency in current dependency context.");

            dependencyContext = this.dependencyContextStack.Peek();

            if ((object)dependencyContext == null)
                throw new InvalidOperationException("dependencyResolutionMethod");
            
            type = typeof(TInterface);

            if (!type.IsInterface)
                throw new InvalidOperationException(type.FullName);

            trait = new KeyValuePair<Type,string>(type, key);
            
            if(!dependencyContext.ContainsKey(trait))
                return false;

            method = dependencyContext[trait];

            if ((object)method == null)
                throw new InvalidOperationException("method");

            dependencyResolutionMethod = (Func<TInterface>)method;
            //dependencyResolutionMethod = (Func<TInterface>)(Delegate.CreateDelegate(typeof(Func<TInterface>), method.Target, method.Method));

            if ((object)dependencyResolutionMethod == null)
                throw new InvalidOperationException("dependencyResolutionMethod");

            value = dependencyResolutionMethod();

            return true;
        }
    }
}

 

/*
	Copyright ©2002-2009 D. P. Bullington
	Distributed under the MIT license: http://www.opensource.org/licenses/mit-license.php
*/

// ActionUsingScope.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace SoftwareIsHardwork.Samples
{
    public class ActionUsingScope : IDisposable
    {
        public ActionUsingScope(Action enterScopeMethod, Action leaveScopeMethod)
        {
            if ((object)enterScopeMethod == null)
                throw new ArgumentNullException("enterScopeMethod");

            if ((object)leaveScopeMethod == null)
                throw new ArgumentNullException("leaveScopeMethod");

            this.enterScopeMethod = enterScopeMethod;
            this.leaveScopeMethod = leaveScopeMethod;

            this.enterScopeMethod();
        }

        private readonly Action enterScopeMethod;
        private readonly Action leaveScopeMethod;
        private bool disposed;

        public void Dispose()
        {
            if (this.disposed)
                return;

            try
            {
                this.leaveScopeMethod();
            }
            finally
            {
                this.disposed = true;
                GC.SuppressFinalize(this);
            }
        }
    }
}

 

Sample usage:
/*
    Copyright ©2002-2009 D. P. Bullington
    Distributed under the MIT license: http://www.opensource.org/licenses/mit-license.php
*/

// Program.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Security.Permissions;
using System.Security.Principal;
using System.Collections.ObjectModel;

using SoftwareIsHardwork.Samples;

namespace SoftwareIsHardwork.Samples
{
    public class Program
    {
        [STAThread]
        public static void Main()
        {
            DependencyManager.Instance.AddResolution<IEnumerable<int>>("", delegate()
            {
                return new Collection<int>();
            });

            DependencyManager.Instance.AddResolution<IEnumerable<int>>("i_want_a_list", delegate()
            {
                return new List<int>();
            });

            DependencyManager.Instance.AddResolution<IEnumerable<int>>("i_want_an_array", delegate()
            {
                return new int[10];
            });
        
            using (DependencyManager.Instance.EnterContext())
            {
                DependencyManager.Instance.AddResolution<IEnumerable<int>>("i_want_an_array", delegate()
                {
                    return new ReadOnlyCollection<int>(new List<int>());
                });

                Console.WriteLine(DependencyManager.Instance.ResolveDependency<IEnumerable<int>>("i_want_an_array"));
            }
                                                    
            Console.WriteLine(DependencyManager.Instance.ResolveDependency<IEnumerable<int>>("i_want_an_array"));
            Console.WriteLine(DependencyManager.Instance.ResolveDependency<IEnumerable<int>>("i_want_a_list"));
            Console.WriteLine(DependencyManager.Instance.ResolveDependency<IEnumerable<int>>(""));
        }
    }    
}

 

Sample output:
System.Collections.ObjectModel.ReadOnlyCollection`1[System.Int32]
System.Int32[]
System.Collections.Generic.List`1[System.Int32]
System.Collections.ObjectModel.Collection`1[System.Int32]

 

Enjoy!

ECM = Content Management Systems + Content Management Processes
Thursday, June 11, 2009

Recently, I have (reluctantly) been working with MOSS 2007 Enterprise Content Management (ECM) features. This work has made me reflect on the distinctions between content management systems (CMS) and content management processes (CMP) in an overall ECM strategy.

Lets start by defining these terms which will play a pivotal role as we continue deeper in this monologue.

The Association for Information and Image Management characterizes ECM as "the strategies, methods and tools used to capture, manage, store, preserve, and deliver content and documents related to organizational processes".

Wikipedia defines a CMS as "a computer application used to manage work flow needed to collaboratively create, edit, review, index, search, publish and archive various kinds of digital media and electronic text".

I assert that the term CMP represents "the business processes and activities which serve to designate, promote, automate, and deliver content".

I contend that an ECM strategy is the sum of these two components CMS and CMP; where the CMS and CMP factors vary depending on the business and technical requirements which necessitate the ECM strategy in the first place. Consider the following generic matrix of CMS and CMP factor combinations as it relates to an overall generic ECM strategy:

  CMP component CMS component
ECM Strategy A low low
ECM Strategy B low high
ECM Strategy C high low
ECM Strategy D high high

ECM Strategy A would be exist in an organization with a small to medium sized content collection in which content is changed informally and infrequently.

ECM Strategy B for instance, could be an organization with a small to medium sized content collection in which content is changed informally but quite frequently. The word could is used because this strategy is a fallacy; frequent content changes without any rhyme or reason is setting up for failure.

ECM Strategy C for instance, would be an organization with a small to medium sized content collection in which content is changed formally but still infrequently.

ECM Strategy D ideally illustrate an organization with a large sized content collection in which content is changed methodically and frequently.

So, ECM strategies A, B, and D are valid; C is to be avoided. Why is this important?

An organization must decide what strategy makes sense based on business need, technical need, etc. The ECM strategy will drive what shakes out in terms of what processes are put in place, what technical solution will be required to facilitate content management, etc. If an organization chooses their ECM strategy incorrectly, they risk under or over architecting a solution. For example, an organization that truly needs to leverage ECM Strategy A but incorrectly chooses ECM Strategy D may embark on a misguided journey to implement a Cadillac CMS solution and convoluted processes when what they really needed was a Chevy.

Microsoft Technology "x" is not Dead
Wednesday, June 3, 2009

I get a chuckle out of all of the development community "experts" that proclaims another Microsoft technology is "dead".

The most notable instances I hear are (deprecated => replacement) and my commentary:

  • Win32 | COM | VB6 => .NET
    • .NET is an excellent framework but will never supplant 100% of the unmanaged OS base services.
  • ASP.NET Forms => ASP.NET MVC
    • Both are perfectly useful in the right circumstances - choose wisely.
  • LINQ to SQL => Entity Framework
    • LINQ to SQL is generally "feature complete" and is designed for a specific usage scenarios. EF has its own demons to exercise.
    • Consider using SoftwareIsHardwork.Database for ease of use with LINQ to SQL.
  • WinForms => WPF
    • WinForms is "feature complete"; do not be afraid to use it. WPF is a new beast to contend with but again, choose wisely.
  • ADO.NET => Entity Framework
    • Um, the heart of ADO.NET is still beating. Still the best for performance and flexibility. Just keep business logic out of the database.
    • Consider using SoftwareIsHardwork.Database for ease of use with raw ADO.NET.
  • AJAX => Silverlight
    • Although I wish AJAX would die a horrible death, unfortunately we cannot expect a Silverlight runtime install in every situation.

Most of the time, claims of "the death of a framework"  stems from three causes:

  • Marketing
    • Does the person rely solely on shiny-new-framework buzz?
  • Ignorance
    • Does the person know enough about the framework roadmap to comment?
  • Agenda
    • Does the person have a vested interest in spreading such information?

Then again, I could be doing all three of the above things in writing this post...hmmmmmmmmmm?! :)

Software Is Hardwork Library 1.4.0.363 Released
Monday, May 25, 2009

I just released a new version of the Software Is Hardwork Library. This release contains many major and minor enhancements. I have decided to stop creating a change log per release as it is time consuming; simply difference any two consecutive release source tree for this data. Major highlights include:

  • Core
    • Reorganization of namespace into Domain.*, Dynamic.*, and Utility.*.
    • Added Widgets.* interfaces for future use.
    • Official support for CommandLineException, CommandLineParameter, and ProgramBase; now in Utility.*.
      • Added as beta enhancement (not completely tested yet).
    • Official support for (I)ListItem et. al; now in Core.*.
    • Simple rules engine in Domain.*.
    • Pager and AsmInfo re-added to Utility.*.
  • Database
    • DatabaseExPersistenceObjectadded as beta enhancement (no tests yet).
    • LinqToSqlDatabase added as beta enhancement (no tests yet).
    • Removed Mapping.* until vision is clarified.
  • SerivceModelClient
    • High priority bug fixes.
    • All objects returned by RealProxyServiceClientFactory now implement IDisposable automatically.
  • All
    • Enhanced test suites and increased unit test code coverage.

This release encompasses quite a lot of work and I am proud to bring it to the developer community. I plan on enhancing this library as time progresses.

Code Camp Talk: WCF Client-Side Objects - The Untold Story (previously Going Proxy-less - The WCF Proxy Factory)

I presented "WCF Client-Side Objects - The Untold Story", formerly titled "Going Proxy-less - The WCF Proxy Factory", a discussion of the SoftwwareIsHardwork.ServiceModelClient API at the NoVa Code Camp 2009.1 this weekend. It went quite well and I have a nice number of attendees to the talk. Good questions too!

The PDF conversion of the slide deck can be had here:
WCF Client-Side Objects - The Untold Story.pdf

The sample application demonstrated during the talk can be had here:
WCF Client-Side Object Sample.zip

MP3 podcast:
WCF Client-Side Objects - The Untold Story (NoVa Code Camp 2009.1).mp3

The OLD but relevant "Going Proxy-less - The WCF Proxy Factory" artifacts:

The PDF conversion of the slide deck can be had here:
Going Proxy-less - The WCF Proxy Factory.pdf

The sample application demonstrated during the talk can be had here:
Distributed String Tokenizer Sample.zip

Thanks to all those who organized the affair and to all those who attended my talk.

Blog Archive

Labels

Blog List

Speaking Enagements

  • 12/8/2009 | Hampton Roads .NET Users Group | Cheaspeake, VA | Topic TBD
  • 07/16/2009 | Charlottesville .NET Users Group | Charlottesville, VA | Debugging on the Windows Platform
  • 05/23/2008 | NoVa CodeCamp 2009.01 | Reston, VA | Going Proxy-less - The WCF Proxy Factory
  • 04/25/2009 | Richmond Code Camp 2009.1 | Richmond, VA | Software Programmer to Software Engineer: Concepts to Span the Divide
  • 02/05/2009 | Richmond .NET Users Group | Richmond, VA | Debugging on the Windows Platform
  • 10/04/2008 | Richmond Code Camp 2008.2 | Richmond, VA | Going Proxy-less - The WCF Proxy Factory

Disclaimer

© D. P. Bullington, all rights reserved. Everything posted on this blog is my personal opinion and does not necessarily represent the views of my employer or its clients.