Tuesday, December 27, 2011

LINQ To LDAP: Visiting the Locals

I recently ran into a problem with how LINQ to LDAP's expression visitor was working. This expression:
source.Where(u=> objectGuid.HasValue && u.objectGuid == objectGuid.Value)
was throwing a NullReferenceException. The problem was related to that first condition. The visit constant for QueryTranslator was making an assumption that for each constant it found there would be a corresponding member access.

My hackish solution was simply to ignore constants that don't have a corresponding property. Well that worked, but I saw two problems. First it created unnecessarily complex filters (& and | where one wasn't necessary). The second issue was the InvalidOperationException caused when .Value was called for a nullable without a value.

So fast forward several days of on and off coding (Christmas is for family and coding!) and I think I have a solution. I already borrowed the SubtreeEvaluator so I just needed to create a visitor that would rewrite only boolean constants. Well, I ended up creating two. One to rewrite and then another to remove redundant and unnecessary values. When I finish refactoring, you'll be able to find the BooleanRewriter and BooleanReducer in the Visitors folder.
//if objectGuid doesn't have a value the expression will reduce to:
source.Where(u=> false)

//if objectGuid has a value then the expression will reduce to:
source.Where(u=> u.objectGuid == objectGuid.Value)

Tuesday, December 13, 2011

SharePoint as a document database

No, not the No-SQL kind. I'm nearing completion of a project at work where our internal customer wanted a central repository for their documents. The catch? SharePoint tends to be a bad word among most users. Thankfully SharePoint 2010 ships with a nice RESTful service exposed via ListData.svc so we can leverage SharePoint without anyone really knowing about it.

Following Corey Roth's blog post and this MSDN article, I was able to easily query for documents using LINQ. You even get an index back, but I had to parse it from the server response when adding the document since it gives you the full location as "http://<sharepoint-server>/<site>/_vti_bin/ListData.svc/<ListName>(<Index>)". A lot of this is off the top of my head (sorry, can't really post code samples from my work code):
var service = new MyListDataService(new Uri("http://<sharepoint-server>/<site>/_vti_bin/ListData.svc"))
{
     Credentials = CredentialCache.DefaultCredentials
};

string path = "/<site>/<ListName>/doc.txt";
string contentType = "plain/text";

DocumentItem item = new DocumentItem()
{
    ContentType = contentType,
    Name = "doc.txt",
    Path = path,
    Title = "doc"
};
            
service.AddToDocuments(item);

byte[] data = //wherever the raw file data comes from

service.SetSaveStream(item, data, false, contentType, path);

var response = service.SaveChanges();

int id = //code to parse the index from the Location header of the response

var document = service.Documents.FirstOrDefault(d => d.Id == id);

So I can save and find the document, but how do I edit it? That was a little trickier. My initial idea was just to use Process.Start on the url for the document (this is a WPF app by the way), but for some reason SharePoint defaults to read only when you do that. Nick Grattan has a great solution that I modified to use dynamics since it works so well when you have to bust out some COM code.
var docUrl = "http://<sharepoint-server>/<site>/<ListName>/doc.txt";
Type t = Type.GetTypeFromProgID("SharePoint.OpenDocuments.3");
dynamic sharePointControl = Activator.CreateInstance(t);

sharePointControl.EditDocument(docUrl, string.Empty);

And voilĂ ! Now I can store meta data about the document in my app database, retrieve it from SharePoint and get the power of locking and versioning! I wish I could take credit for the idea, but my boss recommended it. I just thought I'd share the adventure of trying to make it work.

Sunday, December 4, 2011

LINQ To LDAP: Configuration

There have been more than a few questions about the use of static properties within LdapConfiguration. This was intended for simplicity, but it's always nice to have an option for more complex applications.

With the recent changes you now can control the lifetime of your configuration as well as have multiple configurations within a project. Configurations are self contained with their own DirectoryMapper and ConnectionFactory. You can still choose to have a global configuration by calling UseStaticStorage on LdapConfiguration.
var config = new LdapConfiguration()
    .AddMapping(new AttributeClassMap<User>())
    .MaxPageSizeIs(500)
    .LogTo(new SimpleTextLogger(Console.Out))
    .UseStaticStorage();

config.ConfigurePooledFactory("localhost")
    .MaxPoolSizeIs(50)
    .MinPoolSizeIs(0)
    .ProtocolVersion(3);

//will look for the static Configuration property on LdapConfiguration
using (var context = new DirectoryContext())
{
}

//the next two statements construct a DirectoryContext in the same manner
using (var context = config.CreateContext())
{
}

using (var context = new DirectoryContext(config))
{
}

//will create a LdapConfiguration local to this instance.  
//As a result, no mappings can be cached beyond the lifetime 
//for this DirectoryContext.
using (var context = new DirectoryContext(new LdapConnection("localhost"), true))
{
}

Monday, November 28, 2011

LINQ To LDAP: Logging

I've been working on logging in LINQ to LDAP recently. I added logging support for all requests as well as any errors that occur. I created an interface so that you can integrate your own logging providers.
public interface ILinqToLdapLogger
{
    bool TraceEnabled { get; }

    void Trace(string message);

    void Error(Exception ex, string message = null);
}
Trace is meant for logging query information. I check TraceEnabled before writing information via Trace. Error is always called when exception is encountered. I wasn't sure if I should add logging hooks for error since most projects will handle errors within their own applications, but I figured you could simply ignore calls to Error in your own implementations.

Out of the box I included a basic implementation:
public class SimpleTextLogger : ILinqToLdapLogger
{
    private readonly TextWriter _textWriter;

    public SimpleTextLogger(TextWriter textWriter)
    {
        _textWriter = textWriter;
        TraceEnabled = true;
    }

    public bool TraceEnabled { get; set; }

    public void Trace(string message)
    {
        _textWriter.WriteLine(message);
    }

    public void Error(Exception ex, string message = null)
    {
        if (message != null) _textWriter.WriteLine(message);
        ObjectDumper.Write(ex, 0, _textWriter);
    }
}
This is mainly used for debugging support, but it also serves as an example implementation. Here's another example using Common.Logging .Net which offers a lot more support for different logging frameworks:
public class CommonLogger : ILinqToLdapLogger
{
    private ILog _errorLog;
    private ILog _traceLog;

    public CommonLogger()
    {
        _traceLog = LogManager.GetLogger("trace");
        _errorLog = LogManager.GetLogger("error");
    }

    public void Trace(string message)
    {
        _traceLog.Trace(message);
    }

    public void Error(Exception ex, string message = null)
    {
        _errorLog.Error(message, ex);
    }

    public bool TraceEnabled
    {
        get { return _traceLog.IsTraceEnabled; }
    }
}
You can attach a log directly to a DirectoryContext or through the LdapConfiguration so it can be used with the ConncectionFactory as well. I'll go into more detail in my next post about the LdapConfiguration changes.
var context = new DirectoryContext() 
{
     Logger = new SimpleTextLogger(Console.Out)
};

var config = new LdapConfiguration()
     .LogTo(new SimpleTextLogger(Console.Out));

//will inject the logger for you
context = config.CreateContext();

Friday, November 11, 2011

LINQ To LDAP: NuGet

LINQ to LDAP 2.0.2 has been released over at codeplex. I've also finally made the jump to NuGet. Happy day!

Thursday, November 10, 2011

LINQ To LDAP: More Control

Currently paging and sorting controls will automatically be created for the by the framework. That's nice for most situations, but sometimes you need a little more control.

I added a WithControls extension method that allows you to specify as many additional DirectoryControls as necessary.

List<User> list = context.Query<User>()
    .WithControls(new DirectoryControl[]
                    {
                        new PageResultRequestControl(100)
                            {
                                IsCritical = true
                            },
                        new SortRequestControl(new[]{new SortKey("cn", null, true)})
                            {
                                IsCritical = true
                            },
                        new ShowDeletedControl
                            {
                                IsCritical = true
                            }
                    })
    .ToList();

If you try to perform an OrderBy or ToPage operation while specifying your own via WithControls then an InvalidOperationException will be thrown.

One cool thing here is behind the scenes I still check if you're requesting a page so you can cast the resulting list as a LdapPage and get the cookie for the next page. And that's about it.

Wednesday, November 9, 2011

LINQ To LDAP: Closing in on 2.0

It's been a while since I've posted. I moved across town, took a vacation, and have just been generally busy. But I'm back and with a few updates about 2.0.

Since the last time I posted, I've made a few API changes and added quite a bit more mapping support. One thing I want to talk about is some tweaks to paging. I've covered ToPage and PageAll in a previous post. PageAll, however, is being deprecated in version 2.0. In it's place you can do this:
//will page all results when enumerated based on the server max page size from LdapConfiguration
context.Query<User>();

//will create a page request for 10 results and stop there
context.Query<User>()
    .Take(10);

//will page all the results in groups of 50
context.Query<User>()
    .InPagesOf(50);

