bit Source Generators


Less code to write, read and pay for

We care about how much code it takes to build an app. Every line a developer or an AI assistant writes has to be read, reviewed and kept working, and with AI every line is also tokens you pay for. bit Source Generators write the repetitive code while your app compiles, so you and your assistant only write what's specific to your app.
dotnet add package Bit.SourceGenerators
Every bit Boilerplate project has it already.

Why less code matters
  • Lower AI costs. An assistant reads and writes fewer lines, so it uses fewer tokens, finishes sooner and has less room to get things wrong.
  • Faster reviews. What's left to review is the code that matters.
  • Never out of date. Generated code follows your declarations on every build, so an API client or a constructor can't drift from what it serves.
  • Faster apps. Generated code does at compile time what would otherwise take reflection at runtime.
API clients, written for you
In a bit Boilerplate project, an API is declared once, as an interface the server and the client share:
[Route("api/[controller]/[action]/")]
public interface IProductController : IAppController
{
    [HttpGet("{id}")]
    Task<ProductDto> Get(Guid id, CancellationToken cancellationToken);

    [HttpPost]
    Task<ProductDto> Create(ProductDto dto, CancellationToken cancellationToken);
}
The server's controller implements it, and the generator writes the client's side of every method: the URL, the query string, the JSON body and the response. services.AddTypedHttpClients() registers them all, and the client injects the interface, here with [AutoInject] from the next section, and calls the API like a local service:
[AutoInject] private IProductController productController = default!;

var product = await productController.Get(id, CurrentCancellationToken);
  • Route and query parameters, JSON bodies and streamed IAsyncEnumerable responses all work.
  • While a page prerenders, the response the server already fetched is handed to the browser, so the page doesn't ask twice.
  • A route template that isn't valid fails the build with BITGEN003, rather than a request at runtime.
It builds on the types a bit Boilerplate project defines, so it comes with bit Boilerplate. Its wiki covers the rest.
[AutoInject]
Instead of a constructor that lists every service and assigns each one, mark the fields:
public partial class OrderService
{
    [AutoInject] private IOrderController orderController = default!;
    [AutoInject] private ILogger<OrderService> logger = default!;
}
In a class, the generator writes the constructor, and passes on whatever a base class injects as well. In a Blazor component, it writes the [Inject] property behind each field, so a component injects services the same way. The class has to be partial, and BITGEN001 says so when it isn't.

The attribute is your project's own, in the Microsoft.Extensions.DependencyInjection namespace:
namespace Microsoft.Extensions.DependencyInjection;

[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, Inherited = false, AllowMultiple = false)]
public sealed class AutoInjectAttribute : Attribute
{
}
Fast Blazor parameters
Blazor sets a component's parameters through reflection. For every partial component with [Parameter] or [CascadingParameter] properties, the generator writes a SetParametersAsync that assigns each one directly instead:
public override Task SetParametersAsync(ParameterView parameters)
{
    var __hasUnknownParameters = false;
    foreach (var parameter in parameters)
    {
        switch (parameter.Name)
        {
            case nameof(Title):
               Title = (string)parameter.Value!;
               break;
            default:
               __hasUnknownParameters = true;
               break;
        }
    }

    return base.SetParametersAsync(__hasUnknownParameters ? parameters : ParameterView.Empty);
}
There's nothing to write but partial, and components render faster, most of all in the browser, where reflection costs the most. A component that declares its own SetParametersAsync keeps it.