Showing posts with label C# Tips and Tricks. Show all posts
Showing posts with label C# Tips and Tricks. Show all posts

Wednesday, 5 May 2010

How to Recursively Get the Group Membership of a User in Active Directory using .NET/C# and LDAP (without just 2 hits to Active Directory)

Problem
What should you do if you need to find all the indirect group memberships that a user has in Active Directory? While it is possible to recursively navigate (ie traverse) through all the group structures that your user is a member of, this can be a very intensive process and can potentially involve 100s of calls to the LDAP server (A slight performance hit to say the list. In sum, BAD!)

In the example below, how do we determine that a user is a member of a top level group without making intensive, recursive calls to Active Directory/LDAP?


Solution
A better option is to use the power of [Microsoft's Implementation] of LDAP to get the results in only 2 hits to the server.
  1. We start of with the user's login name (e.g. david.klein)
  2. We query ldap to get their Container Name (CN) e.g. CN=David Klein
  3. We use the special query syntax provided by Microsoft LDAP in the Directory Searcher Filter to recursively get a list of all groups that the user is directly AND indirectly a member of.
Details

See source code below for 2 helper methods you can use to recursively determine if the designated user is directly or indirectly a member of a particular group. Note that we use the special filter syntax using a specific member flag that will get all indirect memberships automatically for us:

"(member:1.2.840.113556.1.4.1941:=CN=My User Name,OU=Users,OU=NSW,OU=DDKONLINE,DC=DDKONLINE,DC=int)"

/// 
        /// Recursively Gets ALL nested group memberships of a user and checks the input group is there.
        /// 
        /// 
e.g. david.klein or kled123/// 
Container Name of Group e.g. "SP_DEV_HR"/// Uses following config entries
        /// 
        /// 
        /// 
        public static bool IsUserMemberOfGroup(string username, string groupname)
        {
            ///ConfigHelper.LDAPRoot is "LDAP://DC=DDKONLINE,DC=int"
            DirectoryEntry entry = new DirectoryEntry(ConfigHelper.LDAPRoot);
            // Create a DirectorySearcher object.
            DirectorySearcher mySearcher = new DirectorySearcher(entry);
            //Filter by special recursive LDAP string e.g. 
            //"(member:1.2.840.113556.1.4.1941:=CN={0},OU=Users,OU=NSW,OU=DDKONLINE,DC=DDKONLINE,DC=int)"
            mySearcher.Filter = string.Format(ConfigHelper.LDAPGroupMemberFilterRecursive, 
                GetUserContainerName(username));
            mySearcher.SearchScope = SearchScope.Subtree; //Search from base down to ALL children. 
            SearchResultCollection result = mySearcher.FindAll();
            //StringBuilder sb = new StringBuilder();

            for (int i = 0; i < result.Count - 1; i++)
            {
                if (result[i].Path.ToUpper().Contains(string.Format("CN={0}", groupname.ToUpper())))
                    return true; //Success - group found
            }
            //No match found
            return false;
        }

        /// 
        /// Gets the Container Name (CN) of the input user.
        /// 
        /// 
/// 
        public static string GetUserContainerName(string userName)
        {
            DirectoryEntry entry = new DirectoryEntry(ConfigHelper.LDAPRoot);
            // Create a DirectorySearcher object.
            DirectorySearcher mySearcher = new DirectorySearcher(entry);
            mySearcher.Filter = string.Format("(&(sAMAccountName={0}))", userName);
            mySearcher.SearchScope = SearchScope.Subtree; //Search from base down to ALL children. 
            SearchResultCollection result = mySearcher.FindAll();
            if (result.Count == 0)
                throw new ApplicationException(string.Format("User '{0}' Not Found in Active Directory.", userName));
            return result[0].GetDirectoryEntry().Name.Replace("CN=",string.Empty);  
        }

Example Unit Test Methods
/// 
        /// This Test checks that the recursive search works correctly against Active directory.
        /// ie. that it picks up indirect membership
        /// Uses following config entries
        /// 
        /// 
        /// 
        [TestMethod()]
        public void IsUserMemberOfGroup_DirectMembership_Positive_Test()
        {
            string username = "sp_dev_pdmtest1"; 
            string groupname = "SP_DEV_HR"; // TODO: Initialize to an appropriate value
            bool expected = true; // TODO: Initialize to an appropriate value
            bool actual;
            actual = ADHelper.IsUserMemberOfGroup(username, groupname);
            Assert.AreEqual(expected, actual);
        }

        /// 
        /// This Test checks that the recursive search works correctly against Active directory.
        /// ie. that it picks up indirect membership
        /// 
        [TestMethod()]
        public void IsUserMemberOfGroup_IndirectMembership_Positive_Test()
        {
            string username = "sp_dev_pdmtest1";
            //Naming Convention for Groups is Environment_AppDomain_FunctionalArea_ObjectType (e.g. Form)_Role
            string groupname = "SP_DEV_Onlineforms_Peoplemgmt_Termination_F_Contributors"; // TODO: Initialize to an appropriate value
            bool expected = true; // TODO: Initialize to an appropriate value
            bool actual;
            actual = ADHelper.IsUserMemberOfGroup(username, groupname);
                Assert.AreEqual(expected, actual);
        }

        ///This Test Checks that the container name is resolved. Container name is used by the recursive group search.
        ///
        [TestMethod()]
        public void GetUserContainerNameTest()
        {
            string username = "david.klein"; 
            string expected = "David Klein"; 
            string actual = ADHelper.GetUserContainerName(username);
            Assert.AreEqual(expected, actual);
        }


DDK

Monday, 26 April 2010

.NET Deep Copy/Deep Clone between 2 different object types in C# – Some of the Different/Alternative Approaches

I’ve had to use this “Translation” pattern (and similar code) several times over the last few years – so I’ve decided to commit it to a blog entry for future reference.

If you have 2 objects (of different types e.g. say 2 different generated WCF client proxies pointing to SAP), with similar generated properties (e.g. both types have the same “Employee” structure with UserId, UserName, Display Name, then you could spend a lot of time trying to write code manually. You could end up writing mapping code like this:

employeeDto.EmployeeId = currentEmployee.EmployeeId
employeeDto.UserId = currentEmployee.UserId
employeeDto.Department = currentEmployee.Department
etc.

If you have a simple, flat object that has just value types (e.g. int, strings) with no complex objects (e.g. arrays, generic lists or reference objects) within, then you can simply use a “Shallow Copy” (also known as a “Memberwise Clone”) of the properties between the 2 objects.

For example:

/// 
/// Shallow Property Example
/// 
/// 
/// 
public static void CopyPropertyValues(object source, object destination)
{
    var destProperties = destination.GetType().GetProperties();
    foreach (var sourceProperty in source.GetType().GetProperties())
    {
        foreach (var destProperty in destProperties)
        {
            if (destProperty.Name == sourceProperty.Name &&
        destProperty.PropertyType.IsAssignableFrom(sourceProperty.PropertyType))
            {
                destProperty.SetValue(destination, sourceProperty.GetValue(
                    source, new object[] { }), new object[] { });

                break;
            }
        }
    }
}


If (as in most situations, your objects have child collections) e.g like a Purchase Order would have Purchase order items, then the Shallow copy approach will not work. If you have reference objects, it will actually just copy the references themselves – so the original object and the cloned object effectively have the same references and become the same object. As the references are the same, changes to the "cloned" object will be reflected in the original object - which is typically NOT what you want!

To solve this problem, you need to perform a Deep copy of your objects that will in fact traverse your object graph (ie full structure/hierarchy of your objects) to copy all properties and “subproperties” between the 2 different objects (of different types).

Deep Copy – for Same Object Type
There are approaches involving serialization and the IClonable interface (see http://en.csharp-online.net/ICloneable) for details – but they only work within the bounds of a single object type (ie they can’t map/translate the properties between 2 different types) – plus it only works with Serializable types:

(Sample taken from here http://www.codeproject.com/KB/tips/SerializedObjectCloner.aspx)

using System;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;     
      
/// 
/// Provides a method for performing a deep copy of an object.
/// Binary Serialization is used to perform the copy.
/// 
public static class ObjectCopier
{
 /// 
 /// Perform a deep Copy of the object.
 /// 
 /// The type of object being copied.
 /// 
The object instance to copy./// The copied object.
 public static T Clone(T source)
 {
  if (!typeof(T).IsSerializable)
  {
    throw new ArgumentException("The type must be serializable.", "source");
  }
  
  // Don't serialize a null object, simply return the default for that object
  if (Object.ReferenceEquals(source, null))
  {
    return default(T);
  }

  IFormatter formatter = new BinaryFormatter();
  Stream stream = new MemoryStream();
  using (stream)
  {
    formatter.Serialize(stream, source);
    stream.Seek(0, SeekOrigin.Begin);
    return (T)formatter.Deserialize(stream);
  }
 }
}

Another sample is here (also only works for deep copies between the same object types)
http://ox.no/posts/extension-methods-for-copying-or-cloning-objects

So for a deep copy approach for (which is what we need for most projects), you can do the following:
• Use Reflection to navigate through the object graph and copy each property one by one (rather than just copying the pointers/references as in the Shallow copy approach) This is a tried and trusted approach, but the performance (as it is using reflection) is not as good as the Expression approach.
• Use Reflection and Emit of IL Code/Expressions (only with .NET 3.5) to perform the copy - this performs better when there are a large number of runs as it runs compiled after the first run (after the initial reflection generates the code) - example can be found here - http://whizzodev.blogspot.com/2008/06/object-deep-cloning-using-il-in-c_20.html
• Use a 3rd Party utility library (which typically use the approaches above) – such as Automapper (http://automapper.codeplex.com/)

I’ve provided samples/details of the Reflection approach below:

Deep Copy Using Reflection, Supporting Different Object Types
(Sample from http://stackoverflow.com/questions/569154/how-to-deep-copy-between-objects-of-different-types-in-c-net)

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

namespace DDKOnline.Common
{
    /// 
    /// Utility 
    /// 
    public static class DeepCopyUtility
    {

        ///  
        /// Copies the data of one object to another. The target object gets properties of the first.  
        /// Any matching properties (by name) are written to the target. 
        ///  
        /// 
The source object to copy from/// 
The target object to copy topublic static void CopyObjectData(object source, object target)
        {
            CopyObjectData(source, target, String.Empty, BindingFlags.Public | BindingFlags.Instance);
        }

        ///  
        /// Copies the data of one object to another. The target object gets properties of the first.  
        /// Any matching properties (by name) are written to the target. 
        ///  
        /// 
The source object to copy from/// 
The target object to copy to/// 
A comma delimited list of properties that should not be copied/// 
Reflection binding accesspublic static void CopyObjectData(object source, object target, string excludedProperties, BindingFlags memberAccess)
        {
            string[] excluded = null;
            if (!string.IsNullOrEmpty(excludedProperties))
            {
                excluded = excludedProperties.Split(new char[1] { ',' }, StringSplitOptions.RemoveEmptyEntries);
            }

            MemberInfo[] miT = target.GetType().GetMembers(memberAccess);
            foreach (MemberInfo Field in miT)
            {
                string name = Field.Name;

                // Skip over excluded properties 
                if (string.IsNullOrEmpty(excludedProperties) == false
                    && excluded.Contains(name))
                {
                    continue;
                }


                if (Field.MemberType == MemberTypes.Field)
                {
                    FieldInfo sourcefield = source.GetType().GetField(name);
                    if (sourcefield == null) { continue; }

                    object SourceValue = sourcefield.GetValue(source);
                    ((FieldInfo)Field).SetValue(target, SourceValue);
                }
                else if (Field.MemberType == MemberTypes.Property)
                {
                    PropertyInfo piTarget = Field as PropertyInfo;
                    PropertyInfo sourceField = source.GetType().GetProperty(name, memberAccess);
                    if (sourceField == null) { continue; }

                    if (piTarget.CanWrite && sourceField.CanRead)
                    {
                        object targetValue = piTarget.GetValue(target, null);
                        object sourceValue = sourceField.GetValue(source, null);

                        if (sourceValue == null) { continue; }

                        if (sourceField.PropertyType.IsArray
                            && piTarget.PropertyType.IsArray
                            && sourceValue != null)
                        {
                            CopyArray(source, target, memberAccess, piTarget, sourceField, sourceValue);
                        }
                        else
                        {
                            CopySingleData(source, target, memberAccess, piTarget, sourceField, targetValue, sourceValue);
                        }
                    }
                }
            }
        }

        private static void CopySingleData(object source, object target, BindingFlags memberAccess, PropertyInfo piTarget, PropertyInfo sourceField, object targetValue, object sourceValue)
        {
            //instantiate target if needed 
            if (targetValue == null
                && piTarget.PropertyType.IsValueType == false
                && piTarget.PropertyType != typeof(string))
            {
                if (piTarget.PropertyType.IsArray)
                {
                    targetValue = Activator.CreateInstance(piTarget.PropertyType.GetElementType());
                }
                else
                {
                    targetValue = Activator.CreateInstance(piTarget.PropertyType);
                }
            }

            if (piTarget.PropertyType.IsValueType == false
                && piTarget.PropertyType != typeof(string))
            {
                CopyObjectData(sourceValue, targetValue, "", memberAccess);
                piTarget.SetValue(target, targetValue, null);
            }
            else
            {
                if (piTarget.PropertyType.FullName == sourceField.PropertyType.FullName)
                {
                    object tempSourceValue = sourceField.GetValue(source, null);
                    piTarget.SetValue(target, tempSourceValue, null);
                }
                else
                {
                    CopyObjectData(piTarget, target, "", memberAccess);
                }
            }
        }

        private static void CopyArray(object source, object target, BindingFlags memberAccess, PropertyInfo piTarget, PropertyInfo sourceField, object sourceValue)
        {
            int sourceLength = (int)sourceValue.GetType().InvokeMember("Length", BindingFlags.GetProperty, null, sourceValue, null);
            Array targetArray = Array.CreateInstance(piTarget.PropertyType.GetElementType(), sourceLength);
            Array array = (Array)sourceField.GetValue(source, null);

            for (int i = 0; i < array.Length; i++)
            {
                object o = array.GetValue(i);
                object tempTarget = Activator.CreateInstance(piTarget.PropertyType.GetElementType());
                CopyObjectData(o, tempTarget, "", memberAccess);
                targetArray.SetValue(tempTarget, i);
            }
            piTarget.SetValue(target, targetArray, null);
        } 

    }
}

Monday, 3 November 2008

Using Attributes, Reflection and GetCustomAttributes() to support field mappings between systems

My task today was to generate MS word documents using Aspose.Word and merge fields. The problem was, the names of the merge fields did not match exactly with the names of the properties of my objects - and I didn't want to map all fields in my objects to the external components in a separate method call. I approached this with a mechanism I have used several times before - using CustomAttributes and Reflection to drive the property mappings between application entities and external components such as Documents or Emails. See below for details on this. Note the use of the GetCustomAttributes() for retrieving custom attributes on your properties.

The attribute class:



/// <summary>
/// Supports the mapping between Aspose.Word Merge fields and the Fields
/// in the Data Transfer Object
/// </summary>
[AttributeUsage(AttributeTargets.All)]
public class DocumentMergeFieldMappingAttribute : System.Attribute
{
public string MergeFieldName { get; set; }

public DocumentMergeFieldMappingAttribute(string mergeFieldName)
{
MergeFieldName = mergeFieldName;
}
}


The mapping class which builds up a Dictionary (name, value pairs)


/// <summary>
/// Gets dictionary of field values for merge into documents - uses the
/// DocumentMergeFieldMappingAttribute to determine which dto properties
/// should render to the Document itself.
/// </summary>
/// <param name="inputDto"></param>
/// <returns></returns>
private IDictionary<string, IFieldValueDto> GetDtoFieldValues(object inputDto)
{
Dictionary<string, IFieldValueDto> dictionary = new Dictionary<string, IFieldValueDto>();

Type objectType = inputDto.GetType();
PropertyInfo[] properties = objectType.GetProperties();

foreach(PropertyInfo property in properties)
{
foreach (Attribute attribute in property.GetCustomAttributes(true))
{
if (attribute is DocumentMergeFieldMappingAttribute)
{
string fieldValue = property.GetValue(inputDto, null) as string ?? string.Empty;
//Set property value and the mapped document field name
dictionary.Add(((DocumentMergeFieldMappingAttribute)attribute)
.MergeFieldName, new FieldValueDto<string>(fieldValue));
}
}
}
return dictionary;
}

Tuesday, 28 October 2008

How do I get the underlying type of a Generic List?

The Type.GetGenericArguments() method will let you determine the underlying type of the elements of a list object at runtime. See this article for more information.

http://msdn.microsoft.com/en-us/library/system.type.getgenericarguments.aspx

For example:


if (t.IsGenericType)
{
// If this is a generic type, display the type arguments.
//
Type[] typeArguments = t.GetGenericArguments();
Console.WriteLine("\tList type arguments ({0}):", typeArguments.Length);
    foreach (Type tParam in typeArguments)
{
// If this is a type parameter, display its
// position.
//

        if (tParam.IsGenericParameter)
{
Console.WriteLine("\t\t{0}\t(unassigned - parameter position {1})",tParam,
tParam.GenericParameterPosition);
}
else
{
Console.WriteLine("\t\t{0}", tParam);
}
}
}

Wednesday, 19 March 2008

Reflection - using GetType(string) to create an instance of a type is returning null

My colleague "CS" today had issues with resolving types in our Lookup Service. The problem: he was trying to use GetType(string) to resolve a type that was in another assembly. e.g. Type sourceType = GetType(string.Format("DDK.Common.Lookups{0}",lookupTypeName))

Problem was that it couldn't resolve the name - even though the dll was referenced and the calling assembly could resolve the name if it needed to.

This article is much better than MSDN in explaning the issue http://blogs.msdn.com/haibo_luo/archive/2005/08/21/454213.aspx. In attempting to resolve the type, GetType() will first look in the current calling assembly, then in mscorlib. It will NOT trawl through all your references in the bin directory for you like it does with normal type resolution - it will just give up if it is not in mscorlib or the current assembly and return a whopping great null.

Instead, you have to give the .NET runtime a hand and tell it which assembly to look in. This is why you have to do the same thing when specifying dynamically loaded types in the web.config - you have to put the fully qualified names so the runtime it can resolve the type with the same GetType(string) mechanism. The simplest way to find this out is to just look at the project properties to find the name of the output assembly name. Alternatively, you can make an instance of your object e.g. new DDK.Common.Lookups.Country().GetType().AssemblyQualifiedName to get the full name that the runtime uses to uniquely identify the type.

In our case, changing the code to include the red (ie the AssemblyQualifiedName minus any version numbers did the trick). ie
Type sourceType = GetType(string.Format("DDK.Common.Lookups.{0}, DDK.Common.Lookups",lookupTypeName))


Friday, 16 November 2007

The simple way to parse strings to GUIDs in C# or VB.NET

While the religious debate rages between whether we should be using GUIDs as primary keys rages, you can use the Parse method of the System.SqlTypes.SQLGuid class to parse query strings to GUIDs. Here's an example:

return System.Data.SqlTypes.SqlGuid.Parse(Request.QueryString["ParticipantGuid"]).Value;

Wednesday, 7 November 2007

The power of Lambda Functions

(Tip thanks to my mate Eric Phan):

Lambda functions are essentially anonymous functions (in-line functions) that you can write instead of creating a new method to perform this once off operation.

A good example is filtering a list

I have a list of customers and I want to know who is more than 25 years old and female.
Traditionally (using a filtering method):


   1:  static void Main(string[] args)

   2:  {

   3:     List<Customer> customers = GetAListOfCustomer();

   4:     List<Customer> matureFemaleCustomers = FindMatureFemaleCustomers(customers);

   5:  } 

   6:   

   7:  // Find female customers over 25 years old 

   8:  public List<Customer> FindMatureFemaleCustomers(List<Customer> customers)

   9:  {

  10:     List<Customer> matureFemaleCustomers =new List<Customer>();

  11:     foreach (Customer c in customers)

  12:     {

  13:        if (c.Age > 25 && c.Gender == Gender.Female)

  14:        {

  15:           matureFemaleCustomers.Add(c);

  16:        }

  17:     }

  18:     return matureFemaleCustomers;

  19:  } 


In C# 2.0 with generics:



   1:  static void Main(string[] args){ List<customer> customers = GetAListOfCustomer();

   2:  List<customer> matureFemaleCustomers = customers.FindAll( delegate(Customer c) { return c.Age > 25 && c.Gender == Gender.Female; } );}



In C# 3.0 with Lambda Functions



   1:  static void Main(string[] args) 

   2:  { List customers = GetAListOfCustomer(); 

   3:    List matureFemaleCustomers = customers.Where(c => c.Age > 25 && c.Gender == Gender.Female);

   4:  } 





Simple and elegant!

Tuesday, 23 October 2007

C# Coalesce

The ?? coalesce operator checks whether the value on the left hand side of the expression is null, if so it returns the value on the right hand side. If the value on the left hand side is not null, then the left hand side value is returned:

string message = "Hi Devs";
string result = message ?? "It was null!";

// Outcome: result == "Hi Devs"


string message = null;
string result = message ?? "It was null!";

// Outcome: result == "Message was null"


Note that this is a short-circuited operator. For example:

a = b ?? c;
translates to:
a = ( b == null ? c : b);
Which means the expression c is only evaluated if b is null. There is no equivalent in VB (short of performing an iif())

Tuesday, 9 October 2007

Converting Arrays without for...loop construct

.NET has built-in functionality to convert one collection or array of one type (e.g. List<typename>) to another collection or array of another type. You don't need a for loop to do this at all. Here's how:

(from http://gsharper.blogspot.com/2007/04/convert-integer-array-to-string-array.html)

private void TestMethod(int[] intArray)
{
string[] stringArray = Array.ConvertAll (intArray,new Converter (ConvertIntToString)); string result = string.Join(",", stringArray);
}

private string ConvertIntToString(int intParameter)
{
return intParameter.ToString();
}