Previous part: Creating a WebApi Project in .NET 9 [GamesCatalog] 18
Step 1. Let's work on user creation. To start, we'll create the user repository.
![]()
Step 1.1. Create the CreateAsync and GetByEmailAsync functions and generate the interface:
using Microsoft.EntityFrameworkCore;
using Models.DTOs;
namespace Repos;
public class UserRepo(IDbContextFactory<DbCtx> DbCtx) : IUserRepo
{
public async Task CreateAsync(UserDTO user)
{
using var context = DbCtx.CreateDbContext();
await context.User.AddAsync(user);
await context.SaveChangesAsync();
}
public async Task<UserDTO?> GetByEmailAsync(string email)
{
using var context = DbCtx.CreateDbContext();
return await context.User.FirstOrDefaultAsync(x => x.Email.Equals(email));
}
}
Step 2. Let's create the models that we'll use for user creation.
![]()
We'll have two folders: Requests and Responses. Inside Requests, we'll create a folder for the user models. The user models will be divided into separate parts that can eventually be used individually:
Step 2.1. ReqBaseModel.cs will contain the function responsible for validating the model fields:
Code for ReqBaseModel.cs
using System.ComponentModel.DataAnnotations;
namespace Models.Reqs;
public record ReqBaseModel
{
public string? Validate()
{
List<ValidationResult> validationResult = [];
Validator.TryValidateObject(this, new ValidationContext(this), validationResult, true);
if (validationResult.Count > 0) return validationResult.First().ErrorMessage;
else return null;
}
}
Step 2.2. Inside the User folder, ReqUserEmail.cs will contain the validation Annotations for the email field:
using System.ComponentModel.DataAnnotations;
namespace Models.Reqs.User;
public record ReqUserEmail : ReqBaseModel
{
[Display(Name = "Email")]
[DataType(DataType.EmailAddress)]
[Required(ErrorMessage = "Email is required")]
[RegularExpression(@"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}$", ErrorMessage = "Invalid Email")]
[StringLength(250, MinimumLength = 4)]
public required string Email { get; init; }
}
Step 2.3. ReqUserSession.cs will contain the password validation and inherit from ReqUserEmail.
using System.ComponentModel.DataAnnotations;
namespace Models.Reqs.User;
public record ReqUserSession : ReqUserEmail
{
[Display(Name = "Password")]
[DataType(DataType.Password)]
[Required(ErrorMessage = "Password is required")]
[StringLength(30, MinimumLength = 4)]
public required string Password { get; init; }
}
Step 2.4. ReqUser.cs will inherit from ReqUserSession.cs and include the validation for the name field, which will be optional.
using System.ComponentModel.DataAnnotations;
namespace Models.Reqs.User;
public record ReqUser : ReqUserSession
{
[StringLength(150)]
public string? Name { get; init; } = null;
}
Step 2.5. In the Responses folder, we'll create BaseResp.cs to handle the service responses:
namespace Models.Resps;
public record BaseResp
{
public bool Success { get; set; } = true;
public object? Content { get; init; }
public Error? Error { get; init; } = null;
public BaseResp(object? content, string? errorMessage = null)
{
if (!string.IsNullOrEmpty(errorMessage))
{
Success = false;
Content = null;
if (!string.IsNullOrEmpty(errorMessage))
Error = new Error { Message = errorMessage };
}
else Content = content;
}
}
public record Error
{
public required string Message { get; init; }
}
Step 2.6. We'll also create the model ResUser.cs:
namespace Models.Resps;
public record ResUser
{
public int Id { get; init; }
public string? Name { get; init; }
public string? Email { get; init; }
public DateTime CreatedAt { get; init; }
}
Step 3. Now let's create UserService.cs:
![]()
Code
using Models.DTOs;
using Models.Reqs.User;
using Models.Resps;
using Repos;
namespace Services
{
public class UserService(IUserRepo userRepo) : IUserService
{
public async Task<BaseResp> CreateAsync(ReqUser reqUser)
{
string? validateError = reqUser.Validate();
if (!string.IsNullOrEmpty(validateError)) return new BaseResp(null, validateError);
UserDTO user = new()
{
Name = reqUser.Name,
Email = reqUser.Email,
Password = reqUser.Password,
CreatedAt = DateTime.Now
};
string? existingUserMessage = await ValidateExistingUserAsync(user);
if (existingUserMessage != null) { return new BaseResp(null, existingUserMessage); }
await userRepo.CreateAsync(user);
ResUser? resUser = new()
{
Id = user.Id,
Name = user.Name,
Email = user.Email,
CreatedAt = user.CreatedAt
};
return new BaseResp(resUser);
}
protected async Task<string?> ValidateExistingUserAsync(UserDTO user)
{
UserDTO? userResp = await userRepo.GetByEmailAsync(user.Email);
if (userResp is not null && userResp.Email.Equals(user.Email))
return "User Email already exists.";
return null;
}
}
}
In this code, inside CreateAsync(), we validate the user data using DataAnnotations. With ValidateExistingUserAsync(), we check if a user with the same email already exists, and if not, the user is registered. Extract the interface.
Step 4. Let's create a BaseController and a UserController.
![]()
Code for BaseController
using Microsoft.AspNetCore.Mvc;
using Models.Resps;
namespace GamesCatalogAPI.Controllers
{
public class BaseController : Controller
{
protected IActionResult BuildResponse(BaseResp baseResp) =>
(!string.IsNullOrEmpty(baseResp.Error?.Message)) ?
BadRequest(baseResp.Error.Message) :
Ok(baseResp.Content);
}
}
Code for UserController
using Microsoft.AspNetCore.Mvc;
using Models.Reqs.User;
using Services;
namespace GamesCatalogAPI.Controllers
{
[Route("[Controller]")]
[ApiController]
public class UserController(IUserService userService) : BaseController
{
[Route("")]
[HttpPost]
public async Task<IActionResult> SignUp(ReqUser reqUser) => BuildResponse(await userService.CreateAsync(reqUser));
}
}
Step 5. In the Program.cs, let's add UserService and UserRepo to the Dependency Injection (DI) container:
![]()
Code
#region Repos
builder.Services.AddScoped<IUserRepo, UserRepo>();
#endregion
#region Servs
builder.Services.AddScoped<IUserService, UserService>();
#endregion
Step 6. Update the GamesCatalogAPI.http file to insert a test for the user creation POST request:
![]()
Code
@GamesCatalogAPI_HostAddress = http://localhost:5048
POST {{GamesCatalogAPI_HostAddress}}/User/
Content-Type: application/json
{
"name": "test",
"email": "[email protected]",
"password": "pass123"
}
###
Step 7. When running the system and sending the request, we receive a successful response.
![Formatted raw Header]()
Step 8. When sending the request again, we receive a response indicating that the email is already registered.
![Formatted raw Header]()
In the next step, we will encrypt the email and generate a token to manage user access.
Code on git: GamesCatalogBackEnd