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

blog comments powered by Disqus
Related Posts Plugin for WordPress, Blogger...