Showing posts with label LINQ To LDAP. Show all posts
Showing posts with label LINQ To LDAP. Show all posts

Sunday, October 20, 2013

LINQ to LDAP Examples

I've been working on a "Getting Started" guide and an official examples project. A friend of mine created LINQ to LDAP Demonstrations to help me out since I was slacking and not keeping the samples project up to date. The new examples point to two live OpenLDAP servers (thanks to Stuart Lewis and the University of Texas) so you can actually play around with the framework.

Some of the examples highlight the differences between plain old DirectoryServices and LINQ to LDAP. Not everything works correctly using DirectoryServices (i.e. paging) so I've made the queries pretty basic so everything works across the board. If you know of a publicly accessible AD/AD-LDS server then let me know. You can find the examples here.

Monday, October 8, 2012

LINQ to LDAP: Event Listeners

I'm adding support for listening to Add, Update, and Delete operations in the latest version of LINQ to LDAP. This will allow you to perform validation or logging operations before anything is executed. So let's try this out:



There are 3 interfaces that you can implement to subscribe to events: IPreAddEventListener, IPreDeleteEventListener, IPreUpdateEventListener. Each interface has a Notify method that will give you access to the entry being modified (the mapped object, the DirectoryAttributes, or the Distinguished Name in the case of Delete), the LdapConnection used for the modification, and the DirectoryRequest that will be sent. The DirectoryRequest won't have any of the attribute values added since you may want to do validation beforehand. You can also add / modify anything you want about the request.

Using your event listener is pretty straightforward:



When using the LdapConfiguration you can call RegisterListener and it will be used across all DirectoryContexts. When using the LdapConnection extension methods you can pass it in as a parameter. And that's about it.

Friday, June 22, 2012

LINQ to LDAP: Closer to Home

During my experience with passwords I was working directly with an LdapConnection. After a while I wondered how easy it would be to add extension methods directly to the LdapConnection. It was actually pretty easy so all of the methods that don't rely on object mappings are now available from an LdapConnection object now.

Thursday, June 21, 2012

LINQ to LDAP: How do passwords work?

A couple hours of trial and error I have finally figured out setting passwords in Active Directory and Lightweight Directory Services. As a result of this I ported all of the dynamic functionality to some extension methods for LdapConnection.

Modifying passwords has been in the back of my head ever since the question was asked here. If I had looked closer at the answer on StackOverflow I would have seen that the solution was for Sun-One. If there's one thing I've learned working on LINQ to LDAP it's that every server has been implemented a little differently.

So let's get down to it. The first thing I learned is userPassword in AD LDS is not the "real" password attribute. That would be unicodePwd. I then received a very helpful "An operation error occurred." A few searches later and I found out that means you'll have to use port 636 instead of 389. Turns out Microsoft does not allow changing the password over a non SSL connection. However, you don't have to explicitly set SSL to true if you're post Windows 2000 (I hope you are). Okie dokie let's fire this up.

"The LDAP server is unavailable."

You'll get this error if SSL is not on for your server. This lead me to generating a self signed certificate and installing it for my local instance. You can read about the how and why here. Alright, now my server is responding to requests on 636, but I'm getting "A value in the request is invalid." That's when I found this post explaining how to do this the right way. I wish I had found that first. So here's the resulting code:



For those using AD LDS and you don't have SSL on, let me save you some trouble. I found this post later allowing you to enable modifying the password over port 389. Well I hope this was helpful.

update
I should probably add that you should not enable setting passwords over an unencrypted connection. I am working in a test environment so it's no big deal for me.

Monday, June 11, 2012

LINQ to LDAP: Documentation

Just a minor update. 3.0 is coming. I'm currently reviewing and updating the documentation over at CodePlex. I realized that I haven't touched it since sometime around 1.5 / 2.0 so it's in dire need of a refresh. There are a few things from 3.0 in there so don't download 2.5 and scream "WTF, this is all wrong!" :).

Saturday, March 17, 2012

LINQ To LDAP: 2.5

Been a while since my last post. There are quite a few changes in 2.5, but I want to focus on the biggest ones.

Multiple Naming Contexts and Configurations
First big change is now you can query for mapped objects in any naming context. This makes it easier to reuse a mapping for different parts of the directory or even with a completely different directory. The naming context you specify here will always override whatever you have mapped.
context.Query<User>(namingContext: "naming context");

This leads me to the next change. I've had a lot of questions about having dynamic configurations and multiple directory support. I decided to give you more control over the lifespan of LdapConfiguration. I've also exposed DirectoryMapper so you can share your mappings among multiple configurations. Each configuration has it's own connection factory so this should handle connecting to multiple directories.

You can store your configurations with your favorite IoC container, in a static class, or whatever your preference. You can still use a single global one by calling UseStaticStorage on the configuration.

Custom Mapping
I've also added support for mapping properties to custom values. There's a new method on ClassMap called MapCustom. Here's an example of mapping ObjectGuid as a custom property:
MapCustom(s => s.ObjectGuid).Named("objectGuid")
    .ConvertFromDirectoryUsing(d =>
                                    {
                                        var bytes = d.GetValues(typeof(byte[]))[0] as byte[];
                                        return new Guid(bytes);
                                    })
    .ConvertToFilterUsing(v => v.ToStringOctet())
    .ConvertToDirectoryUsing(v => v.ToByteArray())
    .CompareChangesUsing((guid1, guid2) => guid1.Equals(guid2));
At a minimum you have to implement ConvertFromDirectoryUsing. If you want to filter by it you have to implement ConvertToFilterUsing. And if you want to update the property then you have to implement ConvertToDirectoryUsing. CompareChangesUsing is optional. If you've implemented IDrectoryObject or subclassed DirectoryObjectBase and your custom mapping is for something that can't really use the default Equals implementation then you'll want to implement it.

I've also added support for forwarding values in place of others. Right now it's only available when mapping DateTimes (due to the 9223372036854775807 value meaning "not set"). I plan to expand this to other property types if necessary. Here's how you use it:
Map(c => c.AccountExpires)
    .Named("accountExpires")
    .DateTimeFormat(null)
    .DirectoryValue("9223372036854775807").Returns(null)
    .InstanceValue(DateTime.MinValue).Sends("9223372036854775807")
    .InstanceValueNullOrDefault().Sends("9223372036854775807");

Querying
I just want to add a small note about the Filter class. Previously I was escaping any special characters you passed in. I think now that I should give you more control over the values. You can now use Filter.Equal("attribute", "*"). However, if you want to escape any special characters you can use Filter.Equal("attribute", "some value".CleanFilterValue()).

You can read more about what's in this release here.

Wednesday, January 11, 2012

LINQ To LDAP: More CRUD

I just realized that I never actually finished writing about CRUD operations with LINQ to LDAP. I guess I don't have to worry too much because someone else did it for me! He plans to have a full series on using LINQ to LDAP with Visual Studio LightSwitch.

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.