Settings for scaffolding out an entity for your Web API.
A list of database entities in your project. These entities are added as dbsets to your database context, so they translate 1:1 to tables in your database. With that said, if you want to create an aggregate, you can scaffold out an entity and update it accordingly to reflect that aggregate yourself (e.g. remove the db set).
Name | Required | Description | Default |
---|---|---|---|
Name | Yes | The name of the entity | None |
Properties | Yes | A list of properties assigned to your entity described in the entity properties section. | None |
Features | Yes | This is a list of features that you want to add for a particular entity. | None |
Plural | No | The plural of the entity name, if needed (e.g. Cities would prevent Citys ) | _Entity Name pluralized with Humanizer . |
Lambda | No | The value to use in lambda expressions for this entity. | First letter of the entity name. |
TableName | No (Yes if you want to set a Schema ) | The name of the table in the database. | Defaulted to the singular Name . |
Schema | No | The schema to use in the database. | None, which will equate to dbo in SqlServer, public in Postgres, or mydb in MySQL |
TableName
is required if you want to set a schema
A list of properties that can be assigned to an entity.
Name | Required | Description | Default |
---|---|---|---|
Name | Yes | The name of the property | None |
Type | No | The data type for the property. These are not case sensitive and they can be set to nullable with a trailing ? . All standard C# data types can be used here. String arrays are also supported string[] . | string |
IsRequired | No | When true, the property will be set as required in the database. | false true for primary key |
ColumnName | No | The database field name for the given property. | None |
CanManipulate | No | When set to false, you will not be able to update this property when calling the associated endpoint. When set to false , the property will be able to be established when using the POST endpoint, but will not be able to be updated after that. This is managed by the DTOs if you want to manually adjust this. | true false for primary key |
DefaultValue | No | Allows you to add a default value to a property. Note that it should be entered exactly as you'd want it, so a string would be something like "My Default Value" , a bool might be true , and an int might be 0 | None |
ForeignEntityName | No | Captures the name of the entity this property is linked to as a foreign key. | None |
ForeignEntityPlural | No | The plural value for the foreign entity, if applicable. | Entity Name appended with an s |
IsChildRelationship | No | Only used when establishing a relationship. Designates whether or not a relationship property is being set from the child entity. | false |
SmartNames | No | A list of strings used to create a smart enum property. Each string denotes an option for the smart enum. See the smart enum example below for details. Property attributes like filtering and sorting can be used if desired. | None |
IsLogMasked | No | Can be used to mark a property as masked when logged. | false |
AsValueObject | No | Can be used to designate a property as a specific kind of value object. See more below. | None |
ValueObjectName | No | The name of the value object. This is only used if designating a value object. | None |
ValueObjectPlural | No | The plural of the value object. This is only used if designating a value object. | None |
Name | Required | Description | Default |
---|---|---|---|
Type | Yes | The type of feature you want to add. The accepted values are AdHoc , Job , GetRecord , GetList , GetAll , DeleteRecord , UpdateRecord , AddRecord , and CreateRecord (same as AddRecord but available as an alias in case you can't remember!) | None |
IsProtected | No | Determines whether or not the feature is protected with an authorization policy attribute. | false |
PermissionName | No | A named permission to check against before executing a particular feature. | None |
GetRecord
, GetList
, GetAll
, DeleteRecord
, UpdateRecord
, AddRecord
, and AddListByFk
are all standard CRUD features along with their endpoints and associated tests.
ℹ DTOs, validators, profiles and functional test routes associated to all features will be added regardless of whether or not a given feature was added. This is due to the complexity of all the minute management around handling these so dynamically. Feel free to delete whatever you're not using.
The AddListByFk
will let you add a list or batch of records using a foreign key. For example, you can add a list of ingredients for a recipe, based on the id of a recipe.
Name | Required | Description | Default |
---|---|---|---|
BatchPropertyName | Yes | The name of the property on the foreign entity you are doing a batch add on. | None |
BatchPropertyType | Yes | The data type of the the property you are doing the batch add on. This is for a FK property, so probably a Guid or int. | None |
ParentEntity | Yes | The name of the parent entity that the FK you using is associated to. For example, if you had a FK of EventId , the parent entity might be Event . Leave null if you're not batching on a FK. | None |
ParentEntityPlural | Yes | The plural of the FK entity. Leave null if you're not batching on a FK. | None |
You can also add features that will be setup for a Hangfire job instead of a MediatR handler using the Job
feature type.
Name | Required | Description | Default |
---|---|---|---|
Name | Yes | The name of the feature. This is the name of the feature's method and would generally be something like AddCustomer or GetCustomer . | None |
EntityPlural | No | The plural name of the entity that the feature will be added to (should match the name in the Domain directory). You can leave this blank to add it to the Domain directory directly. | [ENTITYNAME]s |
You can also add ad hoc skeletons with the AdHoc
type. For AdHoc
features, there's a bit more that needs to be done. In addition to Type
above, you'll want to provide the below:
Name | Required | Description | Default |
---|---|---|---|
Name | Yes | The name of the feature. This is the name of the feature's method and would generally be something like AddCustomer or GetCustomer . | None |
EntityPlural | No | The plural name of the entity that the feature will be added to (should match the name in the Domain directory). You can leave this blank to add it to the Domain directory directly. | [ENTITYNAME]s |
ResponseType | No | Is the type of response that this command will return. This could be any C# type that you want, or even a custom type if you want, but in that case, you'll need to add a using statement for it manually. | bool |
You can also add any of the above features after the fact using the
add feature
command.
Craftsman can handle a few different types of value objects directly on your properties. Let's look at the different types of value objects and how they work.
Craftsman has a few built in value objects below:
Note that
Address
is also a built in value object, but is a complex value object that must be handled manually post-scaffolding for the time being.
To use these value objects, you can simply add the value above in the AsValueObject
property to your property and set it
to the value object you want to use. For example:
Entities:
- Name: Author
Features:
- Type: GetList
Properties:
- Name: Name
- Name: Ownership
AsValueObject: Percent
- Name: Email
AsValueObject: Email
- Name: Ingredient
Features:
- Type: GetList
Properties:
- Name: Name
- Name: AverageCost
AsValueObject: MonetaryAmount
You can also create your own simple value objects. These are value objects that are a single property of whatever type you have designated for that property.
For example, a Rating
value object might be a single int?
property. To create a simple value object,
you can use the AsValueObject
property and set it to Simple
to great a value object called Rating
that will back your property. For example:
Entities:
- Name: Recipe
Features:
#...
Properties:
- Name: Title
- Name: Rating
Type: int?
AsValueObject: Simple
You can also set the name of the value object by using the ValueObjectName
and ValueObjectPlural
properties.
ValueObjectPlural
will try to pluralize for you automatically and get it right in most cases, but you can
override it if you want. For example:
Entities:
- Name: Recipe
Features:
#...
Properties:
- Name: Title
- Name: Rating
Type: int?
AsValueObject: Simple
ValueObjectName: UserRating
ValueObjectPlural: UserRatings
If you want to re-use an existing simple value object, you can do so by adding it as you did originally.
You can also use the SmartNames
property to create value objects backed by smart enum options like so:
Entities:
- Name: Recipe
Features:
#...
Properties:
- Name: Title
- Name: Visibility
SmartNames:
- Public
- Friends Only
- Private
You can customize these with a custom name as well:
Entities:
- Name: Recipe
Features:
#...
Properties:
- Name: Title
- Name: Visibility
SmartNames:
- Public
- Friends Only
- Private
ValueObjectName: RecipeVisibility
ValueObjectPlural: RecipeVisibilities
If you want to re-use an existing smart enum value object, you can do so by adding it as you did originally. You can use the same bulleted options or just do a single placeholder value. For example:
Entities:
- Name: New Concept
Features:
#...
Properties:
- Name: Visibility
SmartNames:
- Placeholder
ValueObjectName: RecipeVisibility # only needed if you did a custom name
ValueObjectPlural: RecipeVisibilities # only needed if you did a custom name
There are a couple of important pieces of information to know about settings keys for scaffolding:
BaseEntity
.Relationships can be set up by adding a special property that looks something like this:
- Name: Product
Relationship: manyto1
ForeignEntityName: Product
ForeignEntityPlural: Products
The different type of relationships are:
1tomany
where the principal entity is the 1
and the foreign entity is the many
. For example, a Customer
can have many Orders
.manyto1
where the principal entity is the many
and the foreign entity is the 1
. For example, a Product
can have many Categories
.manytomany
where the principal entity is the entity that the property is added to. For example, a Product
can have many Tags
.1to1
where the principal entity is the entity that the property is added to. For example, a Customer
can have one CustomerSettings
.self
where the principal entity is the same as the foreign entity. For example, a Category
can have many SubCategories
.Note that child entities are managed from the principal entity. So for example, if you have a Customer
that manage
Order
entities through something like Customer.AddOrder()
, the Customer
would be the principal and you would use
a 1tomany
on the Customer
. If you wanted to manage the Customer
from an Order
entity with something like
Order.SetCustomer()
, you would use a manyto1
on the Order
entity. The main question you should ask yourself when adding a relationship and picking which entity to put it on should
be 'where should this concept be managed from?'. This will help you establish the principal owner of the relationship.
If you're doing an add entities
command and your principal entity is already in the project, you can still add the relationship by adding an IsChildRelationship: true
.
💡 Note that you'll need to flip the relationship type when adding a child relationship. So if you would normally add a
1tomany
Order
relationship on aCustomer
, when adding it as a child onOrder
, you'll want to make theCustomer
relationship amanyto1
relationship.
Notice the difference in the examples below, with the first being the principal based example and the second being the child based example.
- Name: Customer
Features:
- Type: GetList
- Type: GetAll
- Type: GetRecord
- Type: AddRecord
- Type: UpdateRecord
- Type: DeleteRecord
- Type: Job
Name: PerformCustomerMigration
Properties:
- Name: FirstName
- Name: LastName
- Name: Email
- Name: DateJoined
Type: DateOnly?
- Name: LoyaltyPoints
Type: int?
- Name: Orders
Relationship: 1tomany
ForeignEntityName: Order
ForeignEntityPlural: Orders
- Name: Order
Features:
- Type: GetList
- Type: GetRecord
- Type: AddRecord
- Type: UpdateRecord
- Type: DeleteRecord
Properties:
- Name: OrderDate
Type: DateOnly?
- Name: Status
CanManipulate: false
- Name: Customer
Relationship: manyto1
IsChildRelationship: true
ForeignEntityName: Customer
ForeignEntityPlural: Customers
Here's a larger example that has all the different kinds of relationships.
DomainName: ECommerceApi
BoundedContexts:
- ProjectName: ECommerce
Port: 5300
DbContext:
ContextName: ECommerceDbContext
DatabaseName: ECommerceDB
Provider: Postgres
Entities:
- Name: Customer
Features:
- Type: GetList
- Type: GetAll
- Type: GetRecord
- Type: AddRecord
- Type: UpdateRecord
- Type: DeleteRecord
- Type: Job
Name: PerformCustomerMigration
Properties:
- Name: FirstName
- Name: LastName
- Name: Email
- Name: DateJoined
Type: DateOnly?
- Name: LoyaltyPoints
Type: int?
- Name: Orders
Relationship: 1tomany
ForeignEntityName: Order
ForeignEntityPlural: Orders
- Name: Settings
Relationship: 1to1
ForeignEntityName: CustomerSettings
ForeignEntityPlural: CustomerSettings
- Name: Product
Features:
- Type: GetList
- Type: GetRecord
- Type: AddRecord
- Type: UpdateRecord
- Type: DeleteRecord
Properties:
- Name: ProductName
- Name: Description
- Name: Price
Type: decimal?
- Name: Stock
Type: int?
- Name: Category
Relationship: manyto1
ForeignEntityName: Category
ForeignEntityPlural: Categories
- Name: Tags
Relationship: manytomany
ForeignEntityName: Tag
ForeignEntityPlural: Tags
- Name: Category
Plural: Categories
Features:
- Type: GetList
- Type: GetRecord
- Type: AddRecord
- Type: UpdateRecord
- Type: DeleteRecord
Properties:
- Name: CategoryName
- Name: ParentCategory
Relationship: self
ForeignEntityName: Category
ForeignEntityPlural: Categories
- Name: Order
Features:
- Type: GetList
- Type: GetRecord
- Type: AddRecord
- Type: UpdateRecord
- Type: DeleteRecord
Properties:
- Name: OrderDate
Type: DateOnly?
- Name: Status
CanManipulate: false
- Name: OrderItems
Relationship: 1tomany
ForeignEntityName: OrderItem
ForeignEntityPlural: OrderItems
- Name: OrderItem
Features:
- Type: GetList
- Type: GetRecord
- Type: AddRecord
- Type: UpdateRecord
- Type: DeleteRecord
Properties:
- Name: Quantity
Type: int
- Name: Product
Relationship: manyto1
ForeignEntityName: Product
ForeignEntityPlural: Products
- Name: Review
Features:
- Type: GetList
- Type: GetRecord
- Type: AddRecord
- Type: UpdateRecord
- Type: DeleteRecord
Properties:
- Name: Rating
Type: int
- Name: Comment
- Name: Product
Relationship: manyto1
ForeignEntityName: Product
ForeignEntityPlural: Products
- Name: Customer
Relationship: manyto1
ForeignEntityName: Customer
ForeignEntityPlural: Customers
- Name: Tag
Features:
- Type: GetList
- Type: GetRecord
- Type: AddRecord
- Type: UpdateRecord
- Type: DeleteRecord
Properties:
- Name: Name
- Name: Description
- Name: CustomerSettings
Plural: CustomerSettings
Features:
- Type: GetList
- Type: GetRecord
- Type: AddRecord
- Type: UpdateRecord
- Type: DeleteRecord
Properties:
- Name: PreferredDayOfArrival
For the time being, composite keys can not be created using Craftsman commands. With that said, you are more than welcome to scaffold out an entity with one of the keys and then modify the generated entity, repository, tests, etc. to accommodate that composite key.
If using auth, you can protect features with a particular permission. In the example below, I have a permission for doing read overall and default permissions for each destructive feature.
Entities:
- Name: Recipe
Features:
- Type: GetList
IsProtected: true
PermissionName: CanReadRecipes
- Type: GetRecord
IsProtected: true
PermissionName: CanReadRecipes
- Type: AddRecord
IsProtected: true
- Type: UpdateRecord
IsProtected: true
- Type: DeleteRecord
IsProtected: true
Properties:
- Name: Title
Type: string
- Name: Directions
Type: string