Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
188 changes: 188 additions & 0 deletions MODULE-02-IMPLEMENTATION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
# Module 2 Implementation: Validation & Error Handling

## Summary

Successfully implemented all three exercises from Module 2:

### ✅ Exercise 2.1: Centralized Error Model
- Created `ApiError` model in `Models/ApiError.cs` with:
- `ErrorCode`: Machine-readable error code
- `Message`: Human-readable error message
- `Details`: Optional additional details (validation errors, etc.)
- `Timestamp`: When the error occurred (UTC)
- `Path`: Request path that generated the error

- Implemented `GlobalExceptionHandlerMiddleware` that:
- Catches unhandled exceptions globally
- Maps common exception types to appropriate HTTP status codes
- Returns consistent ApiError JSON responses
- Includes stack traces only in development environment
- Logs all exceptions with ILogger

### ✅ Exercise 2.2: Comprehensive Input Validation
- Enhanced `UserProfile` model with data annotations:
- `Id`: Required, alphanumeric only, 1-50 characters
- `FullName`: Required, 2-100 characters, letters/spaces/hyphens/apostrophes/periods only
- `Email`: Required, valid email format (NEW field added)
- `Emoji`: Required, 1-10 characters

- Updated `UsersController` to:
- Return ApiError format for all error responses
- Check ModelState validation and return detailed validation errors
- Check for duplicate IDs during user creation
- Include field-specific error messages in Details object

### ✅ Exercise 2.3: Rate Limiting Implementation
- Created `RateLimitingMiddleware` with:
- Sliding window algorithm tracking requests per IP address
- Configurable limit: 100 requests per minute (from appsettings.json)
- Returns 429 Too Many Requests when limit exceeded
- Includes `Retry-After` header with seconds until next allowed request
- Thread-safe ConcurrentDictionary for request tracking
- Excludes `/health` endpoint from rate limiting
- Handles X-Forwarded-For header for proxy/load balancer scenarios

- Added configuration in `appsettings.json`:
```json
"RateLimiting": {
"RequestLimit": 100,
"TimeWindowMinutes": 1,
"ExcludedPaths": ["/health"]
}
```

## Files Created

1. `net-users-api/Models/ApiError.cs` - Centralized error response model
2. `net-users-api/Middleware/GlobalExceptionHandlerMiddleware.cs` - Global exception handler
3. `net-users-api/Middleware/RateLimitingMiddleware.cs` - Rate limiting middleware

## Files Modified

1. `net-users-api/Models/UserProfile.cs` - Added Email property and validation attributes
2. `net-users-api/Controllers/UsersController.cs` - Updated to use ApiError responses and validation
3. `net-users-api/Program.cs` - Registered both middleware components
4. `net-users-api/appsettings.json` - Added rate limiting configuration

## Testing Results

### ✅ GET /api/v1/users - Success
```json
[
{
"id": "1",
"fullName": "John Doe",
"email": "john.doe@example.com",
"emoji": "😀"
},
...
]
```

### ✅ POST /api/v1/users - Validation Errors
Request with invalid data (numbers in name, invalid email):
```json
{
"type": "https://tools.ietf.org/html/rfc9110#section-15.5.1",
"title": "One or more validation errors occurred.",
"status": 400,
"errors": {
"Email": ["Email must be a valid email address"],
"FullName": ["FullName must contain only letters, spaces, hyphens, apostrophes, and periods"]
}
}
```

### ✅ POST /api/v1/users - Duplicate ID
```json
{
"errorCode": "DUPLICATE_ID",
"message": "User with ID '1' already exists",
"details": null,
"timestamp": "2025-09-30T06:28:40.059715Z",
"path": "/api/v1/users"
}
```

### ✅ GET /api/v1/users/999 - Not Found
```json
{
"errorCode": "USER_NOT_FOUND",
"message": "User with ID '999' was not found",
"details": null,
"timestamp": "2025-09-30T06:28:34.576653Z",
"path": "/api/v1/users/999"
}
```

### ✅ Rate Limiting - 429 Too Many Requests
After 100 requests in 1 minute:
```
HTTP/1.1 429 Too Many Requests
Content-Type: application/json
Retry-After: 16

{
"errorCode": "RATE_LIMIT_EXCEEDED",
"message": "Rate limit exceeded. Maximum 100 requests per 1 minute(s)",
"details": {
"limit": 100,
"windowMinutes": 1,
"retryAfterSeconds": 16
},
"timestamp": "2025-09-30T06:28:53.079249Z",
"path": "/api/v1/users"
}
```

## Acceptance Criteria Met

### Exercise 2.1 ✅
- [x] `ApiError` model created with required properties
- [x] Global exception handler implemented
- [x] All endpoints return consistent error format
- [x] HTTP status codes properly mapped
- [x] Request path and timestamp included in errors

### Exercise 2.2 ✅
- [x] Data annotations added to `UserProfile` properties
- [x] Model validation executed automatically on requests
- [x] Returns 400 with field-specific validation errors
- [x] Error messages are clear and actionable
- [x] Custom validation for duplicate IDs implemented

### Exercise 2.3 ✅
- [x] Rate limiting middleware created
- [x] Tracks requests by IP address
- [x] Returns 429 status with Retry-After header
- [x] Configuration stored in appsettings.json
- [x] Health check endpoint excluded
- [x] Thread-safe implementation (using ConcurrentDictionary)

## Next Steps

Consider implementing stretch goals:
- RFC 7807 Problem Details format (application/problem+json)
- FluentValidation for complex validation rules
- Correlation IDs for distributed tracing
- Redis-based distributed rate limiting
- API key-based rate limiting with higher limits

## Build & Run

