Tuesday, May 25, 2010

Creating a Fluent Interface with C#




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!


Shout it

kick it on DotNetKicks.com

Related Posts Plugin for WordPress, Blogger...