Wednesday, February 9, 2011

LINQ To LDAP: How Does It Work Part 2.6

This will be a quick post. I know I said my next post would be about querying, but I completely forgot about attribute based mapping so I want to cover that first.

So here's my class from before but now with attributes:
[DirectorySchema("CN=Users,CN=Employees,DC=Northwind,DC=local", "Person")]
public class User
{
    [DirectoryAttribute]
    public string DistinguishedName { get; set; }

    [DirectoryAttribute("badpwdcount")]
    public int BadPasswordCount { get; set; }

    [DirectoryAttribute("directreports")]
    public string[] Employees { get; set; }

    [DirectoryAttribute]
    public string Title { get; set; }

    [DirectoryAttribute]
    public string PostalCode { get; set; }

    [DirectoryAttribute("cn")]
    public string CommonName { get; set; }

    [DirectoryAttribute]
    public DateTime? WhenCreated { get; set; }

    [DirectoryAttribute("givenname")]
    public string FirstName { get; set; }

    [DirectoryAttribute("objectguid")]
    public Guid Guid { get; set; }

    [DirectoryAttribute("l")]
    public string City { get; set; }

    [DirectoryAttribute("usnchanged")]
    public int Version { get; set; }

    [DirectoryAttribute("c")]
    public string Country { get; set; }

    [DirectoryAttribute("whenchanged")]
    public DateTime? LastChanged { get; set; }

    [DirectoryAttribute("objectsid")]
    public byte[] Sid { get; set; }

    [DirectoryAttribute("employeeid")]
    public long EmployeeId { get; set; }

    [DirectoryAttribute("telephonenumber")]
    public string PhoneNumber { get; set; }

    [DirectoryAttribute]
    public string Street { get; set; }

    [DirectoryAttribute]
    public string Comment { get; set; }

    [DirectoryAttribute]
    public string Name { get; set; }

    [DirectoryAttribute("sn")]
    public string LastName { get; set; }
}

And here's how I added support:
public static bool HasDirectorySchema(this Type type)
{
    var attributes = type.GetCustomAttributes(typeof (DirectorySchemaAttribute), true);
    return attributes != null && attributes.Length > 0;
}

internal static IObjectMapping Map<T>(string namingContext, string objectCategory) where T : class
{
    lock (Mappings)
    {
        var mapping = GetMapping<T>() ??
                        (typeof (T).HasDirectorySchema()
                            ? Map(new AttributeClassMap<T>())
                            : Map(new AutoClassMap<T>(namingContext, objectCategory)));

        return mapping;
    }
}

public class AttributeClassMap<T> : ClassMap<T> where T : class 
{
    public AttributeClassMap()
    {
        var type = typeof(T);
        var schemaAttribute = type
            .GetCustomAttributes(typeof(DirectorySchemaAttribute), true)
            .Cast<DirectorySchemaAttribute>().First();

        NamingContext(schemaAttribute.NamingContext);
        ObjectCategory(schemaAttribute.ObjectCategory);

            
        var properties = type.GetProperties(Flags)
            .Where(p => p.GetCustomAttributes(typeof(DirectoryAttributeAttribute), true).Any() &&
                        p.GetGetMethod() != null && p.GetSetMethod() != null)
            .Select(p => 
                    new KeyValuePair<DirectoryAttributeAttribute, PropertyInfo>(
                        p.GetCustomAttributes(typeof (DirectoryAttributeAttribute), true)
                             .Cast<DirectoryAttributeAttribute>().FirstOrDefault(), 
                        p));

        properties.ToList().ForEach(p => Map(p.Value).Named(p.Key.AttributeName));
    }
}

Attribute mapping is kind of the bridge between auto-mapping and class mapping. You can query like with class mapping, but without creating a separate class that maps your properties.
using (var context = new DirectoryContext())
{
    var user = context.Query<User>()
        .FirstOrDefault(u => u.CommonName == "Alan Hatter");
}