//will create a page request for 2 entries at a time up to a maximum of 10 results
context.Query<User>()
    .Take(10)
    .InPagesOf(2);

In cases when the take size is smaller than the page size, take size will be used.

This is part of the trunk, but is not in the 2.0 beta 2 release. I've updated most of the documentation so you can see a lot of the new stuff over at the codeplex page. I'll also be adding this project to Nuget soon.

Thursday, August 25, 2011

LINQ To LDAP: Hello DirectoryAttributes!

I had a very pleasant surprise when testing my DirectoryAttributes implementation. The biggest driving force for creating LINQ TO LDAP was simplifying the querying process. With dynamics, you had to create your filters manually which is nice for simple scenarios but can get messy pretty quickly.

Since I've gone back to a static type, I'm pleased to say this query works perfectly:
var query = context.Query(namingContext, objectClass: "Person")
    .Where(da => (Filter.Equal(da, "givenname", "Andrew") && Filter.Equal(da, "sn", "Fuller")) || 
        Filter.Equal(da, "cn", "Andrew Fuller"))
    .Select(da => new 
                     {
                          Guid = da.GetGuid("objectguid"), 
                          EmployeeId = da.GetInt("employeeid")
                     });

var user = query.FirstOrDefault();

//Produces this filter:
(&(objectClass=Person)(|(&(givenname=Andrew)(sn=Fuller))(cn=Andrew Fuller)))
Attributes: objectguid, employeeid
Projections are supported as well. This will look at the parameters for the Get methods so your anonymous object properties can be called anything.

The Filter class still only supports Equal and Approximately so I'll need to fill it out with the other operations. Happy coding!

Wednesday, August 24, 2011

LINQ To LDAP: Bye Bye Dynamics

Well, it was a fun ride while it lasted. Given the performance penalty for blindly accessing byte arrays and the guessing game that I have to handle to avoid it, it only makes sense to peel away the abstraction and give control back to the user.

So in place of dynamics I created IDirectoryAttributes and DirectoryAttributes. If you've ever worked with IDataReader, it will feel very familiar. Here are the get methods for the interface:
string DistinguishedName { get; }

object GetValue(string attribute);

DirectoryAttribute Get(string attribute);

byte[] GetBytes(string attribute);

string[] GetStrings(string attribute);

string GetString(string attribute);

byte? GetByte(string attribute);

int? GetInt(string attribute);

long? GetLong(string attribute);

double? GetDouble(string attribute);

decimal? GetDecimal(string attribute);

short? GetShort(string attribute);

float? GetFloat(string attribute);

bool? GetBoolean(string attribute);

DateTime? GetDateTime(string attribute, string format = "yyyyMMddHHmmss.0Z");

Guid? GetGuid(string attribute);

SecurityIdentifier GetSecurityIdentifier(string attribute);

IEnumerable<byte[]> GetByteArrays(string attribute);

Pretty standard. Attribute is case insensitive. If it's not found then null will be returned. Since everything is either a string or byte array from the directory, if it fails to convert then a FormatException will be thrown. GetValue will try to guess the type like before and is only here to support the implementation of IEnumerable<string, object>. Of course if there's a datatype here that's missing, just use Get and you'll get direct access to the DirectoryAttribute.

So what about change tracking? Since DirectoryAttributes is just a wrapper for SearchResultEntry, setting values is tracked in an internal dictionary. Just call SetValue(attributeName, value) and it will take care of the rest.
var factory = new LdapConnectionFactory("localhost");
string namingContext = "CN=Users,CN=Employees,DC=Northwind,DC=local";

using (IDirectoryContext context = new DirectoryContext(factory.GetConnection(), true))
{
    var query = context.Query(namingContext, objectClass: "Person")
        .Where("cn=Andrew Fuller")
        .Select("cn");

    var user = query.FirstOrDefault();

    user.SetValue("employeeid", 1);

    var updated = context.Update(user);

    Console.WriteLine(updated.GetInt("employeeid"));
    Console.WriteLine(updated.GetGuid("objectguid"));
}

That's it. I hope this helps with performance and satisfies anyone who can't / won't map classes.

Sunday, August 14, 2011

LINQ To LDAP: CR(U)D

So now that I've covered Adding entries, let's talk about updating. Updating your entries is a little different than in a RDBMS.

Using S.DS.P directly you can make modifications to an entry like so:
var firstNameMod = new DirectoryAttributeModification
                        {
                            Name = "givenname",
                            Operation = DirectoryAttributeOperation.Replace,

                        };
firstNameMod.Add("Jack");

var commentMod = new DirectoryAttributeModification
                        {
                            Name = "comment",
                            Operation = DirectoryAttributeOperation.Add,

                        };
commentMod.Add("add a property to this entry");

var dateOfBirthMod = new DirectoryAttributeModification
                        {
                            Name = "dateofbirth",
                            Operation = DirectoryAttributeOperation.Delete,

                        };

ModifyRequest request = new ModifyRequest("CN=John Doe,CN=Users,CN=Employees,DC=Northwind,DC=local", new [] {firstNameMod, commentMod, dateOfBirthMod });

ldapConnection.SendRequest(request);

So what you see here is a request to modify John Doe by changing his given name to Jack, adding a brand new attribute called comment, and deleting his date of birth. Those last two modification will actually make a change to the structure of the object. So how do you do this with LINQ To LDAP? Replace (updating) operations are pretty easy, but I separate adding or removing attributes into different methods because it's difficult to identify the intention (i.e. are you trying to delete an attribute because you set it to null).

I'll continue with my previous User class example, but with a few modifications for change tracking. The primary change is sub typing DirectoryObjectBase (built into LINQ to LDAP) and changing your setters to look like this:
set
{
     _firstName = value;
     AttributeChanged("FirstName");
}

Similar to INotifyPropertyChanged, just call the AttributeChanged when the property changes. However, I don't use the interface because I didn't want to worry about events subscriptions. But don't feel like you have to use DirectoryObjectBase. LINQ To LDAP will work just as well without it.

Change tracking on entries is enabled when you use either a full projection ( Select(u => u) ), no projection or GetByDN. Since I support projections of mapped entries, I had to disable it so you wouldn't accidentally update attributes to an empty value. If your object sub types DirectoryObjectBase and you try to update it without change tracking being enabled, then I actually throw an exception. For dynamic queries, change tracking is already built in so you don't have to do anything special.

So let's update!
var factory = new LdapConnectionFactory("localhost");

//mapped
using (var context = new DirectoryContext(factory.GetConnection(), true))
{
    var user = context.GetByDN<User>("CN=John Doe,CN=Users,CN=Employees,DC=Northwind,DC=local");
    user.FirstName = "Jack";
    context.Update(user);
}

//dynamic
using (var context = new DirectoryContext(factory.GetConnection(), true))
{
    dynamic user = context.GetByDN("CN=John Doe,CN=Users,CN=Employees,DC=Northwind,DC=local", "givenname");
    user.givenname = "Jack";
    context.Update(user.DistinguishedName, user);
}

//update structure
using (var context = new DirectoryContext(factory.GetConnection(), true))
{
    context.AddAttribute(
        "CN=John Doe,CN=Users,CN=Employees,DC=Northwind,DC=local", "comment", 
        "add a property to this entry");

    context.DeleteAttribute(
        "CN=John Doe,CN=Users,CN=Employees,DC=Northwind,DC=local", 
        "dateofbirth");
}

There's one more operation for updating. In order to change an entry's distinguished name or move it to a different location you have to issue a ModifyDN request. This kind of request looks like this:
//Move entry using raw S.DS.P
var dnRequest = new ModifyDNRequest
{
	DistinguishedName = "CN=John Doe,CN=Users,CN=Employees,DC=Northwind,DC=local"
	NewParentDistinguishedName = "CN=Deactivated Users,CN=Employees,DC=Northwind,DC=local",
	NewName = "CN=John Doe"
};

connection.SendRequest(dnRequest);

//Move entry using LINQ To LDAP
string newDn = directoryContext.MoveEntry(
	"CN=John Doe,CN=Users,CN=Employees,DC=Northwind,DC=local", 
	"CN=Deactivated Users,CN=Employees,DC=Northwind,DC=local");


//Rename entry using raw S.DS.P
var dnRequest = new ModifyDNRequest
{
	DistinguishedName = "CN=John Doe,CN=Users,CN=Employees,DC=Northwind,DC=local"
	NewParentDistinguishedName = "CN=Users,CN=Employees,DC=Northwind,DC=local",
	NewName = "CN=Jack Doe"
};

connection.SendRequest(dnRequest);

//Rename entry using LINQ To LDAP
string newDn = directoryContext.RenameEntry(
	"CN=John Doe,CN=Users,CN=Employees,DC=Northwind,DC=local", 
	"Jack Doe");

And that's all there is to it. One thing that you must know is there's no concept of transactions in LDAP (at least not yet) so if you want to update multiple entries, be aware that if it fails half-way through, there's no rollback.

