Building with Nano ASP.NET Boilerplate: Creating a New CRUD Service
Getting Started
In this walkthrough, we’ll create a new web application using the Nano CLI tool.
We will create a new Supplier entity, with an API controller and service. The Supplier entity will form a one-to-many relationship with the Product entity.
The Nano ASP.NET boilerplate is distributed as a NuGet package. For installation instructions, follow this guide. Once installed, you can use the project templates from the Developer PowerShell terminal.
Open a new terminal window and navigate to a folder to create the new project. In the terminal, use the dotnet nano command to create a new project:
dotnet new nano -n MyApp
You can replace the name attribute (-n) with the name of your project. By passing options for multi-tenancy (-m) or user interface (-ui) you can create projects with varying specifications. To learn more, check out the using the CLI tool guide.
Create a Supplier entity
We will start by creating a new entity called Supplier. This entity will be part of a one-to-many relationship with the Product entity.
The code is divided into layers, with the entities being part of the Domain layer.
Open the Domain layer and in the /Catalog folder, create a new entity class called Supplier. Supplier will have a Name field and navigation property for Products. The navigation property will establish one side of the one-to-many relationship for Entity Framework. Inherit from the abstract class AuditableEntity.
public class Supplier : AuditableEntity
{
public string Name { get; set; }
public ICollection<Product> Products { get; set; }
}
Next we need to modify the Product entity to complete the one-to-many relationship for Entity Framework. Add a SupplierId field and Supplier navigation property.
public class Product : AuditableEntity
{
public string Name { get; set; }
public string Description { get; set; }
public Supplier Supplier { get; set; }
public Guid SupplierId { get; set; }
}
All entities should directly or indirectly inherit from abstract class BaseEntity, found in /Entities/Common.
AuditableEntity is an abstract class that inherits from TenantBaseEntity (or directly from BaseEntity in single-tenant deployments). AuditableEntity adds the CreatedBy, CreatedOn, LastModifiedBy, LastModifiedOn fields. By implementing the IAuditableEntity interface, the values of these fields are handled automatically whenever save changes occurs. Business entities usually inherit from AuditableEntity.
TenantBaseEntity inherits from the BaseEntity class and adds the TenantId field. By implementing the IMustHaveTenant interface, query filters in the ApplicationDbContext will isolate tenant data.
Open up ApplicationDbContext in Infrastructure/Persistence/Contexts/ and create the DbSet for Supplier
public DbSet<Supplier> Suppliers { get; set; }
Create a Migration
Next we need to create a migration to apply these schema changes to the database.
Open the Package Manager Console and set the default project to infrastructure. Use the following command to create a new migration:
add-migration -Context ApplicationDbContext -o Persistence/Migrations/AppDb App-SupplierEntity
If you deployed the project with single database multi-tenancy (-m singledb) or as single tenant (-m singletenant), then use this command:
add-migration -Context ApplicationDbContext -o Persistence/Migrations SupplierEntity
Run the application to apply the migrations or use the update-database command.
In ApplicationDbContext you can find a quick reference for all the migration commands in the code comments.
Create the Supplier Service
Next we’ll use the Nano CLI to create a service for the new entity.
In the Developer PowerShell, change directory to MyApp.Application\Services and then run the following command:
dotnet new nano-service -s Supplier -p Suppliers -ap MyApp
If building with Razor pages, use this command:
dotnet new nano-service -s Supplier -p Suppliers -ap MyApp -ui razor
And just like that, all the code for a new service feature has been created within the SupplierService folder:
Passing the -ui razor option will scaffold slightly different code for the pagination method, otherwise it’s the same
The new service contains all the methods for basic CRUD operations. Here is the scaffolded ISupplierService code for reference:
public interface ISupplierService : ITransientService
{
Task<Response<IEnumerable<SupplierDTO>>> GetSuppliersAsync(string keyword = "");
Task<PaginatedResponse<SupplierDTO>> GetSuppliersPaginatedAsync(SupplierTableFilter filter);
Task<Response<SupplierDTO>> GetSupplierAsync(Guid id);
Task<Response<Guid>> CreateSupplierAsync(CreateSupplierRequest request);
Task<Response<Guid>> UpdateSupplierAsync(UpdateSupplierRequest request, Guid id);
Task<Response<Guid>> DeleteSupplierAsync(Guid id);
}
This service will register as a transient service since it implements the ITransientService interface. To register as scoped, change to IScopedService. You can read more about services in the application services guide.
Customize the Supplier DTOs
DTOs for the supplier service can be found within Application/Services/SupplierService/DTOs. All DTOs implement the IDto marker interface.
Since the dotnet CLI tool is not aware of entities and their properties, a scaffolded DTO from dotnet new nano-service will only contain an Id and Name property. Customize your entities as you need to.
In this case, the SupplierDTO should also contain a list of Products. Add a new list of ProductDTO List to the SupplierDTO:
public class SupplierDTO : IDto
{
public Guid Id { get; set; }
public string Name { get; set; }
public IList<ProductDTO> Products { get; set; }
}
Creating a new service with the dotnet new nano-service command also creates DTOs for Create and Update requests. The generated DTO contains a Name field with validation and should be modified per your needs in regards to the properties in your entity.
public class CreateSupplierRequest : IDto
{
public string Name { get; set; }
}
public class CreateSupplierValidator : AbstractValidator<CreateSupplierRequest>
{
public CreateSupplierValidator()
{
RuleFor(x => x.Name).NotEmpty();
}
}
Update the Product DTOs
Because we now have a one-to-many relationship between Supplier and Product, creating and updating Products requires a supplier ID. Navigate to Application/Services/ProductService/DTOs and add a SupplierId property for the Update and Create product requests.
public class CreateProductRequest : IDto
{
public string Name { get; set; }
public string Description { get; set; }
public Guid SupplierId { get; set; }
}
public class CreateProductValidator : AbstractValidator<CreateProductRequest>
{
public CreateProductValidator()
{
_ = RuleFor(x => x.Name).NotEmpty();
_ = RuleFor(x => x.Description).NotEmpty();
_ = RuleFor(x => x.SupplierId).NotEmpty();
}
}
On the ProductDTO, add a GUID SupplierId and a SupplierName string property. In the next step, we will configure Automapper to populate the SupplierName whenever mapping occurs.
public class ProductDTO : IDto
{
public Guid Id { get; set; }
public string? Name { get; set; }
public string? Description { get; set; }
public DateTime CreatedOn { get; set; }
public string SupplierName { get; set; }
public Guid SupplierId { get; set; }
}
Configure Automapping
Next we need to set up mapping profiles for the new DTOs
In Infrastructure/Mapper/MappingProfiles add the following configurations for supplier:
// supplier mappings...
_ = CreateMap<Supplier, SupplierDTO>();
_ = CreateMap<CreateSupplierRequest, Supplier>();
_ = CreateMap<UpdateSupplierRequest, Supplier>();
This will ensure that Automapper knows how to map a Supplier entity to a SupplierDTO and the Create / Update requests to a Supplier entity.
Modify the product mappings so that when going from Product to ProductDTO, the SupplierName field is mapped from the Supplier entity.
// product mappings
_ = CreateMap<Product, ProductDTO>().ForMember(x => x.SupplierName, o => o.MapFrom(s => s.Supplier.Name));
_ = CreateMap<CreateProductRequest, Product>();
_ = CreateMap<UpdateProductRequest, Product>();
With that bit of manual labor out of the way, we can wrap things up in the next step by creating an API controller.
Create the Supplier Controller
We’ll use the Nano CLI again to create an API controller.
In the Developer PowerShell, change directory to MyApp.WebApi\Controllers and run the following command:
dotnet new nano-controller -s Supplier -p Suppliers -ap MyApp
If building with Razor pages, change directory to MyApp.RazorApp\Controllers use this command:
dotnet new nano-controller -s Supplier -p Suppliers -ap MyApp -ui razor
This will create a new controller with endpoints for the CRUD methods provided in the SupplierService we scaffolded earlier.
Testing with Postman
Now we can test with Postman. Create a new folder for Suppliers with a Post request called create-supplier. Create a few suppliers; remember to first obtain a token.
Create a new Get request called get-suppliers-full-list and retrieve the list of suppliers. Copy one of the supplier IDs so that we can use it to create a product in the next step.
In the Products folder, modify the create-product request body to have a SupplierId field. Create a new product for one of the suppliers
Now when you retrieve a list of suppliers, an array of products will be returned.
Returning a list of products works as well, with Automapper taking care of the supplier name field for us
With the Nano Boilerplate CLI tool, creating a new app feature is easy. The bulk of the code can be generated using the dotnet new commands, allowing you to focus on the unique aspects of your project.
That concludes the walkthrough, hopefully now you have a better understanding of how to build API endpoints with the Nano ASP.NET boilerplate!