Skip to content

Excessive Boilerplate code when using Model Binding. Am i doing something wrong? #2329

@thesn10

Description

@thesn10

I am building a command line app with 14+ options per command. The docs reccommend to use model binding if you have more than 8 options, but when i implemented the Binder, i became more and more unsure if i was doing this correctly, because there was just soo much boilerplate. Am i doing something wrong here? How can i properly handle 8+ arguments?

Excessive boilerplate example

You can see just the sheer amount of unnecessary boilerplate just passing around the options objects

public class ThumbnailOptionsBinder : BinderBase<ThumbnailOptions>
{
    private readonly Option<TimeSpan> _startTimeOption;
    private readonly Option<TimeSpan> _intervalOption;
    private readonly Option<TimeSpan> _endTimeOption;
    private readonly Option<string> _filenameOption;
    private readonly Option<bool> _fastModeOption;
    private readonly Option<string> _webVTTOption;
    private readonly Option<string> _imagePathOption;
    private readonly Option<int> _frameWidthOption;
    private readonly Option<int> _frameHeightOption;
    private readonly Option<int> _columnsOption;
    private readonly Option<int> _rowsOption;
    private readonly Option<int> _borderWidthOption;
    private readonly Option<string> _backgroundGradientStartOption;
    private readonly Option<string> _backgroundGradientEndOption;

    public ThumbnailOptionsBinder(
        Option<TimeSpan> startTimeOption,
        Option<TimeSpan> intervalOption,
        Option<TimeSpan> endTimeOption,
        Option<string> filenameOption,
        Option<bool> fastModeOption,
        Option<string> webVTTOption,
        Option<string> imagePathOption,
        Option<int> frameWidthOption,
        Option<int> frameHeightOption,
        Option<int> columnsOption,
        Option<int> rowsOption,
        Option<int> borderWidthOption,
        Option<string> backgroundGradientStartOption,
        Option<string> backgroundGradientEndOption)
    {
        _startTimeOption = startTimeOption;
        _intervalOption = intervalOption;
        _endTimeOption = endTimeOption;
        _filenameOption = filenameOption;
        _fastModeOption = fastModeOption;
        _webVTTOption = webVTTOption;
        _imagePathOption = imagePathOption;
        _frameWidthOption = frameWidthOption;
        _frameHeightOption = frameHeightOption;
        _columnsOption = columnsOption;
        _rowsOption = rowsOption;
        _borderWidthOption = borderWidthOption;
        _backgroundGradientStartOption = backgroundGradientStartOption;
        _backgroundGradientEndOption = backgroundGradientEndOption;
    }

    protected override ThumbnailOptions GetBoundValue(BindingContext bindingContext) =>
        new ThumbnailOptions
        {
            StartTime = bindingContext.ParseResult.GetValueForOption(_startTimeOption),
            Interval = bindingContext.ParseResult.GetValueForOption(_intervalOption),
            EndTime = bindingContext.ParseResult.GetValueForOption(_endTimeOption),
            Filename = bindingContext.ParseResult.GetValueForOption(_filenameOption),
            FastMode = bindingContext.ParseResult.GetValueForOption(_fastModeOption),
            WebVTT = bindingContext.ParseResult.GetValueForOption(_webVTTOption),
            ImagePath = bindingContext.ParseResult.GetValueForOption(_imagePathOption),
            FrameWidth = bindingContext.ParseResult.GetValueForOption(_frameWidthOption),
            FrameHeight = bindingContext.ParseResult.GetValueForOption(_frameHeightOption),
            Columns = bindingContext.ParseResult.GetValueForOption(_columnsOption),
            Rows = bindingContext.ParseResult.GetValueForOption(_rowsOption),
            BorderWidth = bindingContext.ParseResult.GetValueForOption(_borderWidthOption),
            BackgroundGradientStart = bindingContext.ParseResult.GetValueForOption(_backgroundGradientStartOption),
            BackgroundGradientEnd = bindingContext.ParseResult.GetValueForOption(_backgroundGradientEndOption),
            // Add other rendering options here
        };
}

class Program
{
    static async Task<int> Main(string[] args)
    {
        var rootCommand = new RootCommand();

        var startTimeOption = new Option<TimeSpan>("--start-time", () => TimeSpan.FromSeconds(60), "Start time");
        var intervalOption = new Option<TimeSpan>("--interval", () => TimeSpan.FromSeconds(5), "Interval");
        var endTimeOption = new Option<TimeSpan>("--end-time", () => TimeSpan.FromMinutes(4), "End time");
        var filenameOption = new Option<string>("--filename", () => Path.GetFullPath("./thumbnail_systemdrawing.bmp"), "Output filename");
        var fastModeOption = new Option<bool>("--fast-mode", "Use fast mode");
        var webvttOption = new Option<string>("--webvtt", "WebVTT file");
        var imagePathOption = new Option<string>("--image-path", "Image path for WebVTT");
        var frameWidthOption = new Option<int>("--frame-width", () => 192, "Frame width");
        var frameHeightOption = new Option<int>("--frame-height", () => 108, "Frame height");
        var columnsOption = new Option<int>("--columns", () => 4, "Columns for tiling");
        var rowsOption = new Option<int>("--rows", () => 4, "Rows for tiling");
        var borderWidthOption = new Option<int>("--border-width", () => 8, "Border width");
        var backgroundGradientStartOption = new Option<string>("--background-gradient-start", "Background gradient start color");
        var backgroundGradientEndOption = new Option<string>("--background-gradient-end", "Background gradient end color");

        rootCommand.Add(startTimeOption);
        rootCommand.Add(intervalOption);
        rootCommand.Add(endTimeOption);
        rootCommand.Add(filenameOption);
        rootCommand.Add(fastModeOption);
        rootCommand.Add(webvttOption);
        rootCommand.Add(imagePathOption);
        rootCommand.Add(frameWidthOption);
        rootCommand.Add(frameHeightOption);
        rootCommand.Add(columnsOption);
        rootCommand.Add(rowsOption);
        rootCommand.Add(borderWidthOption);
        rootCommand.Add(backgroundGradientStartOption);
        rootCommand.Add(backgroundGradientEndOption);

        var thumbnailOptionsBinder = new ThumbnailOptionsBinder(
            startTimeOption,
            intervalOption,
            endTimeOption,
            filenameOption,
            fastModeOption,
            webvttOption,
            imagePathOption,
            frameWidthOption,
            frameHeightOption,
            columnsOption,
            rowsOption,
            borderWidthOption,
            backgroundGradientStartOption,
            backgroundGradientEndOption
        );

        rootCommand.SetHandler(async (thumbnailOptions) =>
        {
            await Run(thumbnailOptions);
        }, thumbnailOptionsBinder);

        return await rootCommand.InvokeAsync(args);
    }
}

Problems of this code:

  • Excessive Code Redundancy: The constructor of the Binder and the subsequent setup of options in the Program class are excessively redundant. This violates the DRY (Don't Repeat Yourself) principle and makes the codebase unnecessarily complex.

  • Maintenance Nightmare: Any change or addition to the command line options requires modifications in multiple places, making maintenance a nightmare. Developers should not have to tediously update both the constructor and the Program class for each option.

  • Readability and Clarity: The readability of the code is compromised due to the extensive setup of options and bindings, making it hard for developers to quickly grasp the functionality.

Am i doing something wrong here? How can i properly handle 8+ arguments?
Is this really how the model binding should be used? If, yes, then I urge the maintainers to provide a more elegant and maintainable solution for setting up command line options and binders, as this much boilerplate just doesnt seem like the right way forward.
Thanks

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions