{"id":13629344,"url":"https://github.com/m31coding/M31.FluentAPI","last_synced_at":"2025-04-17T08:34:39.397Z","repository":{"id":144710905,"uuid":"606008447","full_name":"m31coding/M31.FluentAPI","owner":"m31coding","description":"Generate fluent builders for your C# classes with ease.","archived":false,"fork":false,"pushed_at":"2025-03-25T14:42:26.000Z","size":1319,"stargazers_count":110,"open_issues_count":4,"forks_count":6,"subscribers_count":4,"default_branch":"main","last_synced_at":"2025-03-26T18:56:22.545Z","etag":null,"topics":["code-generation","fluent","fluent-api","fluent-builder","fluent-design","fluent-interface"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/m31coding.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-02-24T11:46:18.000Z","updated_at":"2025-03-25T14:41:22.000Z","dependencies_parsed_at":null,"dependency_job_id":"9b8968fa-e8af-4633-817f-dec0dad3b6c0","html_url":"https://github.com/m31coding/M31.FluentAPI","commit_stats":null,"previous_names":[],"tags_count":10,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/m31coding%2FM31.FluentAPI","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/m31coding%2FM31.FluentAPI/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/m31coding%2FM31.FluentAPI/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/m31coding%2FM31.FluentAPI/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/m31coding","download_url":"https://codeload.github.com/m31coding/M31.FluentAPI/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":249326186,"owners_count":21251735,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["code-generation","fluent","fluent-api","fluent-builder","fluent-design","fluent-interface"],"created_at":"2024-08-01T22:01:08.043Z","updated_at":"2025-04-17T08:34:39.370Z","avatar_url":"https://github.com/m31coding.png","language":"C#","readme":"# Fluent APIs in C#\n\n![M31.FluentApi logo](https://raw.githubusercontent.com/m31coding/M31.FluentAPI/main/media/fluent-api-logo-256.jpg)\n\nEverybody wants to use fluent APIs but writing them is tedious. With this library providing fluent APIs for your classes becomes a breeze. Simply annotate them with attributes and the source code for the fluent API will be generated. The fluent API library leverages incremental source code generation at development time and your IDE will offer you the corresponding code completion immediately.\n\nThe generated code follows the builder design pattern and allows you to construct objects step by step. This approach avoids big constructors and results in very readable code. \n\n[![license](https://img.shields.io/badge/license-MIT-brightgreen)](https://github.com/m31coding/M31.BinarySearchTrees/blob/master/LICENSE)\n[![.net version](https://img.shields.io/badge/.NET-6.0-6D429C)](https://dotnet.microsoft.com/en-us/)\n[![version](https://img.shields.io/nuget/v/M31.FluentApi)](https://www.nuget.org/packages/M31.FluentApi/)\n[![CI](https://github.com/m31coding/M31.FluentAPI/actions/workflows/ci.yml/badge.svg)](https://github.com/m31coding/M31.FluentAPI/actions/workflows/ci.yml)\n[![m31coding](https://img.shields.io/badge/www-m31coding.com-34345B)](https://www.m31coding.com)\n\nAccompanying blog post: [www.m31coding.com\u003eblog\u003efluent-api](https://www.m31coding.com/blog/fluent-api.html)\n\n## Features\n          \n- Builder code generation controlled by attributes\n- Stepwise object construction\n- Special handling for boolean, collection, and nullable members\n- Nested fluent APIs via lambda methods\n- Custom builder methods\n- Optional (skippable) builder methods\n- Forking and branching capabilities\n- Support for returning arbitrary types\n- Support for inheritance, generics, and partial classes\n\n## Installing via NuGet\n\nInstall the latest version of the package `M31.FluentApi` via your IDE or use the package manager console:\n\n```\nPM\u003e Install-Package M31.FluentApi\n```\n\nA package reference will be added to your `csproj` file. Moreover, since this library provides code via source code generation, consumers of your project don't need the reference to `M31.FluentApi`. Therefore, it is recommended to use the `PrivateAssets` metadata tag:\n\n```xml\n\u003cPackageReference Include=\"M31.FluentApi\" Version=\"1.10.0\" PrivateAssets=\"all\"/\u003e\n```\n\nIf you would like to examine the generated code, you may emit it by adding the following lines to your `csproj` file:\n\n```xml\n\u003cPropertyGroup\u003e\n    \u003cCompilerGeneratedFilesOutputPath\u003e$(BaseIntermediateOutputPath)Generated\u003c/CompilerGeneratedFilesOutputPath\u003e\n    \u003cEmitCompilerGeneratedFiles\u003etrue\u003c/EmitCompilerGeneratedFiles\u003e\n\u003c/PropertyGroup\u003e\n```\n\nThe code can then be found in the `obj/Generated` folder.\n\n## Usage\n\nIf you use this library for the first time I recommend that you read the storybook: \n\n- [01 Basics](src/M31.FluentApi.Storybook/01_Basics.cs)\n- [02 Control attributes](src/M31.FluentApi.Storybook/02_ControlAttributes.cs)\n\nMoreover, you may find several Fluent API examples and their [usage](src/ExampleProject/Program.cs) in the example project:\n\n- [Student](src/ExampleProject/Student.cs)\n- [Person](src/ExampleProject/Person.cs)\n- [Order](src/ExampleProject/Order.cs)\n- [HashCode](src/ExampleProject/HashCode.cs)\n- [Node](src/ExampleProject/Node.cs)\n- [DockerFile](src/ExampleProject/DockerFile.cs)\n- ...\n\nHere is an example from the introduction to the basics:\n\n```cs\n[FluentApi]\npublic class Student\n{\n    [FluentMember(0, \"Named\", 0)]\n    public string FirstName { get; private set; }\n\n    [FluentMember(0, \"Named\", 1)]\n    public string LastName { get; private set; }\n\n    [FluentMember(1, \"OfAge\")]\n    public int Age { get; private set; }\n\n    [FluentMethod(1)]\n    private void BornOn(DateOnly dateOfBirth)\n    {\n        DateOnly today = DateOnly.FromDateTime(DateTime.Today);\n        int age = today.Year - dateOfBirth.Year;\n        if (dateOfBirth \u003e today.AddYears(-age)) age--;\n        Age = age;\n    }\n\n    [FluentMember(2, \"InSemester\")]\n    [FluentDefault(\"WhoStartsUniversity\")]\n    public int Semester { get; private set; } = 0;\n\n    [FluentMember(3, \"LivingIn\")]\n    [FluentDefault(\"LivingInBoston\")]\n    [FluentNullable(\"InUnknownCity\")]\n    public string? City { get; private set; } = \"Boston\";\n\n    [FluentPredicate(4, \"WhoIsHappy\", \"WhoIsSad\")]\n    [FluentNullable(\"WithUnknownMood\")]\n    public bool? IsHappy { get; private set; }\n\n    [FluentCollection(5, \"Friend\", \"WhoseFriendsAre\", \"WhoseFriendIs\", \"WhoHasNoFriends\")]\n    public IReadOnlyCollection\u003cstring\u003e Friends { get; private set; }\n }\n```\n\n![fluent-api-usage](https://raw.githubusercontent.com/m31coding/M31.FluentAPI/main/media/fluent-api.gif)\n\nYou may have a look at the generated code for this example: [CreateStudent.g.cs](src/M31.FluentApi.Tests/CodeGeneration/TestClasses/StudentClass/CreateStudent.g.cs). Note that if you use private members or properties with a private set accessor, as it is the case in this example, the generated code will use reflection to set the properties.\n\n## Attributes\n\nThe attributes `FluentApi` and `FluentMember` are all you need in order to get started. \n\nThe attributes `FluentPredicate` and `FluentCollection` can be used instead of the `FluentMember` attribute if the decorated member is a boolean or a collection, respectively.\n\n`FluentDefault` and `FluentNullable` can be used in combination with these attributes to set a default value or null, respectively. \n\nThe `FluentMethod` attribute is used for custom builder method implementations.\n\nThe control attribute `FluentSkippable` allows builder methods to be optional, while `FluentContinueWith` indicates a jump to the specified builder step. `FluentBreak` stops the builder, and `FluentReturn` allows returning arbitrary types and values within the generated API.\n\n\n### FluentApi\n\n```cs\nFluentApi(string builderClassName = \"Create{Name}\")\n```\nUse this attribute for your class / struct / record. The optional parameter allows you to specify the name of the builder class that will be generated. Within the argument the template `{Name}` can be used, which will be replaced by the name of your decorated type.\n\n```cs\n[FluentApi]\npublic class Student\n```\n\nYou can create instances by statically accessing the generated `CreateStudent` class:\n\n```cs\nStudent alice = CreateStudent.WithFirstName(\"Alice\")...\n```\n\nAlternatively, you can call `InitialStep` to get a new builder instance:\n\n```cs\nICreateStudent createStudent = CreateStudent.InitialStep();\nStudent alice = createStudent.WithFirstName(\"Alice\")...\n```\n\n### FluentMember\n\n```cs\nFluentMember(int builderStep, string method = \"With{Name}\", int parameterPosition = 0)\n```\n\nUse this attribute for fields and properties of your class. They can be private but properties must have a set accessor. The `builderStep` parameter specifies the step in which the member can be set. With the `method` parameter you can specify the name of the builder method.\n\n```cs\n[FluentMember(0)]\npublic string FirstName { get; private set; }\n```\n\n```cs\n...WithFirstName(\"Alice\")...\n```\n\nIf two `FluentMember` attributes with the same builder step and equal method names are specified, a compound method will be created, which is a builder method that sets multiple properties at once. For compounds the position of the parameters can be controlled by the parameter `parameterPosition`.\n\n```cs\n[FluentMember(0, \"Named\", 0)]\npublic string FirstName { get; private set; }\n\n[FluentMember(0, \"Named\", 1)]\npublic string LastName { get; private set; }\n```\n\n```cs\n...Named(\"Alice\", \"King\")...\n```\n               \nIf the decorated member has its own Fluent API, an additional lambda method is generated, e.g.\n\n```cs\n[FluentMember(1)]\npublic Address Address { get; private set; }\n```\n\n```cs\n...WithAddress(a =\u003e a.WithHouseNumber(\"108\").WithStreet(\"5th Avenue\").InCity(\"New York\"))...\n```\n\n\n### FluentPredicate\n\n```cs\nFluentPredicate(int builderStep, string method = \"{Name}\", string negatedMethod = \"Not{Name}\")\n```\n\nCan be used instead of the `FluentMember` attribute if the decorated member is of type `bool`. This attribute generates three methods, one for setting the value of the member to `true`, one for setting it to `false`, and one for passing the boolean value.\n\n```cs\n[FluentPredicate(4, \"WhoIsHappy\", \"WhoIsSad\")]\npublic bool IsHappy { get; private set; }\n```\n\n```cs\n...WhoIsHappy()...\n...WhoIsSad()...\n...WhoIsHappy(true)...\n```\n\n\n### FluentCollection\n\n```cs\nFluentCollection(\n    int builderStep,\n    string singularName,\n    string withItems = \"With{Name}\",\n    string? withItem = \"With{SingularName}\",\n    string? withZeroItems = \"WithZero{Name}\")\n```\n\nCan be used instead of the `FluentMember` attribute if the decorated member is a collection. This attribute generates methods for setting multiple items, one item and zero items. The supported collection types can be seen in the source file [CollectionInference.cs](src/M31.FluentApi.Generator/SourceGenerators/Collections/CollectionInference.cs). If `withItem` or `withZeroItems` is set to `null`, the corresponding method will not be generated.\n\n```cs\n[FluentCollection(5, \"Friend\", \"WhoseFriendsAre\", \"WhoseFriendIs\", \"WhoHasNoFriends\")]\npublic IReadOnlyCollection\u003cstring\u003e Friends { get; private set; }\n```\n\n```cs\n...WhoseFriendsAre(new string[] { \"Bob\", \"Carol\", \"Eve\" })...\n...WhoseFriendsAre(\"Bob\", \"Carol\", \"Eve\")...\n...WhoseFriendIs(\"Alice\")...\n...WhoHasNoFriends()...\n```\n\nIf the element type of the decorated member has its own Fluent API, additional lambda methods are generated, e.g.\n\n```cs\n[FluentCollection(1, \"Address\")]\npublic IReadOnlyCollection\u003cAddress\u003e Addresses { get; private set; }\n```\n\n```cs\n...WithAddresses(\n    a =\u003e a.WithHouseNumber(\"108\").WithStreet(\"5th Avenue\").InCity(\"New York\"),\n    a =\u003e a.WithHouseNumber(\"42\").WithStreet(\"Maple Ave\").InCity(\"Boston\"))...\n...WithAddress(a =\u003e a.WithHouseNumber(\"82\").WithStreet(\"Friedrichstraße\").InCity(\"Berlin\"))...\n```\n\n\n### FluentDefault\n\n```cs\nFluentDefault(string method = \"WithDefault{Name}\")\n```\n\nCan be used for fields and properties in addition to other attributes. When the generated builder method is called the member will keep its initial value.\n\n```cs\n[FluentMember(3, \"LivingIn\")]\n[FluentDefault(\"LivingInBoston\")]\n[FluentNullable(\"InUnknownCity\")]\npublic string? City { get; private set; } = \"Boston\";\n```\n\n```cs\n...LivingInBoston()... // City is not changed\n```\n\n\n### FluentNullable\n\n```cs\nFluentNullable(string method = \"Without{Name}\")\n```\n\nCan be used for fields and properties in addition to other attributes. Generates a builder method that sets the member to `null`.\n\n```cs\n[FluentMember(3, \"LivingIn\")]\n[FluentDefault(\"LivingInBoston\")]\n[FluentNullable(\"InUnknownCity\")]\npublic string? City { get; private set; } = \"Boston\";\n```\n\n```cs\n...InUnknownCity()... // City is set to null\n```\n\n\n### FluentMethod\n\n```cs\nFluentMethod(int builderStep, string method = \"{Name}\")\n```\n\nUse this attribute on methods to provide a custom implementation for setting values or triggering additional behavior. The decorated method must return `void`.\n\n```cs\n[FluentMethod(1)]\nprivate void BornOn(DateOnly dateOfBirth)\n{\n    DateOnly today = DateOnly.FromDateTime(DateTime.Today);\n    int age = today.Year - dateOfBirth.Year;\n    if (dateOfBirth \u003e today.AddYears(-age)) age--;\n    Age = age;\n}\n```\n\n```cs\n...BornOn(new DateOnly(2003, 6, 24))...\n```\n\n\n### FluentSkippable\n\n```cs\nFluentSkippable()\n```\n\nCan be used at all steps on fields, properties, and methods to create an optional builder method. The generated API will offer the method but it does not have to be called.\n\n```cs\n[FluentMember(0)]\npublic string FirstName { get; private set; }\n\n[FluentMember(1)]\n[FluentSkippable]\npublic string? MiddleName { get; private set; }\n\n[FluentMember(2)]\npublic string LastName { get; private set; }\n```\n\n```cs\n...WithFirstName(\"Bob\").WithLastName(\"Bishop\")...\n...WithFirstName(\"Alice\").WithMiddleName(\"Sophia\").WithLastName(\"King\")...\n```\n\n\n### FluentContinueWith\n\n```cs\nFluentContinueWith(int builderStep)\n```\n\nCan be used at all steps on fields, properties, and methods to jump to a specific builder step. Useful for branching.\n\n```cs\n[FluentMethod(3)]\n[FluentContinueWith(7)]\nprivate void WhoIsADigitalNomad()\n{\n    IsDigitalNomad = true;\n}\n\n// ...\n\n[FluentMethod(7)]\nprivate void LivingInCity(string city)\n{\n    City = city;\n}\n```\n\n```cs\n...WhoIsADigitalNomad().LivingInCity(\"Berlin\")...\n```\n\n\n### FluentBreak\n\n```cs\nFluentBreak()\n```\n\nCan be used at all steps on fields, properties, and methods to stop the builder. Only relevant for non-linear APIs that make use of `FluentContinueWith`.\n\n```cs\n[FluentMethod(3)]\n[FluentBreak]\nprivate void WhoseAddressIsUnknown()\n{\n}\n```\n\n```cs\n...WhoseAddressIsUnknown();\n```\n\n### FluentReturn\n\nAllows the builder to respect the return value of the decorated method, enabling the return of arbitrary types and values within the generated API. If a void method is decorated with this attribute, the builder method will also return void.\n                \n```cs\n[FluentMethod(1)]\n[FluentReturn]\npublic string ToJson()\n{\n    return JsonSerializer.Serialize(this);\n}\n```\n\n```cs\nstring serialized = ...ToJson();\n```\n\n\n## Miscellaneous\n\n\n### Forks\n\nTo create forks specify builder methods at the same builder step. The resulting API offers all specified methods at this step but only one can be called:\n\n```cs\n[FluentMember(1, \"OfAge\")]\npublic int Age { get; private set; }\n\n[FluentMethod(1)]\nprivate void BornOn(DateOnly dateOfBirth)\n{\n    DateOnly today = DateOnly.FromDateTime(DateTime.Today);\n    int age = today.Year - dateOfBirth.Year;\n    if (dateOfBirth \u003e today.AddYears(-age)) age--;\n    Age = age;\n}\n```\n\n```cs\n...OfAge(22)...\n...BornOn(new DateOnly(2002, 8, 3))...\n```\n\n\n### Lambda pattern\n                       \nInstances of Fluent API classes can be created and passed into methods of other classes using the lambda pattern. For example, given a `University` class that needs to be augmented with an `AddStudent` method, the following code demonstrates the lambda pattern:\n\n```cs\npublic void AddStudent(Func\u003cCreateStudent.ICreateStudent, Student\u003e createStudent)\n{\n    Student student = createStudent(CreateStudent.InitialStep());\n    students.Add(student);\n}\n```\n\n```cs\nuniversity.AddStudent(s =\u003e s.Named(\"Alice\", \"King\").OfAge(22)...);\n```\n\nNote that if you want to set a member of a Fluent API class, you can simply use `FluentMember` or `FluentCollection` instead of the pattern above.\n\n\n## Problems with the IDE\n\nSince code generation is potentially triggered with every single key stroke, there are sometimes situations where the code completion index of the IDE does not keep up with all the changes.\n\nIn particular, if your IDE visually indicates that there are errors in your code but it compiles and runs just fine, try the following things:\n\n- Rebuild the project or the whole solution\n- Unload and reload the project\n- Close and reopen the IDE\n- Remove the .vs folder (Visual Studio) or the .idea folder (Rider)\n  \n\n## Support and Contribution\n\nThis library is free. If you find it valuable and wish to express your support, please leave a star. You are kindly invited to contribute. If you see the possibility for enhancement, please create a GitHub issue and you will receive timely feedback.\n\nHappy coding!","funding_links":[],"categories":["Content","Source Generators","Source Generator","Identifiers"],"sub_categories":["55. [M31.FluentAPI](https://ignatandrei.github.io/RSCG_Examples/v2/docs/M31.FluentAPI) , in the [EnhancementClass](https://ignatandrei.github.io/RSCG_Examples/v2/docs/rscg-examples#enhancementclass) category","Patterns","GUI - other"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fm31coding%2FM31.FluentAPI","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fm31coding%2FM31.FluentAPI","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fm31coding%2FM31.FluentAPI/lists"}