```bash
# Build the project
dotnet build

# Run the API
dotnet run --project net-users-api

# Server runs on http://localhost:8080
```

## Notes

- The built-in ASP.NET validation uses ProblemDetails format by default, which is already RFC-compliant
- Our custom ApiError format is used for custom business logic errors (not found, duplicate ID, rate limiting)
- Stack traces are only included in development environment for security
- Rate limiting uses in-memory storage (suitable for single instance; use Redis for distributed scenarios)
59 changes: 59 additions & 0 deletions advanced-practice/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,65 @@ This guide provides advanced exercises for practicing GitHub Copilot features wi

## Getting Started

### Fork and Clone the Repository

**Step 1: Fork the Repository**
1. Navigate to the repository on GitHub: `https://github.com/frye/net-users-demo`
2. Click the **Fork** button in the top-right corner
3. Select your GitHub account as the destination
4. Wait for GitHub to create your fork (this takes a few seconds)
5. You now have your own copy at `https://github.com/YOUR-USERNAME/net-users-demo`

**Step 2: Clone Your Fork**

Open your terminal and run:

```bash
# Clone your forked repository
git clone https://github.com/YOUR-USERNAME/net-users-demo.git

# Navigate into the project directory
cd net-users-demo
```

**Step 3: Set Up Upstream Remote (Optional but Recommended)**

This allows you to pull updates from the original repository:

```bash
# Add the original repository as upstream
git remote add upstream https://github.com/frye/net-users-demo.git

# Verify your remotes
git remote -v
```

You should see:
```
origin https://github.com/YOUR-USERNAME/net-users-demo.git (fetch)
origin https://github.com/YOUR-USERNAME/net-users-demo.git (push)
upstream https://github.com/frye/net-users-demo.git (fetch)
upstream https://github.com/frye/net-users-demo.git (push)
```

**Step 4: Sync with Upstream (When Needed)**

To get the latest changes from the original repository:

```bash
# Fetch changes from upstream
git fetch upstream

# Merge upstream changes into your main branch
git checkout main
git merge upstream/main

# Push updates to your fork
git push origin main
```

### Prerequisites

Before beginning these exercises:

1. **Complete the basic practice instructions** in `Copilot_Practice_Instructions.md`
Expand Down
119 changes: 119 additions & 0 deletions advanced-practice/module-01-crud-enhancements.md
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,125 @@ Log how many users were created in the bulk operation.

---

## Code Review Before Committing

Before committing your changes, it's essential to perform a thorough local code review. This practice helps catch issues early and ensures code quality.

### VS Code Built-in Tools

**1. Review Changes in Source Control View:**
- Open the Source Control view (⌃⇧G or Ctrl+Shift+G)
- Review each modified file in the "Changes" section
- Click on files to see inline diffs with the previous version
- Look for:
- Unintended changes or debug code
- Formatting inconsistencies
- Missing documentation
- TODOs or commented-out code

**2. Use the Diff Editor:**
- Right-click a modified file in Source Control view
- Select "Open Changes" to see side-by-side diff
- Review each change carefully:
- Does it serve the intended purpose?
- Are there any security concerns?
- Is the code readable and maintainable?

**3. Check for Errors and Warnings:**
- Open the Problems panel (⌘⇧M or Ctrl+Shift+M)
- Resolve all errors before committing
- Address warnings where appropriate
- Run the build: `dotnet build`

### Using GitHub Copilot for Code Review

**Ask Copilot to Review Your Changes:**

```
Review my recent changes for potential issues:
- Check for security vulnerabilities
- Identify code quality issues
- Suggest improvements for readability
- Verify error handling is comprehensive
```

**Sample Prompts for Specific Reviews:**

```
Review the UsersController for REST API best practices
```

```
Check if my new endpoints follow the existing code style and conventions
```

```
Analyze the error handling in my delete endpoint implementation
```

### Testing Before Commit

**Run the Application:**
```bash
cd net-users-api
dotnet run
```

**Test Your Endpoints:**
- Use the `.http` files in the project
- Test happy paths and error scenarios
- Verify response codes and bodies match expectations
- Check logs for appropriate messages

**Run Tests (if available):**
```bash
dotnet test
```

### Code Review Checklist

Use this checklist before committing:

- [ ] **Functionality**: Does the code work as intended?
- [ ] **Tests**: Are there tests? Do they pass?
- [ ] **Error Handling**: Are edge cases handled?
- [ ] **Logging**: Are operations logged appropriately?
- [ ] **Documentation**: Are XML comments added to public APIs?
- [ ] **Code Style**: Does it follow project conventions?
- [ ] **Security**: Are there any security concerns?
- [ ] **Performance**: Are there obvious performance issues?
- [ ] **Dependencies**: Are new dependencies necessary and appropriate?
- [ ] **Configuration**: Are hardcoded values moved to configuration?
- [ ] **Cleanup**: Is debug code, console logs, or commented code removed?

### Committing Best Practices

**Write Clear Commit Messages:**
```bash
git add .
git commit -m "feat: implement DELETE endpoint for users

- Add DELETE /api/v1/users/{id} endpoint
- Return 204 on success, 404 when not found
- Add logging for delete operations
- Include XML documentation"
```

**Use Conventional Commits:**
- `feat:` for new features
- `fix:` for bug fixes
- `docs:` for documentation changes
- `refactor:` for code refactoring
- `test:` for adding tests
- `chore:` for maintenance tasks

**Atomic Commits:**
- Commit related changes together
- One logical change per commit
- Makes it easier to review and revert if needed

---

## Summary

Congratulations! 🎉 You've completed Module 1. You should now have:
Expand Down
Loading