public sealed class DynamicSearchResultAttributeDictionary : DynamicObject, IDictionary<string, object>
{
    private readonly SearchResultAttributeCollection _collection;
    private SearchResultAttributeDictionary _dictionary;
    public DynamicSearchResultAttributeDictionary(SearchResultAttributeCollection collection)
    {
        _collection = collection;
    }
    private SearchResultAttributeDictionary Dictionary
    {
        get 
        { 
             return _dictionary ?? 
                   (_dictionary = new SearchResultAttributeDictionary(_collection)); 
        }
    }
    public override IEnumerable<string> GetDynamicMemberNames()
    {
        return Keys;
    }
    public override bool TryGetMember(GetMemberBinder binder, out object result)
    {
        Dictionary.TryGetValue(binder.Name, out result);
        return true;
    }
    //IDictionary<string, object> implementation down here
}
public class SearchResultAttributeDictionary : Dictionary<string, object>
{
    public SearchResultAttributeDictionary(SearchResultAttributeCollection collection)
        : base(collection == null ? 0 : collection.Count, StringComparer.InvariantCultureIgnoreCase)
    {
        if (collection == null) return;
        foreach (var attribute in collection.AttributeNames.Cast<string>())
        {
            var value = collection[attribute];
            if (value.Count == 1)
            {
                Add(attribute, value[0]);
            }
            else if (value.Count > 1)
            {
                var type = value[0].GetType();
                Add(attribute, value.GetValues(type));
            }
            else
            {
                Add(attribute, null);
            }
        }
    }
}
With this implementation I can provide dynamic members that are NOT case sensitive and will NOT throw an exception if they do not exist. I also implemented IDictionary<string, object> so you can still access it that way.
So here's the slightly newer hotness way to query:
IQueryable<dynamic> query = context
        .Query("CN=Users,CN=Employees,DC=Northwind,DC=local")
        .Select("cn", "givenname")
        .FilterWith("(cn=Andrew Fuller)");
//Dynamic
dynamic user = query.FirstOrDefault();
Console.Write(user.cn)
Console.Write(user.GIVENNAME)
Console.Write(user["cn"])
Console.Write(user["GIVENNAME"])
//Typed Dictionary
IDictionary<string, object> dictionary = query.FirstOrDefault();
Console.Write(dictionary["cn"])
Console.Write(dictionary["GIVENNAME"])
All in all, this was a pretty fun and relatively easy feature (no one asked for it, but it felt natural given the key-value nature of LDAP).
Now that this unexpected detour is out of the way, here's what's shaping up for version 1.5:
- New property maping support for date times with any format in the directory (file time or some other).
- Enum mapping (string or int)
- GetByDN and GetByCN methods to go to a specific distinguished name in the directory and load the attributes (faster if you know exactly what you're looking for since a Searchscope.Base is used).
- Dynamics of course!
I'll try to have this out next week but no promises. This project comes 100% from my spare time and I'm short on that lately.
 
No comments:
Post a Comment