{"id":35542403,"url":"https://github.com/neatoodotnet/neatoo","last_synced_at":"2026-04-13T04:10:18.899Z","repository":{"id":282987443,"uuid":"950314695","full_name":"NeatooDotNet/Neatoo","owner":"NeatooDotNet","description":"Neatoo is a .NET Domain Model Framework providing meta properties, parent/child relationships and business rule validation.","archived":false,"fork":false,"pushed_at":"2026-04-01T05:03:44.000Z","size":28752,"stargazers_count":3,"open_issues_count":3,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-04-03T02:45:04.621Z","etag":null,"topics":["blazor","blazor-webassembly","ddd","ddd-architecture","ddd-sample","mudblazor","rosyln","source-generator"],"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/NeatooDotNet.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,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-03-18T01:05:44.000Z","updated_at":"2026-03-30T02:25:43.000Z","dependencies_parsed_at":null,"dependency_job_id":"c966dc62-e280-42cc-95ba-2fa5ae4c29c1","html_url":"https://github.com/NeatooDotNet/Neatoo","commit_stats":null,"previous_names":["neatoodotnet/neatoo"],"tags_count":34,"template":false,"template_full_name":null,"purl":"pkg:github/NeatooDotNet/Neatoo","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/NeatooDotNet%2FNeatoo","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/NeatooDotNet%2FNeatoo/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/NeatooDotNet%2FNeatoo/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/NeatooDotNet%2FNeatoo/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/NeatooDotNet","download_url":"https://codeload.github.com/NeatooDotNet/Neatoo/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/NeatooDotNet%2FNeatoo/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31419549,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-04T20:09:54.854Z","status":"ssl_error","status_checked_at":"2026-04-04T20:09:44.350Z","response_time":60,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"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":["blazor","blazor-webassembly","ddd","ddd-architecture","ddd-sample","mudblazor","rosyln","source-generator"],"created_at":"2026-01-04T05:08:45.277Z","updated_at":"2026-04-05T00:01:58.557Z","avatar_url":"https://github.com/NeatooDotNet.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Neatoo\n\nA Domain-Driven Design framework for 3-tier .NET Blazor applications, powered by Roslyn source generators.\n\n[![NuGet](https://img.shields.io/nuget/v/Neatoo.svg)](https://www.nuget.org/packages/Neatoo/)\n\n## Why Neatoo?\n\nBlazor compiles the same .NET code to both client and server. The same types, the same definitions, the same runtime — on both sides of the wire. So why are you still writing DTOs, mapping layers, and serialization boilerplate as if they were different?\n\nThey aren't. Neatoo takes that insight to its conclusion.\n\n**Your domain model is the contract.** Define your entities, validation rules, and business logic once. Neatoo's source generators wire up property backing fields, change tracking, and validation triggers at compile time. When it's time to save, RemoteFactory transfers domain object state to the server, executes your persistence logic, and returns the result — no DTOs, no mapping, no translation layer. Every operation flows through a single controller endpoint. No more writing a new controller method for every create, fetch, update, and delete.\n\nThe domain model you bind to your Blazor form is the same object that validates user input, tracks what changed, and persists to the database. One model, one endpoint, front to back.\n\n## Key Features\n\n- **One model, front to back** — Your domain model binds to the Blazor UI, validates input, tracks changes, and persists to the database. No DTOs, no mapping layers, no translation.\n- **Transparent client-server transfer** — RemoteFactory moves domain object state across the wire through a single controller endpoint. Mark a method `[Remote]` and it runs on the server. No controller-per-operation, no routing boilerplate.\n- **Source-generated properties** — Partial properties generate backing fields, `PropertyChanged` events, validation triggers, and change tracking at compile time. Zero reflection.\n- **Validation and business rules** — Attribute validation (`[Required]`, `[Range]`), inline rules, async rules that call external services, and automatic error aggregation across the entire object graph.\n- **Change tracking** — `IsModified`, `IsSelfModified`, `IsNew`, `IsDeleted` cascade through parent-child graphs to the aggregate root. `ModifiedProperties` tells you exactly what changed.\n- **DDD aggregate support** — `EntityBase` for persistent entities, `ValidateBase` for value objects, `EntityListBase` for child collections. Interface-first design enforces aggregate boundaries at compile time.\n- **Blazor integration** — MudNeatoo components bind directly to domain model properties with two-way binding, validation display, and form integration out of the box.\n\n## Example\n\nFor a complete working application with domain model, validation rules, authorization, persistence, unit tests, and a Blazor Server UI, see the [Person Example](src/Examples/Person/).\n\nDeclare partial properties. Add validation attributes and business rules. Source generators handle the rest — backing fields, `PropertyChanged` events, change tracking, factory methods, and client-server state transfer are all produced at compile time. No reflection, no runtime magic.\n\n\u003c!-- snippet: readme-teaser --\u003e\n\u003ca id='snippet-readme-teaser'\u003e\u003c/a\u003e\n```cs\n// Define an Employee aggregate root with validation and business rules\n[Factory]\npublic partial class Employee : EntityBase\u003cEmployee\u003e\n{\n    public Employee(IEntityBaseServices\u003cEmployee\u003e services) : base(services)\n    {\n        // Business rule: Full name is computed from first and last name\n        RuleManager.AddAction(\n            e =\u003e { e.FullName = $\"{e.FirstName} {e.LastName}\"; },\n            e =\u003e e.FirstName, e =\u003e e.LastName);\n\n        // Validation rule: Salary must be positive\n        RuleManager.AddValidation(\n            e =\u003e e.Salary \u003e 0 ? \"\" : \"Salary must be positive\",\n            e =\u003e e.Salary);\n    }\n\n    [Required]\n    public partial string FirstName { get; set; }\n\n    [Required]\n    public partial string LastName { get; set; }\n\n    public partial string FullName { get; set; }\n\n    public partial decimal Salary { get; set; }\n\n    // Child collection with automatic parent tracking\n    public partial IAddressList Addresses { get; set; }\n\n    [Create]\n    public void Create() { }\n}\n\npublic interface IAddress : IEntityBase { }\n\n[Factory]\npublic partial class Address : EntityBase\u003cAddress\u003e, IAddress\n{\n    public Address(IEntityBaseServices\u003cAddress\u003e services) : base(services) { }\n\n    [Required]\n    public partial string Street { get; set; }\n\n    public partial string City { get; set; }\n\n    [Create]\n    public void Create() { }\n}\n\npublic interface IAddressList : IEntityListBase\u003cIAddress\u003e { }\n\npublic class AddressList : EntityListBase\u003cIAddress\u003e, IAddressList { }\n```\n\u003csup\u003e\u003ca href='/src/samples/ReadmeSamples.cs#L10-L64' title='Snippet source file'\u003esnippet source\u003c/a\u003e | \u003ca href='#snippet-readme-teaser' title='Start of snippet'\u003eanchor\u003c/a\u003e\u003c/sup\u003e\n\u003c!-- endSnippet --\u003e\n\n## What a DDD Framework Gives You\n\n### Updates Without Guesswork\n\nWith transaction scripts, the client sends a partial payload and the server has to merge it with the current database state — figuring out what actually changed, what to update, and what to leave alone. That merge logic is fragile and grows with every new field.\n\nWith Neatoo, the full aggregate comes back to the server with its state intact. Every entity tracks `IsNew`, `IsModified`, `IsDeleted`, and `ModifiedProperties` through the entire object graph. When you reach your `[Insert]`, `[Update]`, or `[Delete]` factory methods, there's no guessing — the domain model already knows exactly what changed and what needs to persist.\n\n### Business Logic That Can't Diverge\n\nIn most applications, business logic drifts. Critical rules get duplicated between the UI and server, lighter rules live only in the UI, and the two definitions slowly diverge until they contradict each other.\n\nNeatoo puts validation and business rules in the domain model — one definition, compiled into both client and server. Rules are engineered to work with data-binding: when a property changes, dependent validation and action rules fire immediately, updating the UI in real time. The same rules execute again on the server during persistence. They can't diverge because they're the same code.\n\n### Authorization Defined Once, Enforced Everywhere\n\nAuthorization follows the same pattern. Client-side checks control what the UI shows — can this user create an order? Edit this field? Delete this record? Server-side checks guard the actual operations. In most applications these are separate implementations that fall out of sync.\n\nNeatoo's `[AuthorizeFactory]` attributes define authorization on the factory operation itself. RemoteFactory always enforces these on the server, regardless of what the client sends. The same definitions also power `CanCreate`, `CanFetch`, `CanUpdate`, and `CanDelete` methods that the UI consumes to show or hide actions, disable buttons, and control navigation. One definition drives both enforcement and UI behavior.\n\n### Field-Level Validation Without the Plumbing\n\nField-level validation — errors displayed next to the input that caused them, updating as the user works — is better UX than a banner at the top of the page. But it fell out of fashion because the plumbing is hard: per-property error tracking, real-time updates on change, cross-field dependencies, and aggregating validity across a parent-child graph.\n\nNeatoo makes it the path of least resistance. Validation rules are declared per-property and fire automatically when bound values change. Errors propagate through the object graph — `IsValid` on the aggregate root reflects every field in every child. MudNeatoo components display errors inline with no extra wiring. You get field-level validation by default, not as an afterthought.\n\n## Installation\n\nInstall the Neatoo package via NuGet.\n\n```bash\ndotnet add package Neatoo\n```\n\nFor Blazor support, install the MudNeatoo package:\n\n```bash\ndotnet add package Neatoo.Blazor.MudNeatoo\n```\n\nNeatoo targets .NET 9.0 and 10.0.\n\n## Quick Start\n\nCreate a domain object by inheriting from ValidateBase or EntityBase and declaring partial properties. Source generators handle the rest.\n\n\u003c!-- snippet: readme-quick-start --\u003e\n\u003ca id='snippet-readme-quick-start'\u003e\u003c/a\u003e\n```cs\n// 1. ValidateBase: For objects that need validation without persistence\n[Factory]\npublic partial class CustomerSearch : ValidateBase\u003cCustomerSearch\u003e\n{\n    public CustomerSearch(IValidateBaseServices\u003cCustomerSearch\u003e services) : base(services)\n    {\n        // Inline validation rule\n        RuleManager.AddValidation(\n            c =\u003e !string.IsNullOrEmpty(c.SearchTerm) ? \"\" : \"Search term is required\",\n            c =\u003e c.SearchTerm);\n    }\n\n    public partial string SearchTerm { get; set; }\n\n    [Range(1, 100)]\n    public partial int MaxResults { get; set; }\n\n    [Create]\n    public void Create() { }\n}\n\n// 2. EntityBase: For domain entities with full lifecycle support\n[Factory]\npublic partial class Customer : EntityBase\u003cCustomer\u003e\n{\n    public Customer(IEntityBaseServices\u003cCustomer\u003e services) : base(services) { }\n\n    [Required(ErrorMessage = \"Customer name is required\")]\n    public partial string Name { get; set; }\n\n    [EmailAddress]\n    public partial string Email { get; set; }\n\n    // Child entity with automatic parent cascade\n    public partial IOrderList Orders { get; set; }\n\n    // RemoteFactory method: Runs on server, result transferred to client\n    [Remote]\n    [Insert]\n    internal Task Insert([Service] ICustomerRepository repo)\n    {\n        // Persistence logic here\n        return Task.CompletedTask;\n    }\n\n    [Create]\n    public void Create() { }\n}\n\npublic interface IOrder : IEntityBase { }\n\n[Factory]\npublic partial class Order : EntityBase\u003cOrder\u003e, IOrder\n{\n    public Order(IEntityBaseServices\u003cOrder\u003e services) : base(services) { }\n\n    public partial decimal Amount { get; set; }\n\n    [Create]\n    public void Create() { }\n}\n\npublic interface IOrderList : IEntityListBase\u003cIOrder\u003e { }\n\npublic class OrderList : EntityListBase\u003cIOrder\u003e, IOrderList { }\n\n// Mock repository interface for the sample\npublic interface ICustomerRepository { }\n```\n\u003csup\u003e\u003ca href='/src/samples/ReadmeSamples.cs#L66-L135' title='Snippet source file'\u003esnippet source\u003c/a\u003e | \u003ca href='#snippet-readme-quick-start' title='Start of snippet'\u003eanchor\u003c/a\u003e\u003c/sup\u003e\n\u003c!-- endSnippet --\u003e\n\nThis example shows:\n- ValidateBase inheritance for validation, business rules, change tracking, and property metadata\n- EntityBase inheritance adds persistence lifecycle state (IsNew, IsDeleted, IsModified)\n- Partial property declarations with source-generated backing fields and change tracking\n- Attribute-based validation (Required, EmailAddress, Range)\n- Custom business rules (inline validation rules in constructor)\n- RemoteFactory methods for client-server persistence operations\n- Parent-child relationships with automatic parent tracking and cascade validation\n\n## Documentation\n\nComprehensive guides are available in the [docs/](docs/) directory:\n\n- **[Getting Started](docs/getting-started.md)** - Installation through first working aggregate\n- **[Validation Guide](docs/guides/validation.md)** - ValidateBase, validation rules, and error handling\n- **[Entities Guide](docs/guides/entities.md)** - EntityBase, aggregate roots, and entity lifecycle\n- **[Collections Guide](docs/guides/collections.md)** - EntityListBase and ValidateListBase\n- **[Properties Guide](docs/guides/properties.md)** - Property system and source generators\n- **[Business Rules Guide](docs/guides/business-rules.md)** - Business rules engine and rule execution\n- **[Change Tracking Guide](docs/guides/change-tracking.md)** - IsModified, IsSelfModified, state management, and cascade\n- **[Async Guide](docs/guides/async.md)** - Async validation and task coordination\n- **[Parent-Child Guide](docs/guides/parent-child.md)** - Parent-child graphs and aggregate boundaries\n- **[Blazor Guide](docs/guides/blazor.md)** - MudNeatoo Blazor integration\n- **[RemoteFactory Guide](docs/guides/remote-factory.md)** - Client-server state transfer\n- **[API Reference](docs/reference/api.md)** - Complete API documentation\n\n## Framework Comparison\n\nNeatoo is inspired by CSLA.NET but redesigned around Roslyn source generators for modern .NET development. Where CSLA relies on runtime reflection and manual coding patterns, Neatoo generates boilerplate at compile time while providing stronger type safety and better IDE support.\n\n## License\n\nMIT License - see [LICENSE](LICENSE) for details.\n\nCopyright (c) 2025 NeatooDotNet\n\n---\n\n**UPDATED:** 2026-01-24\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fneatoodotnet%2Fneatoo","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fneatoodotnet%2Fneatoo","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fneatoodotnet%2Fneatoo/lists"}