LINQ To LDAP 2.0 Beta is out over at CodePlex!

Sunday, July 31, 2011

LINQ To LDAP: GetValues Is Your Friend

I was finally able to put some time towards tackling a strange bug in LINQ to LDAP. Given this directory structure:
  • Company\Sites\DIV_A\Accounts\users\Users 1       -> 387 users
  • Company\Sites\DIV_A\Accounts\users\Users 2       -> 303 users
  • Company\Sites\DIV_A\Accounts\users\...                -> 21 users
  • Company\Sites\DIV_A\Accounts\users           Total -> 711 users

This query would perform horribly (as long as 20 seconds) when querying for all entries:
string namingContext = "OU=Users,OU=Accounts,OU=DIV_A,OU=Sites,DC=company,DC=local";
var example = new
{
 DistinguishedName = "",
 Name = "",
 Cn = "",
 ObjectGuid = default(Guid),
 ObjectSid = default(SecurityIdentifier)
};

var query = context.Query(example, SearchScope.Subtree, namingContext, objectClass: "Person")
  .Select(u => u);

var users = query.PageAll(10000);

If I remove ObjectGuid and ObjectSid then performance goes back to normal. This led me to the code that accesses the attributes. I was using the default index accessor when I should have been using GetValues for multi-valued attributes:
SearchResultAttributeCollection collection = //code to get collection from entry
//bad for multi-valued attributes
object guid = collection["objectguid"][0];

//much better
object guid = collection["objectguid"].GetValues(typeof(byte[]))[0];

So what gives? Why does GetValues perform so much better? It's recommended in the Introduction to System.DirectoryServices.Protocols, but other than that the Internet is pretty quiet on the subject. I fired up reflector and it looks like it tries to parse the bytes to a string, which fails so it catches the exception and returns the bytes as is. The performance issue is being caused by an exception being thrown for every byte array that tries to be retrieved...ouch.

The way mapping is done in LINQ to LDAP makes it easy to fix this, but dynamic queries are a little trickier. The duct tape fix is to hard code a list of popular guid / sid names and check for those:
public class SearchResultAttributeDictionary : Dictionary<string, object="">
{
    private const string ObjectGuid = "objectguid";
    private const string ObjectSid = "objectsid";

    private static readonly Dictionary<string, string> ByteProperties =
        new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
            {
                {"objectguid", "objectguid"},
                {"objectsid", "objectsid"},
                {"Ibm-entryuuid", "Ibm-entryuuid"},
                {"guID", "guID"},
                {"Orclguid", "Orclguid"},
                {"Nsuniqueid", "Nsuniqueid"}
            };

    public SearchResultAttributeDictionary(SearchResultAttributeCollection collection)
        : base(StringComparer.OrdinalIgnoreCase)
    {
        if (collection == null) return;
        foreach (var attribute in collection.AttributeNames.Cast<string>())
        {
            var value = collection[attribute];
            if (value.Count == 1)
            {
                Add(attribute,
                    ByteProperties.ContainsKey(attribute) 
                        ? value.GetValues(typeof (byte[]))[0] 
                        : value[0]);
            }
            else if (value.Count > 1)
            {
                var type = value[0].GetType();
                Add(attribute, value.GetValues(type));
            }
            else
            {
                Add(attribute, null);
            }
        }
    }
}

This is the best solution I could come up with for now. If someone has a better one I'm open to suggestions.

Wednesday, July 27, 2011

LINQ To LDAP: (C)RUD

Just like any other data store you can create, update, and delete data in a directory.

Here's how you create entries using S.DS.P:

LdapConnection connection = new LdapConnection("localhost");

string distinguishedName = "CN=John Doe,CN=Users,CN=Employees,DC=Northwind,DC=local";
AddRequest request = new AddRequest(distinguishedName, "User");

request.Attributes.Add(new DirectoryAttribute("givenname", "John"));
request.Attributes.Add(new DirectoryAttribute("sn", "Doe"));
request.Attributes.Add(new DirectoryAttribute("employeeid", "1"));

connection.SendRequest(request);

Looks pretty straightforward. You give your entry a primary key (distinguished name), an object class, and then populate the attributes for the new entry and submit it to the directory.

So here's how you do it with mapped classes:
public abstract class DirectoryObject
{
    [DistinguishedName]
    public string DistinguishedName { get; set; }

    [DirectoryAttribute(StoreGenerated = true)]
    public DateTime? WhenChanged { get; set; }

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

    [DirectoryAttribute(StoreGenerated = true)]
    public DateTime? WhenCreated { get; set; }

    [DirectoryAttribute("objectguid", StoreGenerated = true)]
    public Guid Guid { get; set; }

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

[DirectorySchema(NamingContext, ObjectCategory = "Person", ObjectClass = "user")]
public class User : DirectoryObject
{
    private const string NamingContext = "CN=Users 1,CN=TestContainer,CN=Employees,DC=Northwind,DC=local";

    [DirectoryAttribute("objectsid", StoreGenerated = true)]
    public SecurityIdentifier SID { get; set; }

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

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

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

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

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

    [DirectoryAttribute(ImageFormat = ImageType.Png)]
    public Bitmap Photo { get; set; }

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

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

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

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

    [DirectoryAttribute("pwdlastset", DateTimeFormat = null, StoreGenerated = true)]
    public DateTime? PasswordLastSet { get; set; }

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

    public void SetDistinguishedName()
    {
        DistinguishedName = "CN=" + CommonName + "," + NamingContext;
    }
}

There are a few items to note here. First is the StoreGenerated property of DirectoryAttribute. This allows the DirectoryContext to only update attributes that the store doesn't manage. The second is the DistinguishedName attribute which mainly helps when calling Add and Update. The third is the DirectoryObject which isn't a part of LINQ to LDAP, but is an example abstract base class that all directory objects can sub-type.
User user = new User
                {
                    City = "Some City",
                    CommonName = "John Doe",
                    Country = "US",
                    EmployeeID = "1",
                    FirstName = "John",
                    LastName = "Doe",
                    Name = "Doe, John",
                    Street = "1234 Street",
                    Title = "Unknown",
                    PostalCode = "12345",
                    TelephoneNumber = "123-456-7890"
                };

user.SetDistinguishedName();
var factory = new LdapConnectionFactory("localhost");
using (var context = new DirectoryContext(factory.GetConnection(), true))
{
    User added = context.Add(user);
}

I create a new user and initialize its properties. Whenever an object is added or updated, a fresh version is retrieved using GetByDN.

Alternatively, you can perform the same operation using a dictionary:
string dn = "CN=John Doe,CN=Users 1,CN=TestContainer,CN=Employees,DC=Northwind,DC=local";
var attributes = new Dictionary<string, object>
                        {
                            {"l", "Some City"},
                            {"cn", "John Doe"},
                            {"c", "US"},
                            {"EmployeeID", "1"},
                            {"givenname", "John"},
                            {"sn", "Doe"},
                            {"Name", "Doe, John"},
                            {"Street", "1234 Street"},
                            {"Title", "Unknown"},
                            {"PostalCode", "12345"},
                            {"TelephoneNumber", "123-456-7890"}
                        };

var factory = new LdapConnectionFactory("localhost");
using (var context = new DirectoryContext(factory.GetConnection(), true))
{
    IDictionary<string, object> added = context.Add(dn, "User", attributes);
}

Or by converting an anonymous object to a dictionary:
string dn = "CN=John Doe,CN=Users 1,CN=TestContainer,CN=Employees,DC=Northwind,DC=local";
var user = new
                {
                    City = "Some City",
                    CommonName = "John Doe",
                    Country = "US",
                    EmployeeID = "1",
                    FirstName = "John",
                    LastName = "Doe",
                    Name = "Doe, John",
                    Street = "1234 Street",
                    Title = "Unknown",
                    PostalCode = "12345",
                    TelephoneNumber = "123-456-7890"
                };

var factory = new LdapConnectionFactory("localhost");
using (var context = new DirectoryContext(factory.GetConnection(), true))
{
    IDictionary<string, object> added = context.Add(dn, "User", user.ToDictionary());
}

ToDictionary is just an extension method that reflects over an attribute and creates a dictionary from its properties.

So I think that covers how to add new entries. Questions, thoughts, improvements?

Sunday, July 10, 2011

Bitmap Mapping

Between a busy job, house hunting and a much needed vacation I've fallen out of the habit of blogging. My first post back I want to talk about an undocumented feature for LINQ to LDAP and why I'm thinking of removing it.

Currently LINQ to LDAP supports mapping images stored in a directory as Bitmaps. I thought this was cool since all you had to do was map it with an image type and it just worked (updates as well in the new 2.0 changes). However, I've left it undocumented since Bitmaps use GDI+ and if you're not careful you can introduce a memory leak. Imagine blindly querying for all users in your directory and suddenly there's 10,000 (assuming you're in a large organization or university) bitmaps being loaded up.

So if this feature gets removed here is how you could still support images:

