Skip to content

Submitted for review#92

Open
RyanW84 wants to merge 3 commits intothe-csharp-academy:mainfrom
RyanW84:main
Open

Submitted for review#92
RyanW84 wants to merge 3 commits intothe-csharp-academy:mainfrom
RyanW84:main

Conversation

@RyanW84
Copy link
Copy Markdown

@RyanW84 RyanW84 commented Jan 11, 2026

Codacy errors ignored, they break the app if changed

Copy link
Copy Markdown

@chrisjamiecarter chrisjamiecarter left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @RyanW84 👋,

Excellent work on your ECommerce API project submission 🎉!

I have performed a peer review. Review/ignore any comments as you wish.

Overview

This is an impressive, production-ready API implementation that demonstrates sophisticated understanding of ASP.NET Core patterns. The project showcases:

  • Clean Architecture: Well-separated concerns with Controllers → Services → Repositories → Data layer
  • Soft Delete Pattern: Elegant implementation using global query filters and BaseEntity
  • Performance Optimizations: Response caching, output caching, response compression, compiled queries
  • Documentation Excellence: Comprehensive README, XML docs, Swagger/Scalar integration
  • Testing Strategy: Unit tests with proper Arrange-Act-Assert patterns (61 passing tests!)

Suggestions for Improvement

  1. Project Structure 🟠 - Code in Root Instead of Username Folder
    The project files are in the repository root instead of a proper project folder containing the username. This violates the requirement that all student code must be inside a single top-level folder named [ProjectName].[Username]. I think at this point in your journey you should be following this basic step during project submission.
📁 CodeReviews.Console.EcommerceApi ⬅️ Forked repo
 ┣ 📄 .codacy.yml
 ┣ 📄 .gitignore
 ┣ 📁 EcommerceApi.RyanW84 ⬅️ Top-level folder *Missing!
 ┃  ┗ 📄 EcommerceApi.sln
 ┃  ┗ 📄 README.md
 ┃  ┗ 📁 EcommerceApi.Api
 ┃  ┗ 📁 EcommerceApi.Ui

Also, as you have included tests, a common standard is to include a src and a tests folder. So that would slightly change your structure:

📁 CodeReviews.Console.EcommerceApi ⬅️ Forked repo
 ┣ 📄 .codacy.yml
 ┣ 📄 .gitignore
 ┣ 📁 EcommerceApi.RyanW84 ⬅️ Top-level folder *Missing!
 ┃  ┣ 📄 EcommerceApi.sln
 ┃  ┣ 📄 README.md
 ┃  ┣ 📁 src ⬅️ src-level folder *New!
 ┃  ┃  ┗ 📁 EcommerceApi.Api
 ┃  ┃  ┗ 📁 EcommerceApi.Ui
 ┃  ┣ 📁 tests
 ┃  ┃  ┗ 📁 EcommerceApi.Api.Tests
 ┃  ┃  ┗ 📁 EcommerceApi.Ui.Tests
  1. Integration Tests Broken 🟠 - The WebApplicationFactory<Program> cannot access the internal Program class. Add public partial class Program { } to make the entry point public for testing.

  2. Namespace Inconsistency 🟠 - Controllers use file-scoped namespaces (namespace X;) while Services and Repositories use traditional block namespaces (namespace X { }). Choose one style and apply consistently.

  3. XML Doc Placement 🟠 - In CategoriesController and SalesController, the class documentation appears after attributes instead of before them.

  4. Required Properties Pattern 🟡 - Using required string Name { get; set; } = null!; suppresses null warnings without providing safety. Remove the = null! initializers.

  5. Generic Exception Handling 🟡 - Services catch Exception broadly. Consider whether the global exception middleware could handle some of these cases, reducing duplicated try/catch blocks.


I will go ahead and mark as approved, keep up the excellent work on the next projects! 😊

Thanks,
@chrisjamiecarter 👍

Comment on lines +8 to +16
[ApiController]
[Route("api/[controller]")]
[Route("api/v1/categories")]
/// <summary>
/// Category CRUD API endpoints.
/// Provides create, read (by id/name), update, delete, and soft-delete restore operations for <see cref="Category"/>.
/// Responses are wrapped in the project's standard API response DTOs and follow the same error mapping conventions.
/// </summary>
public class CategoriesController : ControllerBase
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 XML Documentation Placement

💡 The class-level XML documentation comment is placed after the [ApiController] and [Route] attributes. Per standard conventions, documentation comments should precede all attributes:

