submission#79
Conversation
chrisjamiecarter
left a comment
There was a problem hiding this comment.
Hey @0lcm 👋,
Excellent work on your Document Processor project submission 🎉!
I have performed a peer review. Review/ignore any comments as you wish.
This is an impressively comprehensive project that demonstrates strong command of ASP.NET Core, Entity Framework Core, and modern C# patterns. The layered architecture (Controllers, Services, Repositories, EF Core) is clean and well-organized. The inclusion of a separate console UI client, import/export support for multiple file formats (CSV, XLSX, XLS, PDF), soft delete via an interceptor, pagination, filtering, and search functionality shows a deep understanding of real-world API development. The README is thorough and well-documented. The use of primary constructors, file-scoped namespaces, collection expressions, and required properties shows you are keeping up with modern C# practices.
Project-Wide Patterns:
- Exception handling consistency: Multiple service classes use catch (Exception) { return false; } without logging. Consider injecting ILogger for better debugging.
- Disposable resource management: Several IDisposable objects (FileStream, HSSFWorkbook) are used without proper disposal via using.
- CS8618 warnings: Non-nullable reference type properties across several model/DTO classes need initialization.
- Query string encoding: URL parameters are not encoded, which could break with special characters.
What You Did Well:
⭐ Excellent Architecture: The clean separation into API/Shared/UI projects with distinct layers makes the code testable, maintainable, and follows SOLID principles well.
⭐ EF Core Mastery: Soft delete via SaveChangesInterceptor, many-to-many relationships, query filters, and enum-to-string conversions are all implemented correctly.
⭐ Modern C# Features: Great use of primary constructors, file-scoped namespaces, required properties, collection expressions, and IHttpClientFactory for HTTP client management.
⭐ Factory Pattern for Import/Export: The factory pattern elegantly handles different file formats without coupling to specific implementations.
🔴 Project Submission
💡 Please place all your code inside a single top-level folder named: [ProjectName].[UserName].
👨🏫 Example: DocumentProcessor.0lcm
(Even though you are submitting re-used code from the Ecommerce-API project, it should be clear to the reviewer what project is yours).
📁 CodeReviews.Console.DocumentProcessor. ⬅️ Forked repo
┣ 📄 .codacy.yml
┣ 📄 .gitignore
┣ 📁 DocumentProcessor.0lcm ⬅️ Top-level folder
┃ ┗ 📄 Project.sln
┃ ┗ 📄 README.md
┃ ┗ 📁 ECommerce.API
💭 This helps me clearly identify your work and confirms you can work within community repository guidelines.
🔧 Next Steps
- Please fix any 🔴 items, as they block approval.
- Commit and push your changes, then leave me a comment when done.
- Feel free to reach out if you have any questions or want to discuss further 🆘.
Thanks,
@chrisjamiecarter 👍
| } | ||
|
|
||
| [HttpGet] | ||
| public async Task<ActionResult<PagedResponse<Item>>> GetItemsAsync([FromQuery] PaginationParams paginationParams) |
There was a problem hiding this comment.
🟠 Type Mismatch in Action Return Type
💡 The method signature declares ActionResult<PagedResponse<Item>> but the actual response comes from the service which returns PagedResponse<ItemDto>. While this compiles, it will cause incorrect OpenAPI/Swagger documentation.
| } | ||
|
|
||
| [HttpDelete] | ||
| public async Task<IActionResult> DeleteSaleAsync([FromQuery] int saleId) |
There was a problem hiding this comment.
🟡 Unconventional DELETE Route
💡 The delete action uses [HttpDelete] with no route template and accepts [FromQuery] int saleId. The standard REST convention is to use a route parameter: [HttpDelete("{saleId:int}")] and int saleId without [FromQuery]. This is more idiomatic for ASP.NET Core APIs.
| app.UseSwaggerUI(); | ||
| } | ||
|
|
||
| using (var scope = app.Services.CreateScope()) |
There was a problem hiding this comment.
🟡 Using Statement vs Using Declaration
💡 You are using the using (var scope = ...) statement pattern. The C# Academy guidelines prefer the using declaration pattern:
csharp using var scope = app.Services.CreateScope();
| app.UseHttpsRedirection(); | ||
| app.UseAuthorization(); | ||
| app.MapControllers(); | ||
| app.UseExceptionHandler(error => error.Run(async context => |
There was a problem hiding this comment.
🟡 Error Handler Placement
💡 The UseExceptionHandler middleware is registered after MapControllers(). For exception handling middleware to catch controller exceptions, it should be registered earlier in the pipeline (right after the environment check).
|
|
||
| namespace ECommerce.UI.Helpers; | ||
|
|
||
| internal class UiHelper(ITagService tagService) |
There was a problem hiding this comment.
🟠 Unused Constructor Parameter
💡 The tagService parameter in the UiHelper constructor is injected but never used (build warning CS9113). Either remove the parameter and its DI registration, or use the service within the class.
| DisplayWarning("An error has occurred with one or more of the arguments you have entered, " + | ||
| "please check that any details you enter are correct before trying again."); | ||
| } | ||
| else if (ex is NotSupportedException notSupportedException) |
There was a problem hiding this comment.
🟠 Empty Catch Block
💡 The NotSupportedException is caught with an empty handler body. This silently swallows the exception. Either log it, add a user-facing message, or remove the catch clause.
|
|
||
| namespace ECommerce.API.Services.Import_Export; | ||
|
|
||
| public class PdfService(IConfiguration configuration) : IExportService |
There was a problem hiding this comment.
🟠 Unused Constructor Parameter
| await repo.DeleteItemAsync(item); | ||
| return true; | ||
| } | ||
| catch (Exception) |
There was a problem hiding this comment.
🟠 Swallowing Exceptions Without Logging
| public SeedData GetSeedData(IEnumerable<string> filePaths) | ||
| { | ||
| var filePath = filePaths.First(); | ||
| var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read); |
There was a problem hiding this comment.
🔴 Potential Resource Leak
💡 The FileStream and HSSFWorkbook objects implement IDisposable but are never disposed. Wrap them in using statements:
csharp using var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read); using var workbook = new HSSFWorkbook(stream);
| sb.Append($"{baseUrl}?PageNumber={pageNumber}&PageSize={pageSize}"); | ||
|
|
||
| if (!string.IsNullOrWhiteSpace(searchTerm)) | ||
| sb.Append($"&SearchTerm={searchTerm}"); |
There was a problem hiding this comment.
🟡 SearchTerm in Query String Not URL-Encoded
💡 When searchTerm contains special characters like spaces or ampersands, the query string will break. Use URL-encoding:
csharp sb.Append($"&SearchTerm={Uri.EscapeDataString(searchTerm)}");
(This also applies to Genre on line 19 and SearchTags on line 24)
|
Hi @chrisjamiecarter, I just pushed the requested changes so tell me if there are any other problems, and thank you for reviewing my project! |
There was a problem hiding this comment.
Hey @0lcm 👋,
Great uptdates to work on your Document Processor project submission 🎉!
🟢 Requirements
⭐ You have fulfilled all of the project requirements!
I will go ahead and mark as approved, keep up the excellent work on the next projects! 😊
Best regards,
@chrisjamiecarter 👍
| namespace ECommerce.API.Services.Import_Export; | ||
|
|
||
| public class PdfService(IConfiguration configuration) : IExportService | ||
| public class PdfService() : IExportService |
There was a problem hiding this comment.
Rather than leave an empty constructor, change to:
public class PdfService : IExportService
| namespace ECommerce.UI.Helpers; | ||
|
|
||
| internal class UiHelper(ITagService tagService) | ||
| internal class UiHelper() |
chrisjamiecarter
left a comment
There was a problem hiding this comment.
Hey @0lcm 👋,
Great updates to your Document Processor project submission 🎉!
🟢 Requirements
⭐ You have fulfilled all of the project requirements!
I will go ahead and mark as approved, keep up the excellent work on the next projects! 😊
Best regards,
@chrisjamiecarter 👍
No description provided.