So I’ve been using fluent interfaces for a while now and I’ve really grown to like them. It made me want to write one, so I did. I made a simple interface for creating an LDAP URL. It’s really basic, but I thought it was cool nonetheless. Here’s what I ended up doing…
I wrote a test of how I wanted it to read, which looked like this:
[Test]
public void LDAP_WithBaseDN_WithHost_Should_Return_Valid_URL()
{
var ldapurl = new LDAP()
.WithBaseDN(dc => "ad", dc => "derans", dc => "lab")
.WithHost("noc-dc01")
.URL;
Assert.That(ldapurl, Is.EqualTo("LDAP://noc-dc01/dc=ad,dc=derans,dc=lab"));
}
So what does this look like in inteface form…
public interface ILDAP
{
ILDAP WithBaseDN(params Func<string, string>[] dnitems);
ILDAP WithHost(string hostName);
string URL { get; }
}
As you can see here, I’m using one of my favorite new methods to map/pass data, the good ole Func<string,string>. I could pass create the WithPort, WithScope, etc, but I wanted to keep this short and simple.
So here’s the actual implementation of ILDAP:
public class LDAP : ILDAP
{
private const string _protocol = "LDAP://";
private string _ldap = _protocol;
private bool hasBaseDN;
public ILDAP WithBaseDN(params Func<string, string>[] dcitems)
{
hasBaseDN = true;
_ldap += BuildBaseDN(dcitems);
return this;
}
public ILDAP WithHost(string hostName)
{
if (_ldap.Length > _protocol.Length)
_ldap = _protocol + hostName + "/" + _ldap.Remove(0, _protocol.Length);
else
_ldap += hostName + "/";
return this;
}
public string URL
{
get
{
if (!hasBaseDN)
throw new InvalidOrMissingBaseDN();
return _ldap;
}
}
private static string BuildBaseDN(IEnumerable<Func<string, string>> dcitems)
{
var baseDN = "";
foreach (var dc in dcitems)
{
var dnitem = dc.Method.GetParameters()[0].Name;
if (!Enum.IsDefined(typeof(LdapOptions), dnitem))
throw new InvalidOrMissingBaseDN();
baseDN += "," + dnitem + "=" + dc.Invoke(null);
}
return baseDN.Remove(0, 1);
}
}
One thing about being fluent is that you don’t know which order someone is going to do something in. If someone knows how to handle this, that would be great. I suppose I could look in the NUnit code or fluent NHibernate, but haven’t really had a chance to do a whole lot of research into how to do this…so hopefully this is an okay start :)
So here’s what the LdapOptions looks like:
internal enum LdapOptions
{
dc, cn, ou
}
Not a great name, but it works for now. Here are all the tests:
[TestFixture]
public class LDAP_Tests
{
[Test]
public void LDAP_WithBaseDN_WithHost_Should_Return_Valid_URL()
{
var ldapurl = new LDAP()
.WithBaseDN(dc => "ad", dc => "derans", dc => "lab")
.WithHost("noc-dc01")
.URL;
Assert.That(ldapurl, Is.EqualTo("LDAP://noc-dc01/dc=ad,dc=derans,dc=lab"));
}
[Test, ExpectedException(typeof(InvalidOrMissingBaseDN))]
public void WithBaseDN_Should_Throw_InvalidOrMissingBaseDN_If_BaseDN_Is_Invalid()
{
var ldap = new LDAP().WithBaseDN(dc => "ad", test => "test", dc => "derans", dc => "lab");
}
[Test, ExpectedException(typeof(InvalidOrMissingBaseDN))]
public void URL_Should_Throw_InvalidOrMissingBaseDN_If_BaseDN_Is_Invalid()
{
var ldap = new LDAP().URL;
}
[Test]
public void WithBaseDN_Should_Return_Valid_LDAP_URL()
{
var ldapurl = new LDAP().WithBaseDN(cn => "employees", dc => "ad", dc => "derans", dc => "lab").URL;
Assert.That(ldapurl, Is.EqualTo("LDAP://cn=employees,dc=ad,dc=derans,dc=lab"));
}
[Test]
public void WithHost_And_WithBaseDN_Should_Return_Valid_LDAP_URL()
{
var ldapurl = new LDAP().WithHost("noc-dc01").WithBaseDN(cn => "employees", dc => "ad", dc => "derans", dc => "lab").URL;
Assert.That(ldapurl, Is.EqualTo("LDAP://noc-dc01/cn=employees,dc=ad,dc=derans,dc=lab"));
}
}
That’s it. Please let me know if you see ways to improve on the fluent goodness. Thanks for reading!