'Blazor attribute splatting issues with CssBuilder package

I am using CssBuilder to build the CSS as described in CssBuilder docs. When i add this it in turn breaks my passed click handlers. I either get click handlers or i get CSS, not both.

ComponentUIBase

using Microsoft.AspNetCore.Components;
using System;
using System.Collections.Generic;

namespace BlazorServer.UI
{
    public class UIComponentBase : ComponentBase
    {

        [Parameter]
        public RenderFragment ChildContent { get; set; }

        [Parameter(CaptureUnmatchedValues = true)]
        public IReadOnlyDictionary<string, object> AdditionalAttributes { get; set; } = new Dictionary<string, Object>();

    }
}

Custom Button.razor component - When @attributes exists, CSS breaks and onclick works, when @attributes is removed, CSS works and onclick breaks

@inherits UIComponentBase

<button class="@BaseCss" @attributes="AdditionalAttributes">
    @ChildContent
</button>

Custom Button.cs code-behind

using BlazorComponentUtilities;
using Microsoft.AspNetCore.Components;

namespace BlazorServer.UI.Buttons
{
    public partial class Button
    {

        [Parameter]
        public string Color { get; set; } = "btn-primary";

        [Parameter]
        public bool Rounded { get; set; } = false;

        public string BaseCss =>
           new CssBuilder("btn")
            .AddClass(Color)
            .AddClass("rounded-0", when: !Rounded)
            .AddClass("rounded-2", when: Rounded)
            .AddClass("shadow-none")
            .AddClassFromAttributes(AdditionalAttributes)
            .Build();
    }
}

Parent component

    <Card Color="bg-info">
        <CardHeader>Card Header</CardHeader>
        <CardBody>
            <CardTitle>Card title</CardTitle>
            <CardTitle SubTitle>Card Subtitle</CardTitle>
            <CardText>Card text goes here...</CardText>
        </CardBody>
        <CardFooter>
            <Spacer />
            <Button Color="bg-danger">Cancel</Button>
            <Button class="ms-2" Color="bg-primary" @onclick="Test">Ok</Button>
        </CardFooter>
    </Card>

@code {
    private async void Test()
    {

        await JsRuntime.InvokeVoidAsync("alert", "Warning!11"); // Alert
    }
}


Solution 1:[1]

You need to remove the class from the splatter attributes.

I have a similar setup to you. Here's some code.

My UIComponentBase:

public abstract class UIComponentBase : ComponentBase
{
    [Parameter] public RenderFragment? ChildContent { get; set; }

    [Parameter(CaptureUnmatchedValues = true)] public IDictionary<string, object> UserAttributes { get; set; } = new Dictionary<string, object>();

    protected virtual List<string> UnwantedAttributes { get; set; } = new List<string>();

    protected Dictionary<string, object> SplatterAttributes
    {
        get
        {
            var list = new Dictionary<string, object>();
            foreach (var item in UserAttributes)
            {
                if (!UnwantedAttributes.Any(item1 => item1.Equals(item.Key)))
                    list.Add(item.Key, item.Value);
            }
            return list;
        }
    }
}

My Base class - a default div:

public class UIComponent : UIComponentBase
{

    [Parameter] public bool Show { get; set; } = true;

    [Parameter] public bool Disabled { get; set; } = false;

    [Parameter] public string? Tag { get; set; }

    [Parameter] public EventCallback<MouseEventArgs> ClickEvent { get; set; }

    protected virtual List<string> CssClasses { get; private set; } = new List<string>();

    protected virtual string HtmlTag => this.Tag ?? "div";

    protected override List<string> UnwantedAttributes { get; set; } = new List<string>() { "class" };

    protected string CssClass
        => CSSBuilder.Class()
            .AddClass(CssClasses)
            .AddClassFromAttributes(this.UserAttributes)
            .Build();

    protected override void BuildRenderTree(RenderTreeBuilder builder)
    {
        if (this.Show)
        {
            builder.OpenElement(0, this.HtmlTag);
            builder.AddMultipleAttributes(1, this.SplatterAttributes);
            if (!string.IsNullOrWhiteSpace(this.CssClass))
                builder.AddAttribute(2, "class", this.CssClass);

            if (Disabled)
                builder.AddAttribute(3, "disabled");

            if (ClickEvent.HasDelegate)
                builder.AddAttribute(4, "onclick", EventCallback.Factory.Create<MouseEventArgs>(this, ClickEvent));

            builder.AddContent(5, ChildContent);
            builder.CloseElement();
        }
    }
}

And then my button:

public class UIButton : UIComponent
{
    public UIButton()
        => this.CssClasses.Add("btn mr-1");

    protected override string HtmlTag => "button";

    protected override void BuildRenderTree(RenderTreeBuilder builder)
    {
        if (this.Show)
        {
            builder.OpenElement(0, this.HtmlTag);
            builder.AddAttribute(1, "class", this.CssClass);
            builder.AddMultipleAttributes(2, this.SplatterAttributes);

            if (!UserAttributes.ContainsKey("type"))
                builder.AddAttribute(3, "type", "button");

            if (Disabled)
                builder.AddAttribute(4, "disabled");

            if (ClickEvent.HasDelegate)
                builder.AddAttribute(5, "onclick", EventCallback.Factory.Create<MouseEventArgs>(this, ClickEvent));

            builder.AddContent(6, ChildContent);
            builder.CloseElement();
        }
    }
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1