Suggested change
[ApiController]
[Route("api/[controller]")]
[Route("api/v1/categories")]
/// <summary>
/// Category CRUD API endpoints.
/// Provides create, read (by id/name), update, delete, and soft-delete restore operations for <see cref="Category"/>.
/// Responses are wrapped in the project's standard API response DTOs and follow the same error mapping conventions.
/// </summary>
public class CategoriesController : ControllerBase
/// <summary>
/// Category CRUD API endpoints.
/// Provides create, read (by id/name), update, delete, and soft-delete restore operations for <see cref="Category"/>.
/// Responses are wrapped in the project's standard API response DTOs and follow the same error mapping conventions.
/// </summary>
[ApiController]
[Route("api/[controller]")]
[Route("api/v1/categories")]
public class CategoriesController : ControllerBase

Comment on lines +8 to +15
[ApiController]
[Route("api/[controller]")]
[Route("api/v1/sales")]
/// <summary>
/// Sales API endpoints.
/// Supports creating and managing sales, including read operations that can optionally include historical (soft-deleted) products.
/// </summary>
public class SalesController : ControllerBase
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 XML Documentation Placement

💡 The class-level XML documentation comment is placed after the [ApiController] and [Route] attributes. Per standard conventions, documentation comments should precede all attributes:

Suggested change
[ApiController]
[Route("api/[controller]")]
[Route("api/v1/sales")]
/// <summary>
/// Sales API endpoints.
/// Supports creating and managing sales, including read operations that can optionally include historical (soft-deleted) products.
/// </summary>
public class SalesController : ControllerBase
/// <summary>
/// Sales API endpoints.
/// Supports creating and managing sales, including read operations that can optionally include historical (soft-deleted) products.
/// </summary>
[ApiController]
[Route("api/[controller]")]
[Route("api/v1/sales")]
public class SalesController : ControllerBase

Comment on lines +91 to +96
// GET /api/v1/sales/history
// Noun-based alternative to "with-deleted-products".
[HttpGet("history")]
[ResponseCache(Duration = 30, Location = ResponseCacheLocation.Any)]
public Task<IActionResult> GetHistory(CancellationToken cancellationToken) =>
GetAllWithDeletedProducts(cancellationToken);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Noun-Based Route Alternative Comment

💡 The comment // Noun-based alternative to "with-deleted-products". is helpful, but consider if maintaining both endpoints is necessary. Having both /with-deleted-products and /history could confuse API consumers. Consider deprecating one or documenting which is preferred.

/// <summary>
/// Display name shown in lists and detail views.
/// </summary>
public required string Name { get; set; } = null!;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Required Properties with null! Initialization

💡 Using required string Name { get; set; } = null!; defeats the purpose of the required modifier. The null! suppresses null warnings but doesn't provide compile-time safety. Consider removing the = null! initializer to ensure consumers must set the value.

Comment on lines +7 to +8
namespace ECommerceApp.RyanW84.Services
{
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 Block Namespace Instead of File-Scoped

💡 This file uses traditional block namespaces while Controllers use file-scoped namespaces. For consistency across the codebase, prefer file-scoped namespaces.

Suggested change
namespace ECommerceApp.RyanW84.Services
{
namespace ECommerceApp.RyanW84.Services;

Comment on lines +13 to +17
[ApiController]
[Route("api/[controller]")]
[Route("api/products")]
[Route("api/v1/products")]
public class ProductController(IProductService productService) : ControllerBase
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Redundant Route Attributes

❓ Why do you have duplication in your routes? Why not just keep only the versioned route: [Route("api/v1/[controller]")]

"price" => descending
? query.OrderByDescending(p => p.Price)
: query.OrderBy(p => p.Price),
"createdat" => descending
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❓ Why createdat and not createdate? Also in CategoryRepository.

public class SmokeTests
{
[Fact]
public void True_is_true()
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❓ What benefit does this bring?

namespace ECommerceApp.UnitTests.ConsoleClient;

[Collection(SpectreConsoleCollection.Name)]
public class TableRendererDynamicPagingTests
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❓ What is the purpose of these tests? It seems you are testing Spectre.Console. If so, I would leave that to the owners of that library and not worry about it.

private readonly CategoryValidator _validator = new();

[Fact]
public void Validate_WithValidCategory_ShouldSucceed()
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Good Test Structure

💡 Tests follow Arrange-Act-Assert pattern with clear naming. Excellent test structure!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants