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!