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 appended with an s |
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 | Yes | 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. You even do custom properties like classes that act as foreign keys for instance. | string |
CanFilter | No | Will set the property to be filterable in the API endpoint when set to true. | false |
CanSort | No | Will set the property to be filterable in the API endpoint when set to true. | false |
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 |
Name | Required | Description | Default |
---|---|---|---|
Type | Yes | The type of feature you want to add. The accepted values are AdHoc , GetRecord , GetList , DeleteRecord , UpdateRecord , PatchRecord , 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 | The name of the authorization policy attribute. | Can[FEATURENAME] |
GetRecord
, GetList
, DeleteRecord
, UpdateRecord
, PatchRecord
, 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 |
BatchPropertyDbSetName | Yes | The name of the DbSet for the FK you are doing a batch add on. Generally, the plural of the FK entity. Leave null if you're not batching on a FK. | None |
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 |
Command | Yes | The name of the command. This is the name of the feature's class and would generally be something like AddCustomerCommand or GetCustomerQuery . | [NAME]Command |
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.
There are a couple of important pieces of information to know about settings keys for scaffolding:
BaseEntity
.This is the most noteworthy of the items above and will require the most manual intervention. Scaffolding here is tricky due to the variety of situations that can arise, so they need to be resolved manually. Here are a few tricks that may help:
// you could create an empty list so that your tests don't have to accommodate the default of 3. only add them if you need them for a test
RuleFor(s => s.Organizations, _ => new List<Organization>());
// if you have a single foreign key, you may want to set the object and/or the id to null
RuleFor(r => r.RecipeId, _ => null);
RuleFor(r => r.Recipe, _ => null);
// in some cases, your object may not make sense in your domain without having the object, so could generate a fake for it using your designated faker that has its own rules.
RuleFor(i => i.Recipe, _ => new FakeRecipe().Generate());
For example, this could mean inserting a parent object into the database that can be assigned to the child to prevent a foreign key conflict.
[Test]
public async Task can_add_new_ingredient_to_db()
{
// Arrange
var fakeRecipeOne = new FakeRecipe { }.Generate();
await InsertAsync(fakeRecipeOne);
var fakeIngredientOne = new FakeIngredientForCreationDto { }.Generate();
fakeIngredientOne.RecipeId = fakeRecipeOne.Id;
// Act
var command = new AddIngredient.AddIngredientCommand(fakeIngredientOne);
var ingredientReturned = await SendAsync(command);
var ingredientCreated = await ExecuteDbContextAsync(db => db.Ingredients.SingleOrDefaultAsync());
// Assert
ingredientReturned.Should().BeEquivalentTo(fakeIngredientOne, options =>
options.ExcludingMissingMembers());
ingredientCreated.Should().BeEquivalentTo(fakeIngredientOne, options =>
options.ExcludingMissingMembers());
}
Or making sure that you don't try and insert a user id that isn't in the db.
[Test]
public async Task can_update_existing_ingredient_in_db()
{
// Arrange
var fakeIngredientOne = new FakeIngredient { }.Generate();
await InsertAsync(fakeIngredientOne);
var updatedIngredientDto = new FakeIngredientForUpdateDto { }.Generate();
updatedIngredientDto.UserId = fakeIngredientOne.User.Id;
var ingredient = await ExecuteDbContextAsync(db => db.Ingredients.SingleOrDefaultAsync());
var id = ingredient.Id;
// Act
var command = new UpdateIngredient.UpdateIngredientCommand(id, updatedIngredientDto);
await SendAsync(command);
var updatedIngredient = await ExecuteDbContextAsync(db => db.Ingredients.Where(u => u.Id == id).SingleOrDefaultAsync());
// Assert
updatedIngredient.Should().BeEquivalentTo(updatedIngredientDto, options =>
options.ExcludingMissingMembers());
}
This example would create a supplier entity with several properties. Note:
SupplierId
is marked as a primary keyType
optionIsVip
PatchRecord
. We're also adding an AdHoc
feature for use for batch adding suppliers.Entities:
- Name: Supplier
Features:
- Type: AddRecord
- Type: GetRecord
- Type: GetList
- Type: UpdateRecord
- Type: DeleteRecord
- Type: AddListByFk
BatchPropertyName: OrganizationId
BatchPropertyType: Guid
ParentEntity: Organization
BatchPropertyDbSetName: Organizations
- Type: AdHoc
Name: MySpecialLookup
Command: MySpecialLookupQuery
EntityPlural: Suppliers
Properties:
- Name: Name
Type: string
CanFilter: true
CanSort: true
- Name: EmployeeCount
Type: int?
CanFilter: true
CanSort: true
- Name: CreationDate
Type: datetime?
CanFilter: true
CanSort: true
- Name: SupplierType
Type: int?
CanFilter: true
CanSort: true
- Name: IsVip
Type: bool?
CanFilter: true
CanSort: true
DefaultValue: false
- Name: OrganizationId
Type: Guid?
ForeignEntityName: Organization
Foreign keys can be added in any of the below ways. At a high level, you'll notice there is a syntax for adding the 1
relationship type and the many
relationship type.
Entities:
- Name: User
Properties:
- Name: Name
Type: string
- Name: UserProfileId
Type: Guid?
ForeignEntityName: UserProfile
ForeignEntityPlural: UserProfiles
- Name: UserProfile
Properties:
- Name: Name
Type: string
- Name: UserId
Type: Guid
ForeignEntityName: User
ForeignEntityPlural: Users
Here, we have a Blog
with many Posts
. The Type
of the foreign id doesn't have to be a Guid, but is in this example.
Additionally, the many Posts
can be any collection that works for a normal .NET relationship, in this example, an ICollection
. It does also need a ForeignEntityPlural
to
allow for a proper using statement.
Entities:
- Name: Post
Properties:
- Name: Name
Type: string
- Name: BlogId
Type: Guid?
ForeignEntityName: Blog
ForeignEntityPlural: Blogs
- Name: Blog
Properties:
- Name: Name
Type: string
- Name: Posts
Type: ICollection<Post>
ForeignEntityPlural: Posts
Here, we have many Books
that can also have many Categories
. Like the One to Many relationship, the Type
can be any collection that works for a normal .NET relationship. As
with one to many, they need a ForeignEntityPlural
to allow for a proper using statement.
Entities:
- Name: Book
Properties:
- Name: Name
Type: string
- Name: Categories
Type: ICollection<Category>
ForeignEntityPlural: Categories
- Name: Category
Properties:
- Name: Name
Type: string
- Name: Books
Type: ICollection<Book>
ForeignEntityPlural: Books
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
- Type: PatchRecord
IsProtected: true
Properties:
- Name: Title
Type: string
CanFilter: true
CanSort: true
- Name: Directions
Type: string
CanFilter: true
CanSort: true