[DirectorySchema("OU=Users,DC=mycompany,DC=com", ObjectClass = "User")]
public class User : IDisposable
{
     [DirectoryAttribute("Photo")]
     public byte[] PhotoBytes { get; set; }
     
     private Bitmap _photo;
     public Bitmap Photo
     {
          get
          {
               if (PhotoBytes == null) return null;
               
               if (_photo == null)
               {
                    _photo = new Bitmap(new MemoryStream(PhotoBytes));
               }
               return _photo;
          }
          set
          {
               if (_photo != null) _photo.Dispose();
               if (value == null)
               {
                    _photo = null;
                    PhotoBytes = null;
               }
               else
               {
                    _photo = value;
                    using (var stream = new MemoryStream())
                    {
                         _photo.Save(stream, ImageFormat.Png); //whatever format
                         PhotoBytes = stream.ToArray();
                    }
               }
          }
     }

     public void Dispose()
     {
          if (_photo != null) 
          {
               _photo.Dispose();
               _photo = null;
          }
     }
}

So is this even a big deal? Will anyone care if I removed bitmap mapping?

Tuesday, May 24, 2011

What's Next?

For anyone who has downloaded the trunk for LINQ to LDAP, you may have noticed a project called DirectoryViewer. This is a WPF project I'm building for my own skill and knowledge improvement. Right now it points to my local LDS server, but I've been playing around with pointing it to Penn State's public server. I'll have a series of blog posts on that development.

I'll also be working on LINQ to LDAP 2.0 to support Add, Update, Delete, and Move operations. I don't have a time frame for a release but keep an eye on the trunk if you want to try anything out. I have zero experience with making updates to directories (read only for me) so suggestions on the API would be much appreciated.

Right now I plan to support Add and Update for mapped objects as well as just key-values. Delete will support passing in a mapped object or simply a DN. Move will take an old DN and a new DN. That's about it for now. I'll try to post examples as I develop for maximum visibility and feedback. Stay tuned!

Sunday, May 22, 2011

LINQ To LDAP: Version 1.5

New version over at codeplex. I also added a C# and VB example to the front page. It took me about 30 minutes just to figure out how to write the VB example, haha.

Saturday, May 14, 2011

LINQ To LDAP: Dynamics and More

My last two posts covered how you can use dynamics to query an LDAP server. I'd like you to forget those posts...well mostly forget them. The more I worked with Expando, the more rigid it felt for my use case. Basically I need a dynamic wrapper around a SearchResultAttributeCollection, similar to the ViewBag in .Net MVC. I created my own DynamicObject to support this:
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.

Sunday, May 1, 2011

LINQ To LDAP: Dynamics! Part 2

In my previous post I talked about adding dynamics support. Now I want to cover how I did it. The ExpandoObject added in .Net 4.0 is the type that I return for all dynamic queries. The cool thing about this object is you can cast it to IDictionary<string, object> and do something like this:
dynamic user = context.Query("CN=Users,CN=Employees,DC=Northwind,DC=local")
    .Select("cn", "givenname", "sn", "telephonenumber")
    .FilterWith("cn=Andrew Fuller")
    .FirstOrDefault();

foreach (var attribute in (IDictionary<string, object>)user)
{
    Console.WriteLine(attribute.Key + ": " + attribute.Value);
}

This is especially nice if you prefer to work with static types rather than dynamic and for characters that are invalid for .Net property names but valid for attribute names (such as hyphens).

So other than that, here are a few things you should know about dynamic querying:
  • Dynamic mappings are never cached.
  • Selected attributes will always be present in the result with a value of null, even if the attribute does not exist in the directory.
  • All LINQ extension methods that are currently supported will work with dynamic querying as long as they don't require an expression (i.e. Where conditions)

Well that's about it. I'll end with the result transformer for dynamic objects:
internal class DynamicResultTransformer : IResultTransformer
{
    private readonly IEnumerable<string> _attributesToLoad;

    public DynamicResultTransformer(IEnumerable<string> attributesToLoad)
    {
        _attributesToLoad = attributesToLoad;
    }

    public object Transform(SearchResultEntry entry)
    {
        dynamic expando = new ExpandoObject();
        var attributes = expando as IDictionary<string, object>;

        var names = (_attributesToLoad == null || !_attributesToLoad.Any())
                        ? entry.Attributes.AttributeNames.Cast<string>()
                        : _attributesToLoad;
        foreach (var attribute in names)
        {
            var value = entry.Attributes[attribute];
            if (value == null || value.Count < 1)
            {
                attributes.Add(attribute, null);
            }
            else if (value.Count == 1)
            {
                attributes.Add(attribute, value[0]);
            }
            else if (value.Count > 1)
            {
                var type = value[0].GetType();
                attributes.Add(attribute, value.GetValues(type));
            }
        }

        return expando;
    }

    public object Default()
    {
        return default(ExpandoObject);
    }
}

Monday, April 18, 2011

LINQ To LDAP: Dynamics!

Short post to be updated later. During my property mapping changes (added support for file time date mapping and enum mapping and cleaned up the structure to support directory modifications in the future), I was inspired in a massive way to add dynamic support for LINQ to LDAP. I'm happy to say that it works very well! You can't use expressions, but everything else works.

dynamic user = context.Query("CN=Users,CN=Employees,DC=Northwind,DC=local")
    .Select(new[]{"cn", "givenname", "sn", "telephonenumber"})
    .FilterWith("cn=Andrew Fuller")
    .FirstOrDefault();

Console.WriteLine(user.cn);
Console.WriteLine(user.givenname);
Console.WriteLine(user.sn);
Console.WriteLine(user.telephonenumber);

