Friday, February 13, 2009

EnumHelper using generics to reduce casting

using System;
using System.ComponentModel;

namespace ConsoleApplication1
{
    public enum ThreadPriority
    {
        [Description("Highest")]
        Highest,
        [Description("Above Normal")]
        AboveNormal,
        [Description("Normal")]
        Normal,
        [Description("Below Normal")]
        BelowNormal,
        [Description("Lowest")]
        Lowest
    }

    class Program
    {
        static void Main(string[] args)
        {
            ThreadPriority threadPriority = EnumHelper<ThreadPriority>.Parse("AboveNormal");
            System.Diagnostics.Debug.Assert(threadPriority == ThreadPriority.AboveNormal);

            string description = EnumHelper<ThreadPriority>.EnumValueDescription(threadPriority);
            System.Diagnostics.Debug.Assert(description.Equals("Above Normal"));

            threadPriority = EnumHelper<ThreadPriority>.ParseOrDescriptionMatch("Below Normal", ThreadPriority.Normal);
            System.Diagnostics.Debug.Assert(threadPriority == ThreadPriority.BelowNormal);

            if (!EnumHelper<ThreadPriority>.TryParse("Highest", out threadPriority))
            {
                System.Diagnostics.Debug.Fail("TryParse expected to succeed");
            }
            System.Diagnostics.Debug.Assert(threadPriority == ThreadPriority.Highest);

            if (EnumHelper<ThreadPriority>.TryParse("Foo Bar", out threadPriority))
            {
                System.Diagnostics.Debug.Fail("TryParse expected to fail");
            }
        }
    }

    public static class EnumHelper<T>
       where T : struct, IComparable, IFormattable, IConvertible
    {

        /// 
        /// Static constructor to ensure T is an enum
        /// 
        static EnumHelper()
        {
            if (!typeof(T).IsEnum)
            {
                throw new ArgumentException("Type parameter must be an enum");
            }
        }     
  
        /// <summary>
        ///  Converts the string representation of the name or numeric value of one or
        ///  more enumerated constants to an equivalent enumerated object.
        /// </summary>
        /// <param name="value">A string containing the name or value to convert.</param>
        /// <returns>An object of type T whose value is represented by value.</returns>
        /// <exception cref="System.ArgumentNullException">value is null</exception>
        /// <exception cref="System.ArgumentException">value is either an empty string or
        /// only contains white space.  -or- value is a name, but not one of the named
        /// constants defined for the enumeration.
        ///</exception>
        public static T Parse(string value)
        {
            return (T)Enum.Parse(typeof(T), value);
        }

        /// 
        /// Converts the string representation of the name or numeric value of one or more enumerated constants to an equivalent enumerated object.
        /// 
        /// A string containing the name or value to convert.
        /// When this method returns, contains the object of type T whose 
        /// value is represented by the value, if the conversion succeeded, or the first 
        /// value in the enum if conversion failed. This parameter is passed uninitialized.
        /// true if value was converted successfully; otherwise, false.
        public static bool TryParse(object value, out T returnValue)
        {
            Type underlyingType = Enum.GetUnderlyingType(typeof(T));
            bool supportedType = (value is string || value.GetType().Equals(underlyingType));
            if (supportedType && Enum.IsDefined(typeof(T), value))
            {
                //direct string or underlying type match
                returnValue = Parse(value.ToString());
                return true;
            }
            else if (Enum.IsDefined(typeof(T), Convert.ChangeType(value, underlyingType))) // May throw overflow exception. E.g. long.MaxValue to Int32
            {
                //underlying numeric type match after type conversion
                returnValue = (T)Enum.Parse(typeof(T), value.ToString());
                return true;
            }
            else
            {
                //Default to the first item from the enum
                string[] values = Enum.GetNames(typeof(T));
                returnValue = (T)Enum.Parse(typeof(T), values[0], true);

                //default(T) won't work for all enums.
                //E.g. if the underlying type for the enum is int default(T) will always return 0, for which there might not be a value.
            }

            return false;
        }

        /// <summary>
        /// Attempt to read the Description Attribute
        /// </summary>
        /// <param name="e">The enum value to read the description from</param>
        /// <returns>The description value for the enum, otherwise the enum value converted to a string.</returns>
        public static string EnumValueDescription(T e)
        {
            System.Reflection.FieldInfo EnumInfo = e.GetType().GetField(e.ToString());

            System.ComponentModel.DescriptionAttribute[] enumAttributes =
                (System.ComponentModel.DescriptionAttribute[])
                EnumInfo.GetCustomAttributes(typeof(System.ComponentModel.DescriptionAttribute), false);

            if (enumAttributes.Length > 0)
            {
                return enumAttributes[0].Description;
            }
            return e.ToString();
        }

        /// <summary>
        /// Converts the string representation of the name or numeric value of one or
        ///  more enumerated constants to an equivalent enumerated object.
        ///  If a direct match isn't found a match will be attempted on the description attributes.
        /// </summary>
        /// <param name="value"></param>
        /// <param name="defaultValue"></param>
        /// <returns></returns>
        public static T ParseOrDescriptionMatch(string value, T defaultValue)
        {
            Type type = typeof(T);
            try
            {
                return Parse(value);
            }
            catch (Exception)
            {
                //Try Description Matching
                string[] names = Enum.GetNames(type);
                foreach (string name in names)
                {
                    T nameEnum = Parse(name);

                    string nameEnumValue = EnumValueDescription(nameEnum);

                    if (nameEnumValue == value)
                    {
                        return nameEnum;
                    }
                }

                return defaultValue;
            }
        }
    }
}

