Coding: Role based interfaces
I’ve read a bit about role based interfaces but I’ve never really quite understood how the idea could be applied into our code - this week my colleague Matt Dunn has been teaching me.
We had a requirement to show some content on every page of the website we’re working on. The content would be slightly different depending on which business process you’re doing.
Our first solution made use of an already defined 'BusinessType' property which allowed us to work out which content we needed to create.
public class ApplicationController : Controller
{
public string BusinessType
{
get { return GetType().Replace("Controller", ""); }
}
public override void OnActionExecuting(ActionExecutingContext context)
{
base.OnActionExecuting(context);
ViewData["SomeViewDataKey"] = CreateThatViewDataStuff();
}
private ViewDataStuff CreateThatViewDataStuff()
{
return new ViewDataStuff
{
Content = BuildContent()
// and so on
};
}
private Content BuildContent()
{
if(BusinessType == "BusinessType1")
{
return CreateContentForBusinessType1();
}
else if(BusinessType == "BusinessType2")
{
return CreateContentForBusinessType2();
}
else
{
return CreateContentForEveryOtherBusinessType();
}
}
}
public class BusinessType1Controller : ApplicationController { }
public class BusinessType2Controller : ApplicationController { }
The problem is that it’s really hacky, way too much is going on in the ApplicationController and every time we have a business type which has different content we have to add to the mess.
We decided to make use of polymorphism to make the solution a bit cleaner and my initial thought was that we could just create a 'CreateContent' method on the ApplicationController and then override that if necessary in the other controllers.
Matt suggested that we should take this a step further and define that method as part of an interface called 'IContentFactory'.
With this approach we would then be able to just pass a reference to that interface into the 'ViewDataStuff' class which can delegate to the 'IContentFactory' when the content is requested.
In this case in the 'CreateContent' methods we were making network calls to retrieve data so it allowed us to delay these calls until strictly necessary.
public class ApplicationController : Controller, IContentFactory
{
public override void OnActionExecuting(ActionExecutingContext context)
{
base.OnActionExecuting(context);
ViewData["SomeViewDataKey"] = CreateThatViewDataStuff();
}
private ViewDataStuff CreateThatViewDataStuff()
{
return new ViewDataStuff(this)
{
// and so on
};
}
public virtual Content CreateContent()
{
return new Content(...);
}
}
public class BusinessType1Controller : ApplicationController
{
public override Content CreateContent()
{
// creates the Content object slightly differently.
return new Content(...);
}
}
public class ViewDataStuff
{
private readonly IContentFactory content;
public ViewDataStuff(IContentFactory content)
{
this.content = content;
}
public Content Content
{
get { return content.CreateContent(); }
}
}
We thought about using Udi Dahan’s naming notation for interfaces but the factory pattern is quite well known so it seemed like we might creation more confusion by doing that.
I think it’d be interesting to see what we’d have come up with if we’d test driven this code rather than refactored it into this pattern.
Overall though it seems quite neat as an approach and the next time we came across some similar code I was able to introduce a role based interface having been taught how to do it by Matt on this one.
About the author
I'm currently working on short form content at ClickHouse. I publish short 5 minute videos showing how to solve data problems on YouTube @LearnDataWithMark. I previously worked on graph analytics at Neo4j, where I also co-authored the O'Reilly Graph Algorithms Book with Amy Hodler.