I added a Select extension that takes an array of strings so you still get projection support. FilterWith is the only way to query since everything else uses expressions (you can't use dynamic types in expressions). The only type support you get here is what can come back from the directory so that's string, string[], and byte[]. Also, property names will always be lower case so even though it may be givenName in the directory, use givenname to access it. That's just how DirectoryServices.Protocols rolls.

Tuesday, April 5, 2011

LINQ To LDAP 1.0

You can head over to codeplex and download the 1.0 version of LINQ to LDAP. Woohoo!

Download it now!

Sunday, April 3, 2011

LINQ To LDAP: Connection Pooling

I got a question recently about conneciton pooling. S.DS.P is a low level API so it has no native support for connection pooling. For anyone familiar with S.DS, DirectoryEntry will reuse connections under certain circumstances. I took a stab at implementing connection pooling in LINQ to LDAP and so far so good.

I had to support the following capabilities:
  • Maximum Pool Size: The maximum number of connections that can be created for the pool.
  • Minimum Pool Size: The minimum number of connections to maintain in the pool.
  • Connection cleanup: Cleaning up connections that have been unused for a period of time.

I created a new class called PooledConnectionFactory. You can access it from the LdapConfiguration class. There's a fluent interface for configuring the properties, but I'll only cover the key methods to support the features from above.

Getting Connections
LdapConnection IConnectionFactory.GetConnection()
{
    LdapConnection connection;
    lock (_connectionLockObject)
    {
        if (_isFirstRequest)
        {
            DateTime now = DateTime.Now;
            for (int i = 0; i < _minPoolSize; i++)
            {
                _availableConnections.Add(GetConnection(), now);
            }

            _isFirstRequest = false;
            _timer.Start();
        }

        var pair = _availableConnections.FirstOrDefault();

        if (Equals(pair, default(KeyValuePair<LdapConnection, DateTime>)))
        {
            if ((_inUseConnections.Count + _availableConnections.Count + 1) > _maxPoolSize)
                throw new InvalidOperationException(
                    string.Format("LdapConnection pool limit of {0} exceeded.", _maxPoolSize));

            connection = GetConnection();
            _inUseConnections.Add(connection);
        }
        else
        {
            _inUseConnections.Add(pair.Key);
            _availableConnections.Remove(pair.Key);
            connection = pair.Key;
        }
    }
    return connection;
}

private LdapConnection GetConnection()
{
    //code that constructs a connection from the fluent configuration
}

For all operations that interact with the pool, I have to first get a lock to ensure thread safety. I keep track of available connections via a dictionary that contains the connection and the last time it was used. Connections in use are tracked in a generic List. The first time a request for a connection comes in, I lazily initialize the pool if there's is a minimum pool size. I also start the timer for scavenging stale connections.

The first available connection is always used, and a new connection is created if one is not available. However, if the max pool size is exceeded, then I throw an exception. I've considered implementing a wait time for available connections, but I'll wait to see if it is a needed feature.

Releasing Connections
public void ReleaseConnection(LdapConnection connection)
{
    lock (_connectionLockObject)
    {
        if (_inUseConnections.Contains(connection))
        {
            _inUseConnections.Remove(connection);
            _availableConnections.Add(connection, DateTime.Now);
        }
        else
        {
            connection.Dispose();
        }
    }
}

There's not much going on here. If the connection is found in use, it's removed and returned as an available connection with an updated last used time. If it's not found then it's just disposed.

Stale Connections
private void TimerElapsed(object sender, ElapsedEventArgs e)
{
    lock (_connectionLockObject)
    {
        int amountToScavenge = _minPoolSize == 0
            ? _availableConnections.Count
            : (_availableConnections.Count - _minPoolSize);

        if (amountToScavenge <= 0) return;

        var expiredConnections = _availableConnections
            .Where(p => e.SignalTime.Subtract(p.Value).TotalMinutes > _connectionIdleTime)
            .Select(p => p.Key)
            .ToList();
                
        foreach (var expiredConnection in expiredConnections)
        {
            if (amountToScavenge == 0) break;

            _availableConnections.Remove(expiredConnection);
            expiredConnection.Dispose();
            amountToScavenge--;
        }
    }
}

First thing I do in this method is to determine if any connections actually need to be cleaned up. After that I look for stale connections. I iterate over those connections and dispose of them until I've satisfied the number to scavenge or until there are no more connections.

Benchmarking
I was curious what kind of benefits pooling would provide when hitting a directory server. My initial testing was against my local machine and I saw an average 10ms difference per query when using pooling. That's fine and dandy, but unrealistic.

So I found a public LDAP server hosted by verisign. So here's my test:
static void Main(string[] args)
{
    new LdapConfiguration()
        .MaxPageSizeIs(10)
        .ConfigurePooledFactory("directory.verisign.com")
        .AuthenticateBy(AuthType.Anonymous);

    var thread1 = new Thread(() => 
        DoVerisignWork(1000, "thread 1"))
                      {
                          IsBackground = true, Name = "thread 1"
                      };

    var thread2 = new Thread(() => 
        DoVerisignWork(500, "thread 2"))
                      {
                          IsBackground = true, Name = "thread 2"
                      };

    var thread3 = new Thread(() => 
        DoVerisignWork(1500, "thread 3"))
                      {
                          IsBackground = true, Name = "thread 3"
                      };

    var thread4 = new Thread(() => 
        DoVerisignWork(300, "thread 4"))
                      {
                          IsBackground = true, Name = "thread 4"
                      };

    var thread5 = new Thread(() => 
        DoVerisignWork(600, "thread 5"))
                      {
                          IsBackground = true, Name = "thread 5"
                      };

    var thread6 = new Thread(() =>
    {
        while (Watch.IsRunning)
        {

        }
        Thread.Sleep(1000);
        Console.WriteLine("Total iterations: " + TotalCount + Environment.NewLine +
                        "Operations over limit: " + OverCount + Environment.NewLine +
                        "Average operation time ms: " +
                        (TimeToExecute / TotalCount) + Environment.NewLine +
                        "Total time: " + Watch.ElapsedMilliseconds);
        Console.ReadLine();
    });

    Watch.Start();
    thread1.Start();
    thread2.Start();
    thread3.Start();
    thread4.Start();
    thread5.Start();

    thread6.Start();
}

I set up my threads to simulate a pretty light load. When everything is done thread 6 will display the results.

From what I can tell, the server only exposes the RootDSE. So I can only test the ListServerAttributes method, which is just a ListAttributes request with (objectClass=*) filter. Here's my work method:
private static void DoVerisignWork(int milliseconds, string threadName)
{
    for (int i = 0; i < 5; i++)
    {
        int count = 0;
        while (count < 10)
        {
            TotalCount++;
            var watch = new Stopwatch();
            watch.Start();
            using (var context = new DirectoryContext())
            {
                context.ListServerAttributes();
            }
            watch.Stop();
            TimeToExecute += watch.ElapsedMilliseconds;
            if (watch.ElapsedMilliseconds > 150)
            {
                OverCount++;
            }
            count++;

            Thread.Sleep(milliseconds);
        }
    }

    if (threadName == "thread 3")
    {
        Watch.Stop();
    }

    Console.WriteLine(threadName + " finished");
}

So here are my results for the default connection factory:

Total Iterations: 250
Operations Over 150 ms: 250
Average Operation Time: 272.42 ms
Total Time: 113.734 seconds

And for the pooled connection factory:

Total Iterations: 250
Operations Over 150 ms: 51
Average Operation Time: 149.58 ms
Total Time: 107.248 seconds

The pool created 5 connections for each thread since they are started in sequence. When I added a short sleep after the second thread was started, it was able to do the same work with 4 connections. I'm pretty happy with these results. ~120 ms improvement would definitely help with scalability. This has already been checked into the trunk.

You can download the sample code for the pool here:

Friday, March 25, 2011

LINQ To LDAP: Sorting

I recently checked in my implementation of sorting for LINQ to LDAP. It's a pretty straight forward implementation. I have sort options that I pick up when visiting the expression and they get applied when I execute the request. Here's my QueryCommand that handles standard execution:
internal class StandardQueryCommand : QueryCommand
{
    public StandardQueryCommand(IQueryCommandOptions options, IObjectMapping mapping)
        : base(options, mapping, true)
    {
    }

    public override object Execute(DirectoryConnection connection, SearchScope scope)
    {
        SearchRequest.Scope = scope;
            
        var isSorted = Options.SortingOptions != null;
        if (isSorted)
        {
            var sortRequest = new SortRequestControl(Options.SortingOptions.Keys)
                                    {
                                        IsCritical = false
                                    };
            SearchRequest.Controls.Add(sortRequest);
        }
            
        return Options.PagingOptions == null 
            ? HandleStandardRequest(isSorted, connection) 
            : HandlePagedRequest(connection);
    }

    private object HandleStandardRequest(bool isSorted, DirectoryConnection connection)
    {
        if (isSorted)
        {
            var pageRequest = new PageResultRequestControl(LdapConfiguration.ServerMaxPageSize);
            SearchRequest.Controls.Add(pageRequest);
        }
        var response = connection.SendRequest(SearchRequest) as SearchResponse;

        AssertSuccess(response);

        AssertSortSuccess(response.Controls);
        return Options.GetEnumerator(response.Entries);
    }

    private object HandlePagedRequest(DirectoryConnection connection)
    {
        var pageRequestControl = new PageResultRequestControl(Options.PagingOptions.PageSize)
                                        {
                                            Cookie = Options.PagingOptions.NextPage
                                        };

        SearchRequest.Controls.Add(pageRequestControl);

        var response = connection.SendRequest(SearchRequest) as SearchResponse;

        AssertSuccess(response);

        AssertSortSuccess(response.Controls);
        var pageResponseControl = GetControl<PageResultResponseControl>(response.Controls);
        var parameters = new[]
                                {
                                    Options.PagingOptions.PageSize, 
                                    pageResponseControl.Cookie,
                                    Options.GetEnumerator(response.Entries)
                                };

        return Activator.CreateInstance(
            typeof(LdapPage<>).MakeGenericType(Options.GetEnumeratorReturnType()),
            parameters);
    }

    private static void AssertSortSuccess(IEnumerable<DirectoryControl> controls)
    {
        var control = GetControl<SortResponseControl>(controls);

        if (control != null && control.Result != ResultCode.Success)
        {
            throw new LdapException(
                string.Format("Sort request returned {0}", control.Result));
        }
    }
}

Sort requests require paging or else you'll get a size limit exceeded if you have too many results. I thought this was all I needed to worry about, but when I tried to filter 50K entries I found another gem. The response of the sort request was "UnwillingToPerform". I felt like my laptop just told me to take a hike. I Googled and only turned up that something was wrong with my sort. I looked at my query and it hit me:
var orderBy = context.Query<User>()
    .OrderByDescending(u => u.LastName)
    .Select(u => new User {CommonName = u.CommonName, FirstName = u.FirstName, LastName = u.LastName})
    .ToPage(50);

Basically this is the same error as size limit exceeded. Even with a page request, the filter is too broad to effectively perform a server side sort, so it fails. I applied a filter and everything worked.

Sorting is not an efficient operation for the server which is why it has a size limit. Unless you're working on a horribly constrained client, I recommend doing your sorting there.

Sunday, March 13, 2011

LINQ To LDAP: Paging

Let's talk about paging. The first thing you have to understand is paging with LDAP is done very differently than paging with RDBMS. Typically you'd have something like this:
var paged = context.Users.Skip(0).Take(10)
where the skip amount is the index of the first result and the take amount is the page size. LINQ to Objects works with this as well as most LINQ to SQL implementations. This supports forwards and backwards paging pretty easily since you can always control the position from which you start paging.

LDAP, however, treats paging a little differently. Here's an example:
private byte[] _nextPage;
private int _pageSize = 2;
private void Page()
{    
    var control = new PageResultRequestControl(_pageSize) {Cookie = _nextPage};

    var request = new SearchRequest
                        {
                            DistinguishedName = "CN=Users,CN=Employees,DC=Northwind,DC=local",
                            Filter = "objectClass=user",
                            Scope = SearchScope.Subtree
                        };
    request.Controls.Add(control);
    using (var connection = new LdapConnection("localhost"))
    {
        var response = connection.SendRequest(request) as SearchResponse;
        var next = response.Controls[0] as PageResultResponseControl;
        _nextPage = next.Cookie;
    }
}

You set the page size and you use a paging control to let the server know the position of the page. The server will generate a new cookie that will give you the next page. From the RFC:
The client MUST consider the cookie to be an opaque structure and make no assumptions about its internal organization or value. When the client wants to retrieve more entries for the result set, it MUST send to the server a searchRequest with all values identical to the initial request with the exception of the messageID, the cookie, and optionally a modified pageSize. The cookie MUST be the octet string on the last searchResultDone response returned by the server. Returning cookies from previous searchResultDone responses besides the last one is undefined, as the server implementation may restrict cookies from being reused.
If you know that the LDAP server you're using will work with backwards paging, you can implement client side tracking of the previous cookies, but I can't support it since it's unpredictable between servers. Either way, here's how I added paging support.

So let's start with how to page:
//start paging
var oldPage = context.Query<User>()
   .Where(u => u.FirstName.StartsWith("A"))
   .Select(u => u.DistinguishedName)
   .ToPage(10);

//go to next page
if (oldPage.HasNextPage)
{
   var nextPage = context.Query<User>()
      .Where(u => u.FirstName.StartsWith("A"))
      .Select(u => u.DistinguishedName)
      .ToPage(oldPage.PageSize, oldPage.NextPage);
}

ToPage is an extension method that returns a custom collection I created to allow you to get some basic information about the page and easily move to the next page. Here's PagedCollection:
public interface IPagedCollection<out T> : IEnumerable<T>
{
    int Count { get; }
    int PageSize { get; }
    bool HasNextPage { get; }
    byte[] NextPage { get; }
}

public class PagedCollection<T> : ReadOnlyCollection<T>, IPagedCollection<T>
{
    public PagedCollection(int pageSize, byte[] nextPage, object enumerator)
        : this(pageSize, nextPage, GetList((IEnumerator<T>)enumerator))
    {
    }

    public PagedCollection(int pageSize, byte[] nextPage, IList<T> page)
        : base(page)
    {
        PageSize = pageSize;
        NextPage = nextPage;

        HasNextPage = NextPage != null && NextPage.Length > 0;
    }

    private static IList<T> GetList(IEnumerator<T> enumerator)
    {
        var list = new List<T>();

        while (enumerator.MoveNext()) 
            list.Add(enumerator.Current);

        return list;
    }

    public int PageSize { get; private set; }
    public bool HasNextPage { get; private set; }
    public byte[] NextPage { get; private set; }
}

I've committed these changes to the trunk and updated the sample project. Go ahead and try it out. Let me know if there's anything I can add / change.

Tuesday, March 8, 2011

LINQ To LDAP: Version 0.9

You can head over to codeplex and download a beta version of LINQ to LDAP. I've tested it, but I didn't feel comfortable making it a 1.0 release without getting feedback. I also updated the documentation over there as well.

Download it now!

Sunday, March 6, 2011

LINQ To LDAP: String to Int follow-up

I said I would clean up my DelegateBuilder class when I could. Here's the updated version that now even supports enum mapping. A lot cleaner in my opinion.
private static readonly Func<object, Type, object> ConvertStringToValueTypeIfNecessaryFunction =
    (o, t) =>
        {
            string str;
            if ((str = o as string) != null && t.IsValueType)
            {
                if (t.IsGenericType && string.Empty.Equals(str))
                {
                    return null;
                }

                return t.IsEnum 
                    ? Enum.Parse(t, str, true) 
                    : Convert.ChangeType(str, t);
            }
            return o;
        };

Sunday, February 20, 2011

LINQ To LDAP: How Does It Work Part 3

Time to query! I'll continue working with my User class that created in Part 2. So how can you query?

Where
You can chain any number of where clauses together. For instance these two methods will produce the same filter (not exactly the same):
var where = context.Query<User>()
    .Where(u => u.FirstName == "Sir" && 
                u.LastName == "Phobos" && 
                u.Title == "Knight of Mars");
//produces: (&(objectCategory=Person)(&(givenname=Sir)(sn=Phobos)(Title=Knight of Mars)))

var where = context.Query<User>()
    .Where(u => u.FirstName == "Sir")
    .Where(u => u.LastName == "Phobos")
    .Where(u => u.Title == "Knight of Mars");
//produces: (&(objectCategory=Person)(givenname=Sir)(sn=Phobos)(Title=Knight of Mars))
In the future I will try to work on improving the number of nested ANDs that get created.

First / Single / Any / Count
These are immediate execution methods. I support all of these including the "OrDefault" variations. Single() and SingleOrDefault() will throw exceptions accordingly. First() will not throw an exception if there are no elements in the collection so it behaves the same as FirstOrDefault(). When you use either Any() or Count(), they both make use of LDAP paging controls. If the server does not support paging, then it will fail. You can use any of these methods in combination with Where and Select. However, if you use Select then order does matter depending on the Select projection. For instance:
//this works
var where = context.Query<User>()
    .Where(u => u.FirstName == "Alan")
    .Select(u => new User{FirstName = u.FirstName, LastName = u.LastName})
    .FirstOrDefault(u => u.LastName == "Hatter");

//this works because the property names on the anonymous projection match the mapped class.
var where = context.Query<User>()
    .Where(u => u.FirstName == "Alan")
    .Select(u => new {FirstName = u.FirstName, LastName = u.LastName})
    .FirstOrDefault(u => u.LastName == "Hatter");

//this will fail because the property name Last does not match LastName
var where = context.Query<User>()
    .Where(u => u.FirstName == "Alan")
    .Select(u => new User{FirstName = u.FirstName, Last = u.LastName})
    .FirstOrDefault(u => u.Last == "Hatter");

//produces: (&(objectCategory=Person)(givenname=Alan)(sn=Hatter))

ListAttributes, ListServerAttributes and Custom Filters
I blogged about ListAttributes in my earlier post "Attributes, Where?" so I won't cover it again. ListServerAttributes is a variation of ListAttributes that allows you to see information about the LDAP server.

FilterWith is a method I added to allow you to specify your own filters when querying. You can even mix FilterWith with other methods.
var filterWith = context.Query<User>()
    .FilterWith("&(givenname=Sir)(sn=Phobos)")
    .Select(u => u)
    .ToList();

//produces: (&(objectCategory=Person)(&(givenname=Sir)(sn=Phobos)))

var filterWith = context.Query<User>()
    .Where(u => u.Title == "Knight of Mars")
    .FilterWith("&(givenname=Sir)(sn=Phobos)");

//produces: (&(&(objectCategory=Person)(Title=Knight of Mars))(&(givenname=Sir)(sn=Phobos)))

I also created a static class that allows you to mix searching mapped properties and unmapped properties:
var query = context.Query<User>()
    .Where(u => u.FirstName == "Alan" && 
                Filter.Equal(u, "sn", "Hatter"));

//produces: (&(objectCategory=Person)(&(givenname=Alan)(sn=Hatter)))

var query = context.Query<User>()
    .Where(u => !(u.FirstName == "Alan" && 
                  Filter.Approximately(u, "sn", "Hatter")));
//produces: (&(objectCategory=Person)(&(!(givenname=Alan))(!(sn~=Hatter))))

Query Semantics
Let's start with looking for everyone that fits one of these criteria: Sales rep or manager, named Andrew Fuller, not in the USA, manager for Anne Dodsworth or last name starting with Hatter. Also, all of them must have had an account created before 11/24/2010.
var annesDn = context.Query<User>()
    .Where(u => u.Name == "Anne Dodsworth")
    .Select(u => u.DistinguishedName)
    .FirstOrDefault();

var titles = new List<string> {"Sales Representative", "Sales Manager"};
            
DateTime date = new DateTime(2010, 11, 24);
            
var complex = context.Query<User>()
    .Where(u => u.WhenCreated < date && 
                (titles.Contains(u.Title) ||
                 u.Name == "Andrew Fuller" ||
                 u.Country != "USA" ||
                 u.Employees.Contains(annesDn) ||
                 u.LastName.StartsWith("Hatter")));

//produces this monster: (&(objectCategory=Person)(&(WhenCreated<=20101124000000.0Z)(!(WhenCreated=20101124000000.0Z))(|(|(Title=Sales Representative)(Title=Sales Manager))(Name=Andrew Fuller)(!(c=USA))(directreports=CN=Anne Dodsworth,CN=Users,CN=Employees,DC=Northwind,DC=local)(sn=Hatter*))))

Now let's look for no one with a title or people with the phone number (206) 555-3412:
var complex = context.Query<User>()
    .Where(u => u.Title == null || u.PhoneNumber == "(206) 555-3412");

//produces (&(objectCategory=Person)(|(!(Title=*))(telephonenumber=\\28206\\29 555-3412)))
Null gets converted to a "not anything" value and the parentheses require special escape characters so that's why the phone number looks different in the filter. I filter '\', '*', '(', ')', and '\u0000' for all filter values except when using the FilterWith method.

And now searching by guid:
var complex = context.Query<User>()
    .Where(u => u.Guid == new Guid("f6c3a6b4-9197-4c33-8565-7dbd27cb8bb2"));

//produces (&(objectCategory=Person)(objectguid=\\b4\\a6\\c3\\f6\\97\\91\\33\\4c\\85\\65\\7d\\bd\\27\\cb\\8b\\b2))

I also want to cover a class I added based on PredicateBuilder for building dynamic expressions. I adapted one of their examples to show how you can dynamically build expressions:
var inner = PredicateBuilder.Create<User>();
inner = inner.Or(p => p.Title.Contains("foo"));
inner = inner.Or(p => p.Title.Contains("far"));

var outer = PredicateBuilder.Create<User>();
outer = outer.And(p => p.EmployeeId >= 100);
outer = outer.And(p => p.EmployeeId <= 1000);
outer = outer.And(inner);

var query = context.Query<User>()
    .Where(outer);

//produces (&(objectCategory=Person)(&(EmployeeId>=100)(EmployeeId<=1000)(|(Title=*foo*)(Title=*far*))))

And here's the PredicateBuilder class in LINQ to LDAP:
public static class PredicateBuilder
{
    public static Expression<Func<T, bool>> Create<T>()
    {
        return default(Expression<Func<T, bool>>);
    }

    public static Expression<Func<T, TResult>> CreateExpression<T, TResult>(
        this T example, Expression<Func<T, TResult>> expression)
    {
        return expression;
    }

    public static Expression<Func<T, bool>> Or<T>(
         this Expression<Func<T, bool>> sourceExpression,
         Expression<Func<T, bool>> appendExpression)
    {
        if (sourceExpression == null)
        {
            return appendExpression;
        }
        var invokedExpr = Expression.Invoke(appendExpression, 
            sourceExpression.Parameters);
        return Expression.Lambda<Func<T, bool>>(
            Expression.OrElse(sourceExpression.Body, invokedExpr), 
            sourceExpression.Parameters);
    }

    public static Expression<Func<T, bool>> And<T>(
         this Expression<Func<T, bool>> sourceExpression,
         Expression<Func<T, bool>> appendExpression)
    {
        if (sourceExpression == null)
        {
            return appendExpression;
        }
        var invokedExpr = Expression.Invoke(appendExpression, 
            sourceExpression.Parameters);
        return Expression.Lambda<Func<T, bool>>(
            Expression.AndAlso(sourceExpression.Body, invokedExpr), 
            sourceExpression.Parameters);
    }
}

Something seems to be missing...
You may be wondering why aggreagate functions, joins, etc. support is missing. Basically LDAP is not relational. Implementing this in any way would require client side evaluation and re-querying which is beyond me to implement right now. OrderBy is missing because I didn't discover the SortRequestControl until recently so look for it in a later release (there will be one since I want to add paging and asynchronous support).

On a side note I wanted to mention that I support logging queries through a Log property on the DirectoryContext. You can also see the filter produced without executing the query against a directory by using ToString.
using (var context = new DirectoryContext() { Log = Console.Out })
{
    var query = context.Query<User>()
        .Where(u => u.FirstName == "Alan");

    //Doesn't actually query the directory
    var str = query.ToString();
}

So that's it for now. I hope that's a pretty good starter for how to use LINQ to LDAP. Next stop: v1.0! It'll be over at codeplex when I put together some documentation. Until then you're welcome to build the project yourself. It uses VS 2010 and .Net 4.

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");
}

