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 !!