Designing a flexible Fluent API - Part 3
In the previous post, while designing our API we stumbled on a flexibility problem. How do we add common methods to our API builders without compromising on flexibility?
We are going to use C#’s extension methods. Here is how:
public interface ICustomCssEnabledBuilder<TBuilder>
{
TBuilder GetBuilderInstance();
ICustomCssEnabledComponent GetComponent();
}
public static class CustomCssEnabledBuilderExtensions
{
public static TBuilder AddCssClass<TBuilder>
(this ICustomCssEnabledBuilder<TBuilder> builder, string cssClass)
{
builder.GetComponent().CssClasses.Add(cssClass);
return builder.GetBuilderInstance();
}
}
Now when a builder needs to support adding a css class to a css enabled component, it just has to implement the ICustomCssEnabledBuilder interface.
public class TextBoxBuilder : ICustomCssEnabledBuilder<TextBoxBuilder>
{
TextBox _textBox;
public TextBoxBuilder(TextBox textBox)
{
_textBox = textBox;
}
[EditorBrowsable(EditorBrowsableState.Never)]
public TextBoxBuilder GetBuilderInstance()
{
return this;
}
[EditorBrowsable(EditorBrowsableState.Never)]
public ICustomCssEnabledComponent GetComponent()
{
return _textBox;
}
//Textbox specific option
public TextBoxBuilder Name(string name)
{
_textBox.Name = name;
return this;
}
//code omitted
}
Now our builder can be used like this
panel.AddTextBoxFor(model => model.TextProp2)
.TextBoxSpecificOption(...)
.AddCssClass("custom-css-class")
.OtherTextBoxSpecificOption(...);
The same technique can be used for the events API.
public interface IEventEnabledBuilder<TBuilder, TEventBuilder>
{
TBuilder GetBuilderInstance();
TEventBuilder GetEventBuilder();
}
public static class EventEnabledBuilderExtensions
{
public static TBuilder Events<TBuilder, TEventBuilder>(this IEventEnabledBuilder<TBuilder, TEventBuilder> builder,
Action<TEventBuilder> eventBuilderExpression)
{
var eventBuilder = builder.GetEventBuilder();
eventBuilderExpression(eventBuilder);
return builder.GetBuilderInstance();
}
}
public class TextBoxEventBuilder
{
private TextBox _textBox;
public TextBoxEventBuilder(TextBox textBox)
{
_textBox = textBox;
}
public TextBoxEventBuilder OnChange(string jsHandle)
{
_textBox.Events.Add(new ComponentEvent()
{
EventName = "change",
JsHandler = jsHandle
});
return this;
}
}
So our TextBoxBuilder is changed to this.
public class TextBoxBuilder : ICustomCssEnabledBuilder<TextBoxBuilder>,
IEventEnabledBuilder<TextBoxBuilder, TextBoxEventBuilder>
{
private TextBox _textBox;
public TextBoxBuilder(TextBox textBox)
{
_textBox = textBox;
}
[EditorBrowsable(EditorBrowsableState.Never)]
public TextBoxBuilder GetBuilderInstance()
{
return this;
}
[EditorBrowsable(EditorBrowsableState.Never)]
public ICustomCssEnabledComponent GetComponent()
{
return _textBox;
}
[EditorBrowsable(EditorBrowsableState.Never)]
public TextBoxEventBuilder GetEventBuilder()
{
return new TextBoxEventBuilder(_textBox);
}
//Textbox specific option
public TextBoxBuilder Name(string name)
{
_textBox.Name = name;
return this;
}
//code omitted
}
And now it can be used like this.
panel.AddTextBoxFor(model => model.TextProp2)
.TextBoxSpecificOption(...)
.AddCssClass("custom-css-class")
.OtherTextBoxSpecificOption(...)
.Events(e=> e.OnChange("jsHandlerName"));
Awesomeness achieved !!