Guide: Adding a New Feature
Relevant source files
The following files were used as context for generating this wiki page:Introduction
This guide provides a comprehensive walkthrough for adding a new feature module to the WEBAPI project. It is based on the existing architectural patterns observed in the implementation of the User, Product, and Order management systems. The project follows a standard n-tier architecture, separating concerns into Controllers (API layer), Services (business logic layer), and Data Access (database layer using Entity Framework Core).
By following this guide, developers can create new features that are consistent with the project's structure, maintainable, and scalable. The process involves creating a data model, configuring the database context, implementing business logic in a service, exposing functionality through a controller, and registering the necessary dependencies for injection.
Core Architecture
The application follows a layered architecture. A typical request flows from the client to a Controller, which then utilizes a Service to perform business operations. The Service, in turn, interacts with the UserDBContext to access the database.
This diagram illustrates the high-level request flow for a new feature.
Step 1: Define the Data Model
The first step is to define the entity model for your new feature. This is a plain C# class representing the data structure that will be stored in the database. For example, the User and Product models define their respective properties.
Example: User Model
The User model includes properties for user credentials, roles, and password reset tokens.
| Property | Type | Description |
|---|---|---|
Id | int | The primary key for the user. |
Email | string | The user's email address. |
UserName | string | The user's unique username. |
Password | string | The user's password. |
Role | bool | Indicates if the user has an admin role. |
ResetToken | string? | A token for password reset functionality. |
ResetTokenExpiration | DateTime? | The expiration date for the reset token. |
Sources: Models/User.cs, Migrations/UserDBContextModelSnapshot.cs:199-238
When creating a new model, you will create a similar class in the Models directory.
Step 2: Configure the Database
After defining the model, you must integrate it with Entity Framework Core.
2.1. Update the DbContext
Add a DbSet<T> property for your new entity to the UserDBContext class. This allows EF Core to query and save instances of your model.
// Data/UserDBContext.cs
public DbSet<User> User { get; set; }
public DbSet<Order> Orders { get; set; }
public DbSet<Product> Products { get; set; }
// Add your new DbSet here:
// public DbSet<YourNewModel> YourNewModels { get; set; }
Sources: Data/UserDBContext.cs:30-35
2.2. Create an Entity Type Configuration
Create a configuration class that implements IEntityTypeConfiguration<T> in the Configuration directory. This class defines table name, primary key, property constraints (like max length), and relationships.
// Configuration/UserConfiguration.cs
public class UserConfiguration : IEntityTypeConfiguration<User>
{
public void Configure(EntityTypeBuilder<User> builder)
{
builder.ToTable("User");
builder.HasKey(u => u.Id);
builder.Property(u => u.Email).IsRequired().HasMaxLength(100);
builder.Property(u => u.UserName).IsRequired().HasMaxLength(50);
builder.Property(u => u.Password).IsRequired().HasMaxLength(256);
builder.Property(u => u.Role).IsRequired();
}
}
Sources: Configuration/UserConfiguration.cs:7-25
2.3. Apply the Configuration
Apply your new configuration in the OnModelCreating method of UserDBContext.
// Data/UserDBContext.cs
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ApplyConfiguration(new UserConfiguration());
modelBuilder.ApplyConfiguration(new ProductConfiguration());
// Add your new configuration here:
// modelBuilder.ApplyConfiguration(new YourNewModelConfiguration());
modelBuilder.Seed();
}
Sources: Data/UserDBContext.cs:14-20
Database Schema Overview
This ER diagram shows the existing relationships between entities in the database. A new feature's model would be added to this schema.
Step 3: Implement the Business Logic (Service Layer)
The service layer contains the core business logic. It decouples the controllers from the data access implementation.
- Create an Interface: Define a new interface (e.g.,
IYourNewService) in theServicedirectory. This should declare the methods for your feature's operations (e.g.,GetAsync,AddAsync,UpdateAsync). - Create the Service Class: Implement the interface in a new class (e.g.,
YourNewService). This class will takeUserDBContextas a dependency via its constructor and use it to perform database operations.
The UserService class provides a clear example of how to implement CRUD operations.
// Service/user/UserService.cs
public class UserService : IUserService
{
private readonly UserDBContext _userDBContext;
public UserService(UserDBContext userDBContext)
{
_userDBContext = userDBContext;
}
public async Task<User> GetUserByUserNameAsync(string userName)
{
return await _userDBContext.User.FirstOrDefaultAsync(u => u.UserName == userName);
}
public async Task<User> CreateUserAsync(User user)
{
_userDBContext.User.Add(user);
await _userDBContext.SaveChangesAsync();
return user;
}
// ... other methods
}
Sources: Service/user/UserService.cs:11-48
Step 4: Expose API Endpoints (Controller Layer)
Controllers are responsible for handling incoming HTTP requests, validating input, calling the appropriate service method, and returning an HTTP response.
- Create a Controller: Add a new class inheriting from
ControllerBasein theControllersdirectory. - Inject the Service: Inject your new service interface into the controller's constructor.
- Define Endpoints: Create public methods for each API endpoint, decorated with HTTP method attributes (
[HttpGet],[HttpPost], etc.) and route templates.
Example: UserController Endpoints
The UserController exposes endpoints for user registration, login, and password management.
| Method | Verb | Route | Description |
|---|---|---|---|
Register | POST | api/v1/user/register | Registers a new user. |
Login | POST | api/v1/user/login | Authenticates a user and returns their data. |
ForgotPassword | POST | api/v1/user/forgot-password | Sends a password reset token to the user's email. |
ResetPassword | POST | api/v1/user/reset-password | Resets the user's password using a valid token. |
Get | GET | api/v1/user | Retrieves a list of all users. |
Get | GET | api/v1/user/{id} | Retrieves a single user by their ID. |
Sources: Controllers/Login/UserController.cs:16-180
User Registration Sequence
This diagram shows the sequence of events when a user registers through the API.
Step 5: Register Dependencies
Finally, you must register your new service and its interface with the application's dependency injection container in Program.cs. This allows the framework to automatically provide an instance of your service to controllers that require it.
// Program.cs
// ...
builder.Services.AddTransient<IUserService, UserService>();
builder.Services.AddScoped<IProductService, ProductService>();
builder.Services.AddScoped<IOrderService, OrderService>();
// Add your new service registration here:
// builder.Services.AddScoped<IYourNewService, YourNewService>();
// ...
Sources: Program.cs:33-35
The choice between AddScoped, AddTransient, and AddSingleton depends on the required lifetime of the service. AddScoped (scoped to the request) is a common choice for services that use a DbContext.
Summary
Adding a new feature to the WEBAPI project is a structured process that leverages its existing n-tier architecture. By following the five steps outlined—defining the model, configuring the database, implementing the service, creating the controller, and registering dependencies—developers can ensure their contributions are consistent, maintainable, and well-integrated into the application. The existing User, Product, and Order modules serve as excellent reference implementations for this pattern.