Project XYZ – Solution & Folder Structure
This solution is organized for best-practice, scalable ASP.NET Core API development. It contains two main projects and a modular folder structure for maintainability and growth.
Projects
Project XYZ.API The main ASP.NET Core Web API project. Contains business logic, endpoints, models, services, repositories, configuration, and more.
Project XYZ.Tests The xUnit test project for unit/integration tests. References the API project for direct testing of logic and controllers.
Main Project Folders (Project XYZ.API/
)
Folder | Description |
---|---|
Controllers/ | API endpoints (e.g., UserController.cs ). |
Domain/ | Core business entities/models (e.g., User.cs ). |
Dtos/ | Data Transfer Objects for API in/out. |
Services/ | Business logic layer (e.g., UserService.cs ). |
Repositories/ | Data access layer (e.g., UserRepository.cs ). |
Interfaces/ | Abstractions for services, repos, tokens, etc. |
Data/ | EF Core DbContext and data seeding. |
Mappers/ | Logic for mapping between entities and DTOs. |
Middlewares/ | Cross-cutting concerns (e.g., error handling middleware). |
Configuration/ | Dependency injection and app configuration. |
Extensions/ | Extension methods for utility code. |
Helpers/ | Utility/helper classes. |
Migrations/ | EF Core database migrations. |
Features/ | (Optional) Feature-based folders for large, modular code. |
Example Feature-Based Structure
Project XYZ.API/Features/User/
Controllers/
Services/
Repositories/
Dtos/
Mappers/
Repeat for each major feature (e.g., Account, Speciality, etc.) as your API grows.
Key Sample Files
- Controllers/UserController.cs
Example endpoint:
GET /User/{id}
. - Domain/User.cs Example business entity.
- Dtos/UserDto.cs DTO for user data exchange.
- Services/UserService.cs Business logic for users.
- Repositories/UserRepository.cs Data access for users.
- Interfaces/IUserService.cs, IUserRepository.cs, ITokenService.cs Abstractions for clean architecture.
- Mappers/UserMapper.cs Maps between domain and DTO.
- Middlewares/ErrorHandlingMiddleware.cs Handles global errors.
- Configuration/DependencyInjection.cs Central DI/service registration.
Test Project Structure (Project XYZ.Tests/
)
- Controllers/UserControllerTests.cs Unit tests for API endpoints.
- Services/UserServiceTests.cs Unit tests for service logic.
Program Entry
Program.cs Minimal, modern .NET startup:
- Registers all dependencies
- Adds Swagger for API docs
- Configures endpoints and middleware
SH File Templating
#!/bin/bash
# Replace YOUR_PROJECT
# Solution & Project Names
SOLUTION_NAME="YOUR_PROJECT"
PROJECT_NAME="YOUR_PROJECT.API"
TEST_PROJECT_NAME="YOUR_PROJECT.Tests"
# 1. Create Solution & Projects
dotnet new sln -n $SOLUTION_NAME
dotnet new webapi -n $PROJECT_NAME
dotnet new xunit -n $TEST_PROJECT_NAME
dotnet sln add $PROJECT_NAME/$PROJECT_NAME.csproj
dotnet sln add $TEST_PROJECT_NAME/$TEST_PROJECT_NAME.csproj
dotnet add $TEST_PROJECT_NAME/$TEST_PROJECT_NAME.csproj reference $PROJECT_NAME/$PROJECT_NAME.csproj
# 2. Create core folders
FOLDERS=("Controllers" "Domain" "Dtos" "Services" "Repositories" "Interfaces" "Data" "Mappers" "Middlewares" "Configuration" "Extensions" "Helpers" "Migrations")
for folder in "${FOLDERS[@]}"; do
mkdir -p $PROJECT_NAME/$folder
touch $PROJECT_NAME/$folder/.gitkeep
done
# 3. Feature-based folders (example)
FEATURES=("User" "Account" "Speciality")
for feature in "${FEATURES[@]}"; do
mkdir -p $PROJECT_NAME/Features/$feature/Controllers
mkdir -p $PROJECT_NAME/Features/$feature/Services
mkdir -p $PROJECT_NAME/Features/$feature/Repositories
mkdir -p $PROJECT_NAME/Features/$feature/Dtos
mkdir -p $PROJECT_NAME/Features/$feature/Mappers
touch $PROJECT_NAME/Features/$feature/.gitkeep
done
# 4. Sample classes
# Interface: Repository
cat > $PROJECT_NAME/Interfaces/IUserRepository.cs < GetByIdAsync(int id);
Task> GetAllAsync();
Task AddAsync(User user);
Task UpdateAsync(User user);
Task DeleteAsync(int id);
}
}
EOL
# Interface: Service
cat > $PROJECT_NAME/Interfaces/IUserService.cs < GetUserAsync(int id);
Task> GetAllUsersAsync();
Task CreateUserAsync(UserDto userDto);
Task UpdateUserAsync(int id, UserDto userDto);
Task DeleteUserAsync(int id);
}
}
EOL
# Interface: TokenService
cat > $PROJECT_NAME/Interfaces/ITokenService.cs < $PROJECT_NAME/Services/UserService.cs < GetUserAsync(int id) => throw new NotImplementedException();
public Task> GetAllUsersAsync() => throw new NotImplementedException();
public Task CreateUserAsync(UserDto userDto) => throw new NotImplementedException();
public Task UpdateUserAsync(int id, UserDto userDto) => throw new NotImplementedException();
public Task DeleteUserAsync(int id) => throw new NotImplementedException();
}
}
EOL
# Repository
cat > $PROJECT_NAME/Repositories/UserRepository.cs < GetByIdAsync(int id) => throw new NotImplementedException();
public Task> GetAllAsync() => throw new NotImplementedException();
public Task AddAsync(User user) => throw new NotImplementedException();
public Task UpdateAsync(User user) => throw new NotImplementedException();
public Task DeleteAsync(int id) => throw new NotImplementedException();
}
}
EOL
# Controller - WITH GetUser endpoint!
cat > $PROJECT_NAME/Controllers/UserController.cs < GetUser(int id)
{
var user = await _userService.GetUserAsync(id);
if (user == null)
return NotFound();
return Ok(user);
}
// Other endpoints using _userService can go here
}
}
EOL
# Middleware Interface (not mandatory, but pattern example)
cat > $PROJECT_NAME/Interfaces/IMiddleware.cs < $PROJECT_NAME/Middlewares/ErrorHandlingMiddleware.cs < $PROJECT_NAME/Configuration/DependencyInjection.cs <();
services.AddScoped();
// Add more as needed
return services;
}
}
}
EOL
# Domain Entity
cat > $PROJECT_NAME/Domain/User.cs < $PROJECT_NAME/Dtos/UserDto.cs < $PROJECT_NAME/Mappers/UserMapper.cs < new UserDto { Id = user.Id, Name = user.Name };
public static User ToDomain(UserDto dto) => new User { Id = dto.Id, Name = dto.Name };
}
}
EOL
# Tests
# Add Moq to test project
dotnet add $TEST_PROJECT_NAME package Moq
# Create test folders and files
mkdir -p $TEST_PROJECT_NAME/Services $TEST_PROJECT_NAME/Controllers
# UserService unit tests
cat > $TEST_PROJECT_NAME/Services/UserServiceTests.cs < _userRepoMock;
public UserServiceTests()
{
_userRepoMock = new Mock();
_userService = new UserService(_userRepoMock.Object);
}
[Fact]
public async Task GetUserAsync_ReturnsUserDto_WhenUserExists()
{
var user = new User { Id = 1, Name = "Test User" };
_userRepoMock.Setup(r => r.GetByIdAsync(1)).ReturnsAsync(user);
var result = await _userService.GetUserAsync(1);
Assert.NotNull(result);
Assert.Equal(user.Id, result.Id);
Assert.Equal(user.Name, result.Name);
}
[Fact]
public async Task GetUserAsync_ReturnsNull_WhenUserDoesNotExist()
{
_userRepoMock.Setup(r => r.GetByIdAsync(2)).ReturnsAsync((User?)null);
var result = await _userService.GetUserAsync(2);
Assert.Null(result);
}
}
}
EOL
cat > $TEST_PROJECT_NAME/Controllers/UserControllerTests.cs < _userServiceMock;
public UserControllerTests()
{
_userServiceMock = new Mock();
_controller = new UserController(_userServiceMock.Object);
}
[Fact]
public async Task GetUser_ReturnsOk_WhenUserExists()
{
var userDto = new UserDto { Id = 1, Name = "Test User" };
_userServiceMock.Setup(s => s.GetUserAsync(1)).ReturnsAsync(userDto);
var result = await _controller.GetUser(1);
var okResult = Assert.IsType(result);
var returnedUser = Assert.IsType(okResult.Value);
Assert.Equal(userDto.Id, returnedUser.Id);
}
[Fact]
public async Task GetUser_ReturnsNotFound_WhenUserDoesNotExist()
{
_userServiceMock.Setup(s => s.GetUserAsync(99)).ReturnsAsync((UserDto?)null);
var result = await _controller.GetUser(99);
Assert.IsType(result);
}
}
}
EOL
cat > $PROJECT_NAME/Program.cs <
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "${PROJECT_NAME}", Version = "v1" });
});
var app = builder.Build();
// Use Swagger in dev
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
// Optionally: Use global error handling middleware
// app.UseMiddleware<${PROJECT_NAME}.Middlewares.ErrorHandlingMiddleware>();
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
EOL
# Restore, clean, and build solution
dotnet restore
dotnet clean
dotnet build
echo "API Solution, folders, interfaces, and sample classes created!"
echo "Open in VS Code with: code ."
This structure enables clean, maintainable, and testable APIs for any professional project.