Monday, February 7, 2011

LINQ To LDAP: How Does It Work Part 2.5

This is a continuation of my last post here. I showed how you can manually map classes in that post. Now I want to show how you wire it all up.

I created a class for fluent configuration called LdapConfiguration. This is what it looks like right now:

public class LdapConfiguration : IConnectionFactory
{
    private readonly string _serverName;
    private int _port;
    private bool _fullyQualifiedDnsHostName;
    private bool _useUdp;
    private AuthType? _authType;
    private NetworkCredential _credentials;

    internal static IConnectionFactory ConnectionFactory;

    /// <summary>
    /// Begins the fluent configration for connecting to an LDAP server.
    /// </summary>
    /// <param name="serverName">
    /// The server name can be an IP address,  a DNS domain or host name.
    /// </param>
    public LdapConfiguration(string serverName)
    {
        if (string.IsNullOrWhiteSpace(serverName)) 
            throw new ArgumentNullException("serverName");

        ConnectionFactory = this;
        _serverName = serverName;
        _port = 389;
    }

    /// <summary>
    /// Sets the port manually for the LDAP server.  The default is 389.
    /// </summary>
    /// <param name="port">
    /// The port to use when communicating with the LDAP server.
    /// </param>
    /// <returns></returns>
    public LdapConfiguration UsePort(int port)
    {
        _port = port;
        return this;
    }

