Solution Explorer
📁 th3chris.API
📄 Program.cs
📄 UsersController.cs
📄 Startup.cs
📁 Controllers
📄 AuthController.cs
📄 ProjectsController.cs
📄 OrdersController.cs
📁 Models
📄 User.cs
📄 Project.cs
📄 Order.cs
📄 BaseEntity.cs
📁 Services
📄 UserService.cs
📄 IUserService.cs
📄 AuthService.cs
📄 EmailService.cs
📁 Data
📄 AppDbContext.cs
📄 DbInitializer.cs
📁 DTOs
📄 UserDto.cs
📄 CreateUserRequest.cs
📄 UpdateUserRequest.cs
📁 Middleware
📄 ErrorHandlingMiddleware.cs
📄 LoggingMiddleware.cs
📁 Extensions
📄 ServiceExtensions.cs
📄 ControllerExtensions.cs
UsersController.cs
// File: UsersController.cs
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using System.Threading.Tasks;
using th3chris.API.Models;
using th3chris.API.Services;
using Microsoft.Extensions.Logging;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using System.Security.Claims;
using AutoMapper;
using FluentValidation;
namespace th3chris.API.Controllers
{
[ApiController]
[Route("api/[controller]")]
[Authorize]
public class UsersController : ControllerBase
{
private readonly IUserService _userService;
private readonly ILogger<UsersController> _logger;
private readonly IMapper _mapper;
private readonly IValidator<CreateUserRequest> _createValidator;
private readonly IValidator<UpdateUserRequest> _updateValidator;
public UsersController(
IUserService userService,
ILogger<UsersController> logger,
IMapper mapper,
IValidator<CreateUserRequest> createValidator,
IValidator<UpdateUserRequest> updateValidator)
{
_userService = userService ?? throw new ArgumentNullException(nameof(userService));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_mapper = mapper ?? throw new ArgumentNullException(nameof(mapper));
_createValidator = createValidator;
_updateValidator = updateValidator;
}
/// <summary>
/// Retrieves all users with pagination support
/// </summary>
/// <param name="pageNumber">Page number (default: 1)</param>
/// <param name="pageSize">Page size (default: 10, max: 100)</param>
/// <returns>Paginated list of users</returns>
[HttpGet]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
public async Task<ActionResult<PagedResult<UserDto>>> GetUsers(
[FromQuery] int pageNumber = 1,
[FromQuery] int pageSize = 10,
[FromQuery] string? search = null,
[FromQuery] string? sortBy = null,
[FromQuery] bool sortDescending = false)
{
try
{
if (pageNumber < 1)
return BadRequest("Page number must be greater than 0");
if (pageSize < 1 || pageSize > 100)
return BadRequest("Page size must be between 1 and 100");
_logger.LogInformation("Getting users - Page: {PageNumber}, Size: {PageSize}, Search: {Search}",
pageNumber, pageSize, search);
var currentUserId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
var filter = new UserFilter
{
PageNumber = pageNumber,
PageSize = pageSize,
Search = search,
SortBy = sortBy,
SortDescending = sortDescending,
RequestingUserId = currentUserId
};
var users = await _userService.GetUsersAsync(filter);
var userDtos = _mapper.Map<IEnumerable<UserDto>>(users.Items);
var result = new PagedResult<UserDto>
{
Items = userDtos,
TotalCount = users.TotalCount,
PageNumber = pageNumber,
PageSize = pageSize,
TotalPages = (int)Math.Ceiling((double)users.TotalCount / pageSize)
};
return Ok(result);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error occurred while getting users");
return StatusCode(StatusCodes.Status500InternalServerError,
"An error occurred while processing your request");
}
}
/// <summary>
/// Retrieves a specific user by ID
/// </summary>
/// <param name="id">User ID</param>
/// <returns>User details</returns>
[HttpGet("{id:int}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
public async Task<ActionResult<UserDetailDto>> GetUser(int id)
{
try
{
if (id <= 0)
return BadRequest("Invalid user ID");
var currentUserId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
_logger.LogInformation("Getting user {UserId} requested by {CurrentUserId}", id, currentUserId);
var user = await _userService.GetUserByIdAsync(id, currentUserId);
if (user == null)
{
_logger.LogWarning("User {UserId} not found or access denied for {CurrentUserId}", id, currentUserId);
return NotFound($"User with ID {id} not found");
}
var userDto = _mapper.Map<UserDetailDto>(user);
return Ok(userDto);
}
catch (UnauthorizedAccessException ex)
{
_logger.LogWarning(ex, "Unauthorized access attempt for user {UserId}", id);
return Forbid();
}
catch (Exception ex)
{
_logger.LogError(ex, "Error occurred while getting user {UserId}", id);
return StatusCode(StatusCodes.Status500InternalServerError,
"An error occurred while processing your request");
}
}
/// <summary>
/// Creates a new user
/// </summary>
/// <param name="request">User creation request</param>
/// <returns>Created user</returns>
[HttpPost]
[ProducesResponseType(StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status409Conflict)]
public async Task<ActionResult<UserDto>> CreateUser([FromBody] CreateUserRequest request)
{
try
{
if (request == null)
return BadRequest("Request body is required");
var validationResult = await _createValidator.ValidateAsync(request);
if (!validationResult.IsValid)
{
var errors = validationResult.Errors.Select(e => e.ErrorMessage);
return BadRequest(new { Errors = errors });
}
var currentUserId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
_logger.LogInformation("Creating new user {Email} by {CurrentUserId}", request.Email, currentUserId);
var user = _mapper.Map<User>(request);
user.CreatedBy = currentUserId;
user.CreatedAt = DateTime.UtcNow;
var createdUser = await _userService.CreateUserAsync(user);
var userDto = _mapper.Map<UserDto>(createdUser);
return CreatedAtAction(nameof(GetUser), new { id = createdUser.Id }, userDto);
}
catch (DuplicateUserException ex)
{
_logger.LogWarning(ex, "Attempt to create duplicate user {Email}", request?.Email);
return Conflict(new { Message = "User with this email already exists" });
}
catch (Exception ex)
{
_logger.LogError(ex, "Error occurred while creating user");
return StatusCode(StatusCodes.Status500InternalServerError,
"An error occurred while processing your request");
}
}
/// <summary>
/// Updates an existing user
/// </summary>
/// <param name="id">User ID</param>
/// <param name="request">User update request</param>
/// <returns>Updated user</returns>
[HttpPut("{id:int}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
public async Task<ActionResult<UserDto>> UpdateUser(int id, [FromBody] UpdateUserRequest request)
{
try
{
if (id <= 0)
return BadRequest("Invalid user ID");
if (request == null)
return BadRequest("Request body is required");
var validationResult = await _updateValidator.ValidateAsync(request);
if (!validationResult.IsValid)
{
var errors = validationResult.Errors.Select(e => e.ErrorMessage);
return BadRequest(new { Errors = errors });
}
var currentUserId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
_logger.LogInformation("Updating user {UserId} by {CurrentUserId}", id, currentUserId);
var existingUser = await _userService.GetUserByIdAsync(id, currentUserId);
if (existingUser == null)
{
_logger.LogWarning("User {UserId} not found for update by {CurrentUserId}", id, currentUserId);
return NotFound($"User with ID {id} not found");
}
_mapper.Map(request, existingUser);
existingUser.UpdatedBy = currentUserId;
existingUser.UpdatedAt = DateTime.UtcNow;
var updatedUser = await _userService.UpdateUserAsync(existingUser);
var userDto = _mapper.Map<UserDto>(updatedUser);
return Ok(userDto);
}
catch (UnauthorizedAccessException ex)
{
_logger.LogWarning(ex, "Unauthorized update attempt for user {UserId}", id);
return Forbid();
}
catch (Exception ex)
{
_logger.LogError(ex, "Error occurred while updating user {UserId}", id);
return StatusCode(StatusCodes.Status500InternalServerError,
"An error occurred while processing your request");
}
}
/// <summary>
/// Deletes a user by ID
/// </summary>
/// <param name="id">User ID</param>
/// <returns>No content on success</returns>
[HttpDelete("{id:int}")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
public async Task<IActionResult> DeleteUser(int id)
{
try
{
if (id <= 0)
return BadRequest("Invalid user ID");
var currentUserId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
_logger.LogInformation("Deleting user {UserId} by {CurrentUserId}", id, currentUserId);
var success = await _userService.DeleteUserAsync(id, currentUserId);
if (!success)
{
_logger.LogWarning("User {UserId} not found for deletion by {CurrentUserId}", id, currentUserId);
return NotFound($"User with ID {id} not found");
}
_logger.LogInformation("User {UserId} successfully deleted by {CurrentUserId}", id, currentUserId);
return NoContent();
}
catch (UnauthorizedAccessException ex)
{
_logger.LogWarning(ex, "Unauthorized deletion attempt for user {UserId}", id);
return Forbid();
}
catch (Exception ex)
{
_logger.LogError(ex, "Error occurred while deleting user {UserId}", id);
return StatusCode(StatusCodes.Status500InternalServerError,
"An error occurred while processing your request");
}
}
}
}
// File: UserService.cs
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using th3chris.API.Models;
using th3chris.API.Data;
using th3chris.API.Exceptions;
namespace th3chris.API.Services
{
public class UserService : IUserService
{
private readonly AppDbContext _context;
private readonly ILogger<UserService> _logger;
public UserService(AppDbContext context, ILogger<UserService> logger)
{
_context = context ?? throw new ArgumentNullException(nameof(context));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
public async Task<PagedResult<User>> GetUsersAsync(UserFilter filter)
{
var query = _context.Users.AsQueryable();
if (!string.IsNullOrWhiteSpace(filter.Search))
{
var searchTerm = filter.Search.ToLower();
query = query.Where(u => u.Email.ToLower().Contains(searchTerm) ||
u.FirstName.ToLower().Contains(searchTerm) ||
u.LastName.ToLower().Contains(searchTerm));
}
var totalCount = await query.CountAsync();
if (!string.IsNullOrWhiteSpace(filter.SortBy))
{
query = ApplySorting(query, filter.SortBy, filter.SortDescending);
}
else
{
query = query.OrderBy(u => u.LastName).ThenBy(u => u.FirstName);
}
var users = await query
.Skip((filter.PageNumber - 1) * filter.PageSize)
.Take(filter.PageSize)
.ToListAsync();
return new PagedResult<User>
{
Items = users,
TotalCount = totalCount
};
}
}
}
Test Explorer
GetUsers_ReturnsOk
GetUsers_WithPagination
GetUsers_WithSearch
GetUser_ValidId_ReturnsUser
GetUser_InvalidId_NotFound
CreateUser_ValidData
CreateUser_DuplicateEmail
CreateUser_InvalidData
UpdateUser_ValidData
UpdateUser_NotFound
UpdateUser_Unauthorized
DeleteUser_Success
DeleteUser_NotFound
DeleteUser_Unauthorized
Auth_Login_ValidCredentials
Auth_Login_InvalidPassword
Auth_Register_NewUser
Email_SendWelcome_Success
Validation_User_RequiredFields
Validation_Email_Format
Solution Explorer
📁 th3chris.API
📄 Program.cs
📄 UsersController.cs
📄 Startup.cs
📁 Controllers
📄 AuthController.cs
📄 ProjectsController.cs
📄 OrdersController.cs
📁 Models
📄 User.cs
📄 Project.cs
📄 Order.cs
📄 BaseEntity.cs
📁 Services
📄 UserService.cs
📄 IUserService.cs
📄 AuthService.cs
📄 EmailService.cs
📁 Data
📄 AppDbContext.cs
📄 DbInitializer.cs
📁 DTOs
📄 UserDto.cs
📄 CreateUserRequest.cs
📄 UpdateUserRequest.cs
📁 Middleware
📄 ErrorHandlingMiddleware.cs
📄 LoggingMiddleware.cs
📁 Extensions
📄 ServiceExtensions.cs
📄 ControllerExtensions.cs
UsersController.cs
// File: UsersController.cs
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using System.Threading.Tasks;
using th3chris.API.Models;
using th3chris.API.Services;
using Microsoft.Extensions.Logging;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using System.Security.Claims;
using AutoMapper;
using FluentValidation;
namespace th3chris.API.Controllers
{
[ApiController]
[Route("api/[controller]")]
[Authorize]
public class UsersController : ControllerBase
{
private readonly IUserService _userService;
private readonly ILogger<UsersController> _logger;
private readonly IMapper _mapper;
private readonly IValidator<CreateUserRequest> _createValidator;
private readonly IValidator<UpdateUserRequest> _updateValidator;
public UsersController(
IUserService userService,
ILogger<UsersController> logger,
IMapper mapper,
IValidator<CreateUserRequest> createValidator,
IValidator<UpdateUserRequest> updateValidator)
{
_userService = userService ?? throw new ArgumentNullException(nameof(userService));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_mapper = mapper ?? throw new ArgumentNullException(nameof(mapper));
_createValidator = createValidator;
_updateValidator = updateValidator;
}
/// <summary>
/// Retrieves all users with pagination support
/// </summary>
/// <param name="pageNumber">Page number (default: 1)</param>
/// <param name="pageSize">Page size (default: 10, max: 100)</param>
/// <returns>Paginated list of users</returns>
[HttpGet]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
public async Task<ActionResult<PagedResult<UserDto>>> GetUsers(
[FromQuery] int pageNumber = 1,
[FromQuery] int pageSize = 10,
[FromQuery] string? search = null,
[FromQuery] string? sortBy = null,
[FromQuery] bool sortDescending = false)
{
try
{
if (pageNumber < 1)
return BadRequest("Page number must be greater than 0");
if (pageSize < 1 || pageSize > 100)
return BadRequest("Page size must be between 1 and 100");
_logger.LogInformation("Getting users - Page: {PageNumber}, Size: {PageSize}, Search: {Search}",
pageNumber, pageSize, search);
var currentUserId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
var filter = new UserFilter
{
PageNumber = pageNumber,
PageSize = pageSize,
Search = search,
SortBy = sortBy,
SortDescending = sortDescending,
RequestingUserId = currentUserId
};
var users = await _userService.GetUsersAsync(filter);
var userDtos = _mapper.Map<IEnumerable<UserDto>>(users.Items);
var result = new PagedResult<UserDto>
{
Items = userDtos,
TotalCount = users.TotalCount,
PageNumber = pageNumber,
PageSize = pageSize,
TotalPages = (int)Math.Ceiling((double)users.TotalCount / pageSize)
};
return Ok(result);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error occurred while getting users");
return StatusCode(StatusCodes.Status500InternalServerError,
"An error occurred while processing your request");
}
}
/// <summary>
/// Retrieves a specific user by ID
/// </summary>
/// <param name="id">User ID</param>
/// <returns>User details</returns>
[HttpGet("{id:int}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
public async Task<ActionResult<UserDetailDto>> GetUser(int id)
{
try
{
if (id <= 0)
return BadRequest("Invalid user ID");
var currentUserId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
_logger.LogInformation("Getting user {UserId} requested by {CurrentUserId}", id, currentUserId);
var user = await _userService.GetUserByIdAsync(id, currentUserId);
if (user == null)
{
_logger.LogWarning("User {UserId} not found or access denied for {CurrentUserId}", id, currentUserId);
return NotFound($"User with ID {id} not found");
}
var userDto = _mapper.Map<UserDetailDto>(user);
return Ok(userDto);
}
catch (UnauthorizedAccessException ex)
{
_logger.LogWarning(ex, "Unauthorized access attempt for user {UserId}", id);
return Forbid();
}
catch (Exception ex)
{
_logger.LogError(ex, "Error occurred while getting user {UserId}", id);
return StatusCode(StatusCodes.Status500InternalServerError,
"An error occurred while processing your request");
}
}
/// <summary>
/// Creates a new user
/// </summary>
/// <param name="request">User creation request</param>
/// <returns>Created user</returns>
[HttpPost]
[ProducesResponseType(StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status409Conflict)]
public async Task<ActionResult<UserDto>> CreateUser([FromBody] CreateUserRequest request)
{
try
{
if (request == null)
return BadRequest("Request body is required");
var validationResult = await _createValidator.ValidateAsync(request);
if (!validationResult.IsValid)
{
var errors = validationResult.Errors.Select(e => e.ErrorMessage);
return BadRequest(new { Errors = errors });
}
var currentUserId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
_logger.LogInformation("Creating new user {Email} by {CurrentUserId}", request.Email, currentUserId);
var user = _mapper.Map<User>(request);
user.CreatedBy = currentUserId;
user.CreatedAt = DateTime.UtcNow;
var createdUser = await _userService.CreateUserAsync(user);
var userDto = _mapper.Map<UserDto>(createdUser);
return CreatedAtAction(nameof(GetUser), new { id = createdUser.Id }, userDto);
}
catch (DuplicateUserException ex)
{
_logger.LogWarning(ex, "Attempt to create duplicate user {Email}", request?.Email);
return Conflict(new { Message = "User with this email already exists" });
}
catch (Exception ex)
{
_logger.LogError(ex, "Error occurred while creating user");
return StatusCode(StatusCodes.Status500InternalServerError,
"An error occurred while processing your request");
}
}
/// <summary>
/// Updates an existing user
/// </summary>
/// <param name="id">User ID</param>
/// <param name="request">User update request</param>
/// <returns>Updated user</returns>
[HttpPut("{id:int}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
public async Task<ActionResult<UserDto>> UpdateUser(int id, [FromBody] UpdateUserRequest request)
{
try
{
if (id <= 0)
return BadRequest("Invalid user ID");
if (request == null)
return BadRequest("Request body is required");
var validationResult = await _updateValidator.ValidateAsync(request);
if (!validationResult.IsValid)
{
var errors = validationResult.Errors.Select(e => e.ErrorMessage);
return BadRequest(new { Errors = errors });
}
var currentUserId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
_logger.LogInformation("Updating user {UserId} by {CurrentUserId}", id, currentUserId);
var existingUser = await _userService.GetUserByIdAsync(id, currentUserId);
if (existingUser == null)
{
_logger.LogWarning("User {UserId} not found for update by {CurrentUserId}", id, currentUserId);
return NotFound($"User with ID {id} not found");
}
_mapper.Map(request, existingUser);
existingUser.UpdatedBy = currentUserId;
existingUser.UpdatedAt = DateTime.UtcNow;
var updatedUser = await _userService.UpdateUserAsync(existingUser);
var userDto = _mapper.Map<UserDto>(updatedUser);
return Ok(userDto);
}
catch (UnauthorizedAccessException ex)
{
_logger.LogWarning(ex, "Unauthorized update attempt for user {UserId}", id);
return Forbid();
}
catch (Exception ex)
{
_logger.LogError(ex, "Error occurred while updating user {UserId}", id);
return StatusCode(StatusCodes.Status500InternalServerError,
"An error occurred while processing your request");
}
}
/// <summary>
/// Deletes a user by ID
/// </summary>
/// <param name="id">User ID</param>
/// <returns>No content on success</returns>
[HttpDelete("{id:int}")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
public async Task<IActionResult> DeleteUser(int id)
{
try
{
if (id <= 0)
return BadRequest("Invalid user ID");
var currentUserId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
_logger.LogInformation("Deleting user {UserId} by {CurrentUserId}", id, currentUserId);
var success = await _userService.DeleteUserAsync(id, currentUserId);
if (!success)
{
_logger.LogWarning("User {UserId} not found for deletion by {CurrentUserId}", id, currentUserId);
return NotFound($"User with ID {id} not found");
}
_logger.LogInformation("User {UserId} successfully deleted by {CurrentUserId}", id, currentUserId);
return NoContent();
}
catch (UnauthorizedAccessException ex)
{
_logger.LogWarning(ex, "Unauthorized deletion attempt for user {UserId}", id);
return Forbid();
}
catch (Exception ex)
{
_logger.LogError(ex, "Error occurred while deleting user {UserId}", id);
return StatusCode(StatusCodes.Status500InternalServerError,
"An error occurred while processing your request");
}
}
}
}
// File: UserService.cs
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using th3chris.API.Models;
using th3chris.API.Data;
using th3chris.API.Exceptions;
namespace th3chris.API.Services
{
public class UserService : IUserService
{
private readonly AppDbContext _context;
private readonly ILogger<UserService> _logger;
public UserService(AppDbContext context, ILogger<UserService> logger)
{
_context = context ?? throw new ArgumentNullException(nameof(context));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
public async Task<PagedResult<User>> GetUsersAsync(UserFilter filter)
{
var query = _context.Users.AsQueryable();
if (!string.IsNullOrWhiteSpace(filter.Search))
{
var searchTerm = filter.Search.ToLower();
query = query.Where(u => u.Email.ToLower().Contains(searchTerm) ||
u.FirstName.ToLower().Contains(searchTerm) ||
u.LastName.ToLower().Contains(searchTerm));
}
var totalCount = await query.CountAsync();
if (!string.IsNullOrWhiteSpace(filter.SortBy))
{
query = ApplySorting(query, filter.SortBy, filter.SortDescending);
}
else
{
query = query.OrderBy(u => u.LastName).ThenBy(u => u.FirstName);
}
var users = await query
.Skip((filter.PageNumber - 1) * filter.PageSize)
.Take(filter.PageSize)
.ToListAsync();
return new PagedResult<User>
{
Items = users,
TotalCount = totalCount
};
}
}
}
Test Explorer
GetUsers_ReturnsOk
GetUsers_WithPagination
GetUsers_WithSearch
GetUser_ValidId_ReturnsUser
GetUser_InvalidId_NotFound
CreateUser_ValidData
CreateUser_DuplicateEmail
CreateUser_InvalidData
UpdateUser_ValidData
UpdateUser_NotFound
UpdateUser_Unauthorized
DeleteUser_Success
DeleteUser_NotFound
DeleteUser_Unauthorized
Auth_Login_ValidCredentials
Auth_Login_InvalidPassword
Auth_Register_NewUser
Email_SendWelcome_Success
Validation_User_RequiredFields
Validation_Email_Format
Christian Daniel
Ihre Kunden warten.Ihre Prozesse sind manuell.Ich ändere das.

Prozessautomatisierung, Cloud-Lösungen und künstliche Intelligenz — für KMU und Enterprise.

Kommt Ihnen das bekannt vor?

Leads gehen nach Feierabend verloren

Um 20 Uhr antwortet niemand — und der Kunde ist am nächsten Morgen beim Wettbewerb.

Das Team verliert Stunde über Stunde mit manuellen Aufgaben

Datenerfassung, E-Mail-Sortierung, Berichterstellung — alles Aufgaben, die sich automatisieren lassen. In Sekunden statt Stunden.

Technologie allein löst kein Problem

Was bringt konkret etwas für Ihr Geschäft — und was ist nur Hype? Genau das klären wir.

Vibe Coding liefert Code. Kein Verständnis.

Schnell generiert, nie hinterfragt. Irgendwann kommt die Rechnung — Sicherheitslücken, Ausfälle, und ein Entwickler, der seinen Code nicht versteht.

Lösungen, die wirklich funktionieren

KI-Chatbots, die 24/7 arbeiten

Beantworten Kundenfragen, qualifizieren Leads und buchen Termine — auch um Mitternacht.

Für Unternehmen mit Kundenkontakt

Prozessautomatisierung mit KI

Dokumente extrahieren, E-Mails klassifizieren, Berichte generieren — automatisch statt manuell.

Für Teams mit repetitiven Aufgaben

Cloud-Systeme, die skalieren

Kubernetes, Microservices, CI/CD — Architektur, die mit Ihrem Geschäft mitwächst.

Für Unternehmen mit Wachstumsambitionen

Team-Verstärkung, wenn es drauf ankommt

Senior-Architektur-Erfahrung für Ihr bestehendes Team — von Wochen bis Jahre.

Für Teams, die Expertise brauchen

Projekt-Highlights

Ausgewählte Projekte mit messbarem Impact

Vertraut von führenden Unternehmen

10Geeks
Hoffmann Group
Schneider Electric
Sartorius
Razorfish Frankfurt
Quickpac
Fimat International
10Geeks
Hoffmann Group
Schneider Electric
Sartorius
Razorfish Frankfurt
Quickpac
Fimat International

Arbeitsmodell – flexibel nach Ihrem Bedarf

Solo-Entwicklung

Komplettlösungen aus einer Hand

Von der Architektur über die Implementierung bis zum Deployment – ein Ansprechpartner für alles. Kein Overhead, kurze Entscheidungswege, maximale Geschwindigkeit.

Quickmail/Quickpac: 4 Jahre komplette Mobile-App-Entwicklung als Solo-Developer für die Schweizer Post. 6 eigenständige Lösungen. Offline-first für Bergregionen.

Team-Integration

Nahtlos im bestehenden Team

Code Reviews, Mentoring, gemeinsame Architektur-Entscheidungen. Ich integriere mich in Ihr Team, als wäre ich seit Tag 1 dabei – und bringe dabei die Erfahrung mit, die Teams weiterbringt.

Hoffmann Group: 5+ Jahre im 5-köpfigen Digital Services Team. Gemeinsam 3 zentrale DataHubs aufgebaut und 8 Shared Libraries gepflegt.

Enterprise-Architektur

Komplexe Systemlandschaften beherrschen

Wenn mehrere Divisionen, Legacy-Systeme und internationale Teams zusammenspielen müssen – dann bin ich in meinem Element. SAP-Integration, Multi-System-Orchestrierung, regulatorische Compliance.

Schneider Electric: Integration diverser Divisionen in eine nahtlose IDE – nachdem Microsoft-Profis es als 'unmöglich' eingestuft hatten.

KI ist mein Werkzeug – nicht mein Autopilot

Mein Ansatz für verantwortungsvollen Einsatz von KI in der Softwareentwicklung

Jede Zeile wird verstanden

KI generiert Code schneller als je zuvor. Aber Code der nicht verstanden wird, ist technische Schuld vom ersten Tag an. Ich nutze AI für Speed – und reviewe, verstehe und optimiere jedes Ergebnis.

Sicherheit ist kein Nachgedanke

KI-generierter Code hat blinde Flecken: Injection-Anfälligkeiten, fehlende Input-Validierung, ungeprüfte Edge Cases. Mit 25+ Jahren Erfahrung erkenne ich diese Schwachstellen – bevor sie in Produktion landen.

Code muss auch ohne KI wartbar sein

Was passiert wenn das LLM-Modell sich ändert oder der AI-Service ausfällt? Mein Code folgt Clean Architecture Prinzipien und ist für Menschen lesbar – heute und in 5 Jahren.

KI-Engineering in Aktion

Live Demo: Diese Features laufen auf meinem eigenen RAG-System

RAG Pipeline

So funktioniert der Chat auf dieser Seite: Ihre Frage wird in einen Vektor umgewandelt, relevante Dokumente gesucht und mit einem LLM zu einer fundierten Antwort verarbeitet.

16
Dokumente
70k+
Tokens
6
Chunks/Query

Möchten Sie mehr Details? Fragen Sie mich!

Stellen Sie mir Fragen zu meinen Projekten, Technologien oder Erfahrungen.

Was andere sagen

Oscar Angress
Oscar Angress
Cyber Security Consultant
Bosch Engineering GmbH
LinkedIn - Referenz
... outstanding mind with excellent skills in development; great software architect. Highly recommended if you need to find a professional fast and scalable solution. We've been working together on a project that was rated by Microsoft professionals as "not possible". Together with Christian our team managed to deliver a great working solution/product!

Enterprise-Projekte & Referenzen

13 Engagements25+ Jahre100% Remote seit 2010

Klicken Sie auf ein Projekt, um direkt mit dem AI Assistant darüber zu chatten.

Letzte Einsätze:

... und viele weitere spannende Projekte aus über 25 Jahren Berufserfahrung.

Tech Stack & Expertise

AI & RAG Systems

RAG-Pipelines, Embeddings, Vector Search mit pgvector und Weaviate, Semantic Kernel, Dify – von der Wissensbasis bis zur intelligenten Antwort. Produktionsreife KI-Systeme, nicht nur Prototypen.

AI Agent Development

LLM-orchestrierte Agenten mit Tool-Use, MCP (Model Context Protocol), autonome Workflows – AI Agents die eigenständig Aufgaben lösen und sich in bestehende Systeme integrieren.

C# & .NET

Mein Heimathafen seit über 15 Jahren. Von ASP.NET Core über Entity Framework bis zu modernen Minimal APIs – hier fühle ich mich zuhause und baue Backend-Systeme, die rocken.

Azure Cloud

Cloud-native Architecture at its best: AKS für Container-Orchestrierung, Service Bus für Messaging, Cosmos DB für globale Daten – der perfekte Playground für moderne Enterprise-Lösungen.

Kubernetes

Container-Orchestrierung ist Kunst und Wissenschaft zugleich. Mit K8s bringe ich Microservices ins Rollen, manage Deployments ohne Downtime und halte Systeme skalierbar und resilient.

RabbitMQ

Message-driven Architecture für die Win. Asynchrone Communication, Event-driven Workflows, und Entkopplung von Services – RabbitMQ ist der unsichtbare Held in verteilten Systemen.

Elasticsearch

Wenn Suche mehr sein muss als SQL LIKE. Full-Text Search, Log-Aggregation mit dem Elastic Stack, Real-time Analytics – hier wird aus Daten echte Insight gewonnen.

TypeScript & Next.js

Frontend, das Spaß macht: Type-safe React mit Server Components, moderne Routing-Patterns, und Performance out of the box. Full-Stack heißt auch, dass der Browser genauso wichtig ist wie der Server.

GitOps & DevOps

ArgoCD, Flux, GitLab CI, Terraform – Infrastructure as Code und GitOps-Workflows für reproduzierbare, auditierbare Deployments. Die gesamte Infrastruktur lebt im Git-Repository.

CI/CD & Automation

Deploy sollte keine Wissenschaft sein. Mit GitHub Actions, Azure DevOps und automatisierten Pipelines mache ich aus Manual Work schnelle, sichere und reproduzierbare Release-Prozesse.

Vorher vs. Nachher

So verändert KI Ihre Arbeitsabläufe

Vorher: Manuell

Kunde schreibt um 20 Uhr eine E-Mail

Niemand antwortet bis zum nächsten Morgen

Mitarbeiter liest, leitet weiter, fragt nach

Antwort kommt erst nach 24-48 Stunden

Kunde ist längst beim Wettbewerb

Nachher: KI-gestützt

Kunde chattet um 20 Uhr auf der Website

KI-Chatbot antwortet sofort und qualifiziert

Relevante Infos werden automatisch erfasst

Termin wird direkt gebucht

Mitarbeiter startet morgens mit warmem Lead

Bereit, Ihre Prozesse zu automatisieren?

Beschreiben Sie Ihr Projekt im Chat — oder schreiben Sie mir direkt.

E-Mail schreiben