[AI Readability Summary]
GeneratePoco is a DTO generation tool built on .NET Source Generators. Its core capability is to project companion types from model classes, reducing repetitive property definitions, lowering refactoring overhead, and supporting rule-based naming, nullability control, and attribute migration. Keywords: Source Generator, DTO projection, code generation.
The technical specification snapshot captures the tool at a glance
| Parameter | Description |
|---|---|
| Language | C# / .NET |
| Technical Model | Roslyn Source Generator |
| Core Capabilities | DTO generation, projection rules, domain primitive simplification, attribute migration |
| Installation | dotnet add package Hand.GeneratePoco --version 0.2.0.2-alpha |
| Rule Model | Prefix, Exclude, Cross, NullableRule |
| Core Dependencies | SyntaxTree, partial, AttributeData/TypedConstant |
| Source Repositories | donetsoftwork/Hand.Generators, donetsoftwork/HandCore.net |
| Star Count | Not provided in the original article |
The tool solves the problem of duplicate DTO and model definitions
GeneratePoco has a straightforward purpose: automatically generate DTOs, ViewModels, or other companion types from source model classes. For types that share a similar property structure but differ slightly in naming conventions, this approach is more stable than writing everything by hand.
Its value is not just about writing less code. More importantly, it reduces the chain reaction of changes after model updates. When the source type adds, removes, or renames fields, the generator can automatically synchronize the target type, which lowers the maintenance cost caused by complex DTO inheritance chains.
dotnet add package Hand.GeneratePoco --version 0.2.0.2-alpha
This command installs the GeneratePoco package and serves as the entry point for code generation.
The minimal example already shows the basic projection workflow
The simplest usage pattern is this: define a model class, then annotate the target partial type with the GeneratePoco attribute. The generator reads the source type properties and creates corresponding properties on the target class.
public record User(int Id, string Name);
[GeneratePoco(typeof(User))] // Mark the source model and trigger DTO generation
public partial class UserDto;
This code declares the generation relationship between the source type and the target type.
partial class UserDto
{
public int Id { get; set; } // Projected from User.Id
public string Name { get; set; } // Projected from User.Name
}
This generated code completes the most basic property copy operation.
Projection rules make property mapping structured and controllable
When the target type needs a specific naming convention, you can configure projection behavior through Rules. The most common example is a prefix rule, which applies a batch transformation to all property names.
[GeneratePoco(typeof(User), Rules = ["Prefix User"])] // Add the User prefix to all target properties
public partial class UserDto;
This configuration transforms Id and Name into UserId and UserName.
partial class UserDto
{
public int UserId { get; set; } // Prefix rule applied
public string UserName { get; set; } // Prefix rule applied
}
This generated result works well for input models, API response models, and similar scenarios.
Filter rules and rule order both affect the final result
In addition to prefixes, Exclude can filter out specific properties. It is commonly used to create objects such as “create DTOs” or “edit DTOs” that do not need primary keys or internal fields.
[GeneratePoco(typeof(User), Rules = ["Exclude: Id", "Prefix User"])] // Exclude Id first, then add the prefix
public partial class NewUserDto;
This configuration keeps only Name and then transforms it into UserName.
More importantly, rule order changes semantics. If Prefix User runs first, the original Id becomes UserId. In that case, Exclude: Id no longer works, and you must use Exclude: UserId instead.
Cross projection works well when you need to keep original fields and add variant fields
By default, projection behaves more like a Through mode: one input property becomes one transformed output property. Cross, however, preserves the original property and also generates an additional transformed property, so both versions coexist.
[GeneratePoco(typeof(User), Rules = ["Cross: Prefix User"])] // Keep original fields and append prefixed fields
public partial class UserDto;
This configuration generates Id, Name, UserId, and UserName at the same time.
partial class UserDto
{
public int Id { get; set; } // Keep the original property
public string Name { get; set; } // Keep the original property
public int UserId { get; set; } // Property added by Cross
public string UserName { get; set; } // Property added by Cross
}
This generated shape fits transitional models where legacy and new field names must coexist.
Domain primitive simplification makes domain models easier to project into transport objects
Domain models often use Domain Primitives to wrap identifiers and value objects, such as UserId and UserName. GeneratePoco can recognize these wrappers and project the Original value onto the target type.
public class UserEntity(UserId id, UserName name) : IEntity
<UserId>
{
public UserId Id { get; } = id;
public UserName Name { get; } = name;
}
public record struct UserId(long Original) : IEntityId;
public record struct UserName(string Original) : IEntityProperty
<string>;
[GeneratePoco(typeof(UserEntity), Rules = ["Prefix User"])]
public partial class UserViews;
This code shows automatic simplification from domain objects to a flat DTO.
partial class UserViews
{
public long UserId { get; set; } // Simplified from UserId.Original
public string UserName { get; set; } // Simplified from UserName.Original
}
This generated result reduces the complexity of exposing domain-specific types directly at the API layer.
Nullable rules make input DTOs align better with real API constraints
For create and update requests, many fields are optional. NullableRule lets you convert selected fields or the entire set into nullable types.
[GeneratePoco(typeof(User), Rules = ["Prefix User"], NullableRule = "UserEmail UserSex")] // Make selected fields nullable
public partial class UserDto;
This configuration makes only UserEmail and UserSex nullable.
[GeneratePoco(typeof(User), Rules = ["Prefix User"], NullableRule = "ALL")] // Make all fields nullable
public partial class UserDto;
This configuration is suitable for Patch-style or loosely constrained input models.
Reverse nullability configuration reduces the cost of maintaining long field lists
When most fields should be nullable, you can use the reverse rule to keep only a few fields non-nullable.
[GeneratePoco(typeof(User), Rules = ["Prefix User"], NullableRule = "Exclude: UserId")] // All fields nullable except UserId
public partial class UserDto;
This configuration avoids maintaining a long whitelist of field names.
Attribute migration is one of the tool’s most practical capabilities
In many projects, validation, serialization, or persistence-related attributes are the part of a DTO that delivers the most value. GeneratePoco can migrate attributes from source model properties onto target type properties.
public class User(int id, string name, string email)
{
public int Id { get; } = id;
[Required]
[StringLength(100, MinimumLength = 6)]
public string Name { get; } = name;
[EmailAddress]
public string Email { get; } = email;
}
This source model centralizes validation semantics on the domain properties.
partial class UserDto
{
public int UserId { get; set; }
[Required] // Migrate the validation rule from the source property
[StringLength(100, MinimumLength = 6)]
public string UserName { get; set; }
[EmailAddress] // Migrate the email validation rule
public string UserEmail { get; set; }
}
This generated result eliminates the need to maintain validation rules repeatedly across multiple models.
The exception handling mechanism ensures the rule system never traps developers
Not every field fits a rule-based projection. If a property needs a special type or a special semantic meaning, you can write it directly in the partial target class, and the generator will skip the property with the same name.
For example, if Sex in the source model is an int, but the DTO input model needs string? for UserSex, you can hand-write UserSex directly. This strategy makes the tool practical by combining rule-first generation with manual fallback where needed.
The implementation is built on SyntaxTree, partial types, and configuration parsing
The project relies on Roslyn SyntaxTree APIs to generate syntax nodes and uses the partial type pattern to safely compose generated code with handwritten code. Rule strings are parsed into different projection models such as Cross, Through, and Filter, and then applied to the member set.
Attribute migration is one of the more complex implementation details. The generator receives AttributeData and TypedConstant, but the final output must be reconstructed as AttributeSyntax. That process requires extra conversion logic for enum values, type arguments, and array parameters.
FAQ answers the most common implementation questions
Q1: What is the biggest difference between GeneratePoco and handwritten DTOs?
A1: GeneratePoco treats a DTO as the projection result of a model class rather than as an independently maintained object. That means field changes only require updates to the source model and the rules, which significantly reduces duplicate definitions and refactoring overhead.
Q2: When should you use Cross instead of a regular Prefix rule?
A2: Use Cross when you need to preserve the original fields and add transformed fields at the same time. Typical examples include maintaining compatibility with legacy API field names or supporting two naming conventions during a migration period.
Q3: If a field cannot be expressed by the rule system, do you have to give up code generation?
A3: No. You can hand-write the special property in the partial target class, and the generator will ignore members with the same name. This preserves the efficiency of batch generation while still allowing fine-grained local control.
AI Visual Insight: GeneratePoco turns DTOs into deterministic projections of model classes. By combining projection rules, nullability strategies, domain primitive flattening, and attribute migration, it reduces repetitive code while keeping generated models adaptable to real-world API and domain requirements.
Core takeaway: This article reconstructs the core capabilities of GeneratePoco: using a .NET Source Generator to automatically generate DTOs from model classes, while reducing duplicate code and refactoring overhead through projection rules, nullability strategies, domain primitive simplification, and attribute migration.