    /// <summary>
    /// Sets the port to the default SSL port for the LDAP server.  The default is 636.
    /// </summary>
    /// <returns></returns>
    public LdapConfiguration UseSsl()
    {
        _port = 636;
        return this;
    }

    /// <summary>
    /// Sets the port to the default Global Catalog port for Active Directory 
    /// based LDAP servers.  The default is 3268.
    /// </summary>
    /// <returns></returns>
    public LdapConfiguration UseGlobalCatalog()
    {
        _port = 3268;
        return this;
    }

    /// <summary>
    /// If this option is called, the server name is a fully-qualified DNS host name. 
    /// Otherwise the server name can be an IP address, a DNS domain or host name.
    /// </summary>
    /// <returns></returns>
    public LdapConfiguration ServerNameIsFullyQualified()
    {
        _fullyQualifiedDnsHostName = true;
        return this;
    }

    /// <summary>
    /// Indicates that the connections will use UDP (User Datagram Protocol).
    /// </summary>
    /// <returns></returns>
    public LdapConfiguration UseUdp()
    {
        _useUdp = true;
        return this;
    }

    /// <summary>
    /// Allows you to specify an authentication method for the 
    /// connection.  If this method is not called,  the authentication method 
    /// will be resolved by the <see cref="LdapConnection"/>.
    /// </summary>
    /// <param name="authType">
    /// The type of authentication to use.
    /// </param>
    /// <returns></returns>
    public LdapConfiguration AuthenticateBy(AuthType authType)
    {
        _authType = authType;
        return this;
    }

    /// <summary>
    /// Allows you to specify credentials for the conneciton to use.  
    /// If this method is not called,  then the <see cref="LdapConnection"/> 
    /// will use the credentials of the current user.
    /// </summary>
    /// <param name="credentials">
    /// The credentials to use.
    /// </param>
    /// <returns></returns>
    public LdapConfiguration AuthenticateAs(NetworkCredential credentials)
    {
        _credentials = credentials;
        return this;
    }

    /// <summary>
    /// Adds the mapping for querying.
    /// </summary>
    /// <param name="classMap">The mapping for the class</param>
    /// <returns></returns>
    public LdapConfiguration AddMapping(IClassMap classMap)
    {
        DirectoryMapper.Map(classMap);
        return this;
    }

    /// <summary>
    /// Adds all mappings in the assembly.
    /// </summary>
    /// <param name="assembly">
    /// The assembly containing all of the mappings
    /// </param>
    /// <returns></returns>
    public LdapConfiguration AddMappingsFrom(Assembly assembly)
    {
        foreach (var type in assembly.GetTypes())
        {
            if (type.IsInterface) continue;

            var baseType = type.BaseType;
            while (baseType != typeof(object))
            {
                if (baseType.IsGenericType && 
                    baseType.GetGenericTypeDefinition() == typeof(ClassMap<>))
                {
                    AddMapping(Activator.CreateInstance(type) as IClassMap);
                    break;
                }
                baseType = baseType.BaseType;
            }
        }

        return this;
    }

    /// <summary>
    /// Adds all mappings in the assembly.
    /// </summary>
    /// <param name="assemblyName">
    /// The assembly containing all of the mappings
    /// </param>
    /// <returns></returns>
    public LdapConfiguration AddMappingsFrom(string assemblyName)
    {
        if (string.IsNullOrWhiteSpace(assemblyName)) 
            throw new ArgumentNullException("assemblyName");

        assemblyName = assemblyName.ToLower().EndsWith(".dll")
                            ? assemblyName
                            : assemblyName + ".dll";

        var assembly = Assembly.LoadFrom(assemblyName);

        return AddMappingsFrom(assembly);
    }

    /// <summary>
    /// Provides access to all currently mapped classes.
    /// </summary>
    /// <returns></returns>
    public static ReadOnlyDictionary<Type, IObjectMapping> GetMappings()
    {
        return DirectoryMapper.GetMappings();
    }

    LdapConnection IConnectionFactory.GetConnection()
    {
        return GetConnection();
    }

