This project is read-only.

Building an API with Linq to LDAP to support multiple ADs?

Nov 17, 2011 at 7:54 PM
Edited Nov 17, 2011 at 8:02 PM

Hi Madhatter, excellent work on this project, it's great. Two questions for you:

1) Typically when I work through AD's DirectoryEntry I do like so: new DirectoryEntry("LDAP://ldap.mydomain.com/dc=mydomain,dc=com");

Is my server name ldap.mydomain.com or the entire thing or just mydomain.com? Also is that a FQDN? I am still somewhat new to AD/DNS things myself so wasn't sure what is considered what for AD.

2) I am trying to write an API using Linq to LDAP that needs to support multiple ADs. Basically we have 3 ADs and depending on who uses the API they'll need to use a different domain and OU/CNs. There would even be cases where one project would need to reference 2 ADs. We'll use the same mappings across all ADs regardless of which one is used though. Since this API would be a referenced assembly in their projects, I am unsure how to go about using this still, it seems like all mapping must include a naming context, which would change. It looks like to support that I would need to set the LDAP configuration and connection in the referencing project, but I am trying to abstract all that to easy service method calls.

Can you recommend an approach for doing that?

Nov 21, 2011 at 4:40 AM
Edited Nov 21, 2011 at 5:26 AM

Hi sah302,

1) In my experience mydomain.com is sufficient with AD.  The Locator service will find an available directory server for you.  The fully qualified name would be the actual name of the directory server.

2) Yes, you are correct.  An object can only be mapped to one naming context.  You can subclass your objects so you can map classes with the same structure across multiple naming contexts.  You can check the mapping documentation for how to do that.  From what you describe the dynamic queries would probably be better for your situation.  One thing that I need to address is moving past static storage for the connection factory and mappings.  I'll try to address that in a future release.  For now, something like this seems to be the best solution I have for you:

 

public class DirectoryService
{
     private IDirectoryContext _context;
     public DirectoryService(IDirectoryContext context)
     {
          _context = context;
     }

     public List<User> GetUsers(string namingContext, Expression<Func<IDirectoryAttributes, bool>> expression)
     {
          return _context.Query(namingContext, objectClass: "user")
               .Where(expression)
               .Select(da => new User
                                 {
                                      CommonName = da.GetString("cn"),
                                      //rest of the attributes
                                 })
               .ToList();
     }
}

public class SomeClient
{
     public void DoWork()
     {
          //you can either move this expression into the service or allow it to be passed in
          var expression = PredicateBuilder.Create<IDirectoryAttributes>();
          expression = expression.And(da => Filter.Equal(da, "givenname", "John") && Filter.Equal(da, "sn", "Doe"));

          var namingContext = //get naming context from somewhere based on client configuration.

          var users = service.GetUsers(namingContext, expression);
     }
}
Nov 21, 2011 at 10:10 PM

Thanks for the reply.

Hmm I see, would it be possible to use a class map but in the naming context use a static property instead? Like:

public class RoleMap : ClassMap<Role>
{
    public RoleMap()
    {
        NamingContext(LdapApi.LdapProps.NamingContext);
        ObjectClass("Group");

        DistinguishedName(x => x.DistinguishedName);
        Map(x => x.CommonName)
            .Named("cn");
        Map(x => x.Members);
    }
}

Then I could have some method like this :

 public static void SetLdapProperties(string namingContext)
        {
            LdapApi.LdapProps.NamingContext = "context here";

            //set LDAP Configuration and connection factory

        }

Then just call that method in the application start-up in the project that references the API, do you think that would work? At least for allowing my different people who need different naming contexts to set it at run time instead of hard code it into the API.

 

Nov 29, 2011 at 6:34 AM

That's a good idea given the constraints.  That should work just fine.

Mar 16, 2012 at 9:04 PM

A better approach might be to define a base DN when the connection factory is being setup.  Not unlike specifying the database and schema name when setting up a session factory with FluentNHibernate.