Thursday, February 12, 2009

Common Visual Studio settings

Tools > Options > Projects and Solutions > General > ...

  • "Track Active Item in Solution Explorer"
  • "Show Output window when build starts"

Tools > Options > Environment > Find and Replace ...

  • Automatically populate Find What with text from the editor

If you have a suitable level of OCD you can also show white spaces.
Edit > Advanced > View White Space. See Also Coding Horror - Whitespace: The Silent Killer

DataReader.ToInt32(0) versus Convert.ToInt32(dataReader[0])

DataReader.GetInt32() will not perform any type conversions; therefore, the data retrieved must already be a 32-bit signed integer. Convert.ToInt32() will be happy to convert from a double, for example.

I'd tend to err towards the stricter approach to avoid unforeseen casting issues. I.e. something changes in the database.

Checks for IsDBNull() may also be required.

You will also need to consider how to reference the columns. By name or by ordinal position. See Roughly 3% penalty for indexing SqlDataReader columns by string rather than int.

Monday, February 9, 2009

Fine grained authorization in ASP.NET 2.0+

I have the requirement for fine grained authorization control. I considered using the standard ASP.NET roles, but really need an extra "level" added.

For example, tasks or logical operations like "CanCreateNewDocument" map to logical roles in the organization like "Manager". Users are then assigned to logical roles rather than logical operations. Authorization checks are performed against logical operations.

There doesn't appear to be the ability to nest roles within roles. I.e. Role Inheritance.

Something like the Microsoft Authorization Manager may be able to fill the gap.

Using Membership, Role, and Profile outside of ASP.NET (3.5)

From Accessing the ASP.NET Authentication, Profile and Role Service in Silverlight. Use the following WCF services:

LINQ to SQL and LINQ to Entities

The blog entry Microsoft’s Data Access Strategy has some good points on when you would consider using LINQ to Entities versus LINQ to SQL.

Generating compiled help files - CHM with sandcastle

Setting up Sandcastle to build CHM files can be a bit fiddly using MSBuild files etc... The Sandcastle Help File Builder adds a GUI front end among other things.
Sandcastle, created by Microsoft, is a tool used for creating MSDN-style documentation from .NET assemblies and their associated XML comments files. The current version is the May 2008 release. It is command line based and has no GUI front-end, project management features, or an automated build process like those that you can find in NDoc. The Sandcastle Help File Builder was created to fill in the gaps, provide the missing NDoc-like features that are used most often, and provide graphical and command line based tools to build a help file in an automated fashion.

See also - MSDN: Recommended Tags for Documentation Comments (C# Programming Guide)