    /// <summary>
    /// Allows access for controlling connection creation and lifetime.
    /// </summary>
    /// <returns></returns>
    protected virtual LdapConnection GetConnection()
    {
        var identifier = new LdapDirectoryIdentifier(_serverName, _port, 
            _fullyQualifiedDnsHostName, _useUdp);

        return _authType.HasValue
            ? new LdapConnection(identifier, _credentials, _authType.Value)
            : new LdapConnection(identifier, _credentials);
    }
}

Here's a basic example for configuring an Active Directory based configuration:
new LdapConfiguration("myserver.com")
    .UseGlobalCatalog()
    .AuthenticateBy(AuthType.Negotiate)
    .AddMappingsFrom("some.other.assembly")
    .AddMapping(new UserMapping());

So what's this doing?
  • UseGlobalCatalog sets the port for connecting to a global catalog server in an AD Forrest. See here for more information.
  • AuthenticateBy sets the authentication method for communicating with the server. This will always default to Negotiate (DirectoryServices.Protocols makes that decision) so most of the time you won't have to set this value. In my basic experience with non-AD LDAP servers, I imagine you'll be setting this and AuthenticateAs more often.
  • AddMappingFrom allows you to map all of the classes that subtype ClassMap in a given assembly. Pretty useful if you have a bunch of mappings.
  • AddMapping let's you add them one at a time or exclude some.

You only need to configure this once so put it in your start up code and you're good to go. Whenever you create a DirectoryContext now you don't need to pass in a connection:
using (var context = new DirectoryContext())
{
    var user = context.Query<User>()
        .FirstOrDefault(u => u.CommonName == "Alan Hatter");
}

And here's what goes on behind the scenes:
public DirectoryContext()
{
    if (LdapConfiguration.ConnectionFactory == null) 
        throw new MappingException("No configuration has been provided.  Please use LinqToLdap.Mapping.LdapConfiguration.");

    _disposeOfConnection = true;
    _connection = LdapConfiguration.ConnectionFactory.GetConnection();
}

I think I'll have one more post on querying and everything should be ready for a 1.0 release. WOOO!

Sunday, February 6, 2011

LINQ to LDAP: Attributes, Where?

I think one of the hardest things for me in working with LDAP is finding out what is actually in the directory. A Google search can help with identifying predefined attributes, but what about custom ones? Each directory is going to be different and different organizations are going to track different things. I wanted to make this discovery process easier.

I created an extension method called ListAttributes that does what it's name suggests. I'll use LINQPad to show how much I like this feature. You can download it here for free.


First thing you'll want to do is Launch the application and switch the language to "C# Program"


Right click and then select "Query Properties"


You should now see this screen. From here you should click on "Add" to add System.DirectoryServices.Protocols and "Browse" to add LinqToLdap.dll from where you have it on your machine.


It should look like this when you're done.


Next click the "Additional Namespace Imports" tab and type in System.DirectoryServices.Protocols and LinqToLdap.

After you hit OK, you can then type in some C# code and execute it. I'm going to use Andrew Fuller from the Northwind database that I used to populate my directory.

void Main()
{
 using (LdapConnection connection = new LdapConnection("localhost"))
 {
  var example = new { Cn = "" };
  using (var context = new DirectoryContext(connection))
  {
   context.Query(example, "CN=Employees,DC=Northwind,DC=local")
    .Where(u => u.Cn == "Andrew Fuller")
    .ListAttributes()
    .Dump();
  }
 }
}

That Dump() method is a special extension method that is part of LINQPad. It will format the results auto-magically for you which is why I like it so much. Here's what I get when I execute the query:


Now this won't just dump everything. It will only list populated attributes. It's not perfect, but it will give you a pretty good idea what is and isn't tracked about directory objects and what their data types are. LINQPad is even doing some color coding for you to show you what's a byte[]. Awesome!

Tuesday, February 1, 2011

LINQ To LDAP: How Does It Work Part 2

So up until this point all of my posts have used the auto-mapping via anonymous types. For this post I want to cover manual mapping of classes.

Here is an example mapping:

public class User
{
    public string DistinguishedName { get; set; }
    public int BadPasswordCount { get; set; }
    public string[] Employees { get; set; }
    public string Title { get; set; }
    public string PostalCode { get; set; }
    public string CommonName { get; set; }
    public DateTime? WhenCreated { get; set; }
    public string FirstName { get; set; }
    public Guid Guid { get; set; }
    public string City { get; set; }
    public int Version { get; set; }
    public string Country { get; set; }
    public DateTime? LastChanged { get; set; }
    public byte[] Sid { get; set; }
    public long EmployeeId { get; set; }
    public string PhoneNumber { get; set; }
    public string Street { get; set; }
    public string Comment { get; set; }
    public string Name { get; set; }
    public string LastName { get; set; }
}

public class UserMapping : ClassMap<User>
{
    public UserMapping()
    {
        NamingContext("CN=Users,CN=Employees,DC=Northwind,DC=local");
        ObjectCategory("person");

        Map(u => u.BadPasswordCount).Named("badpwdcount");
        Map(u => u.Employees).Named("directreports");
        Map(u => u.Title);
        Map(u => u.PostalCode);
        Map(u => u.CommonName).Named("cn");
        Map(u => u.WhenCreated);
        Map(u => u.FirstName).Named("givenname");
        Map(u => u.Guid).Named("objectguid");
        Map(u => u.City).Named("l");
        Map(u => u.Version).Named("usnchanged");
        Map(u => u.Country).Named("c");
        Map(u => u.LastChanged).Named("whenchanged");
        Map(u => u.Sid).Named("objectsid");
        Map(u => u.DistinguishedName);
        Map(u => u.EmployeeId);
        Map(u => u.PhoneNumber).Named("telephonenumber");
        Map(u => u.Street);
        Map(u => u.Comment);
        Map(u => u.Name);
        Map(u => u.LastName).Named("sn");
    }
}

I specify the object's location in the directory by using NamingContext(). I also map it to a object category, but it's not required. Mapping properties is pretty straightforward. You use a lambda expression to map a property and if it has a different name in the directory then you can specify that with Named().

It's not always necessary to create a specific mapping for your classes. The same way auto-mapping works with anonymous types, you can use the class as an example and it will map everything by convention. You have two options if you want to auto-map an existing class:

//Option 1
var user = context.Query<User>("CN=Users,CN=Employees,DC=Northwind,DC=local")
                .FirstOrDefault(u => u.Name == "Alan Hatter");

//Option 2
var example = new User();
var user = context.Query(example, "CN=Users,CN=Employees,DC=Northwind,DC=local", "Person")
                .FirstOrDefault(u => u.Name == "Alan Hatter");

If you try to auto-map an existing class without specifying a namingContext, you will get a MappingException. Also, remember your property names have to match the names in the directory for everything to work with auto-mapping. I'll cover finding available attributes, their types, and names in my next post. Stay tuned!

Sunday, January 9, 2011

LINQ To LDAP: How Does It Work Part 1

UPDATE: Query methods have been reduced to encourage manual mapping for non-anonymous types.
UPDATE: root has been renamed to namingContext

So in my last post I had two examples showing Linq to LDAP working. I want to cover how it all works. I'll start with the interface IDirectoryContext:

public interface IDirectoryContext : IDisposable
{
    IQueryable<T> Query<T>() where T : class;
    IQueryable<T> Query<T>(SearchScope scope) where T : class;
    IQueryable<T> Query<T>(T example, string namingContext) where T : class;
    IQueryable<T> Query<T>(T example, string namingContext, string objectCategory) where T : class;
    IQueryable<T> Query<T>(T example, SearchScope scope, string namingContext) where T : class;
    IQueryable<T> Query<T>(T example, SearchScope scope, string namingContext, string objectCategory) where T : class;
}

The available parameters:
  • example: This as an anonymous object that can be used for auto mapping. In my previous post my anonymous object looked like this:
    var example = new {DistinguishedName = "", Cn = ""};
       
    All you need is to specify a name and a value so the compiler can infer the type. Here's an example with more properties:
    var example =
        new
            {
                DistinguishedName = "",
                Cn = "",
                WhenCreated = default(DateTime),
                ObjectGuid = default(Guid),
                DirectReports = default(string[])
            };
       
    Auto mapped properties must have names that match the directory attribute name. Casing does not matter.
  • scope: This determines the depth at which the search is performed. You can read more about search scopes here. I always default to a subtree scope if you don't specify one.
  • namingContext: This is the place in the directory from which you want to start your search. You can make it the root of the directory "DC=Northwind,DC=local" or you can go deeper "CN=Users,CN=Employees,DC=Northwind,DC=local". You'll notice that if you query by example, you must always specify the naming context. I do this because auto mapping cannot infer the naming context.
  • objectCategory: This is an LDAP attribute you can specify to improve searching performance. You may be wondering why I use objectCategory instead of objectClass. It boils down to objectCategory being single valued and objectClass being multivalued so searching by objectCategory is faster. You can read more about it here.