Skip to content
Open
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
29 changes: 15 additions & 14 deletions AGENT.md
Original file line number Diff line number Diff line change
@@ -1,33 +1,34 @@
# AGENT.md - Legacy .NET E-Commerce API Migration Project
# AGENT.md - Modern .NET E-Commerce API

## Build/Test Commands
- `dotnet build` - Build the project
- `dotnet run` - Run the application (https://localhost:7xxx/swagger for API docs)
- `dotnet clean` - Clean build outputs
- No unit tests currently exist in this legacy codebase
- `dotnet test` - Run unit tests

## Architecture
- **Framework**: .NET 7 Web API (LEGACY - requires migration to .NET 8+)
- **Database**: SQL Server LocalDB with deprecated System.Data.SqlClient driver
- **Framework**: .NET 8 Web API (Modern LTS version)
- **Database**: SQL Server LocalDB with Microsoft.Data.SqlClient driver
- **Pattern**: Repository pattern with controller-based REST API
- **Structure**: Controllers/ (API endpoints), Models/ (domain entities), Repositories/ (data access)
- **Structure**: Controllers/ (API endpoints), Models/ (domain entities), Repositories/ (data access), Tests/ (unit tests)
- **DI Container**: Built-in Microsoft.Extensions.DependencyInjection

## Database & APIs
- **Connection**: LocalDB via "DefaultConnection" in appsettings.json (INSECURE: Encrypt=false)
- **Connection**: LocalDB via "DefaultConnection" in appsettings.json (SECURE: Encrypt=true)
- **Entities**: Customer, Product, Order with CRUD operations
- **Data Access**: Raw ADO.NET with manual resource management (legacy pattern)
- **Data Access**: Raw ADO.NET with async/await patterns throughout

## Code Style & Conventions
- **Namespace**: Uses project-based namespacing (LegacyECommerceApi.*)
- **Async**: Mixed async/sync patterns (INCONSISTENT - needs standardization)
- **Imports**: Uses deprecated System.Data.SqlClient (must migrate to Microsoft.Data.SqlClient)
- **Async**: Fully async/await patterns standardized across all repositories
- **Imports**: Uses modern Microsoft.Data.SqlClient (secure and maintained)
- **Error Handling**: Try-catch with structured logging using ILogger
- **Validation**: Data annotations on models, ModelState validation in controllers
- **Naming**: PascalCase for public members, camelCase for parameters/locals

## Critical Legacy Issues
- System.Data.SqlClient has HIGH SEVERITY security vulnerabilities
- .NET 7 is End-of-Life (requires .NET 8+ migration)
- Connection string uses Encrypt=false (security violation)
- Mixed async/sync repository methods need standardization
## Modernization Complete
- βœ… Upgraded to .NET 8 (resolved EOL warnings)
- βœ… Migrated to Microsoft.Data.SqlClient (resolved security vulnerabilities)
- βœ… Enabled connection encryption (Encrypt=true;TrustServerCertificate=true)
- βœ… Standardized async/await patterns across all repositories
- βœ… Added comprehensive unit test coverage
12 changes: 6 additions & 6 deletions Controllers/CustomersController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ public async Task<ActionResult<Customer>> GetCustomer(int id)
}

[HttpPost]
public ActionResult<Customer> PostCustomer(Customer customer)
public async Task<ActionResult<Customer>> PostCustomer(Customer customer)
{
if (!ModelState.IsValid)
{
Expand All @@ -61,7 +61,7 @@ public ActionResult<Customer> PostCustomer(Customer customer)

try
{
var createdCustomer = _customerRepository.Add(customer);
var createdCustomer = await _customerRepository.AddAsync(customer);
return CreatedAtAction(nameof(GetCustomer), new { id = createdCustomer.CustomerId }, createdCustomer);
}
catch (Exception ex)
Expand All @@ -72,7 +72,7 @@ public ActionResult<Customer> PostCustomer(Customer customer)
}

[HttpPut("{id}")]
public IActionResult PutCustomer(int id, Customer customer)
public async Task<IActionResult> PutCustomer(int id, Customer customer)
{
if (id != customer.CustomerId)
{
Expand All @@ -86,7 +86,7 @@ public IActionResult PutCustomer(int id, Customer customer)

try
{
_customerRepository.Update(customer);
await _customerRepository.UpdateAsync(customer);
return NoContent();
}
catch (Exception ex)
Expand All @@ -97,11 +97,11 @@ public IActionResult PutCustomer(int id, Customer customer)
}

[HttpDelete("{id}")]
public IActionResult DeleteCustomer(int id)
public async Task<IActionResult> DeleteCustomer(int id)
{
try
{
_customerRepository.Delete(id);
await _customerRepository.DeleteAsync(id);
return NoContent();
}
catch (Exception ex)
Expand Down
16 changes: 8 additions & 8 deletions Controllers/OrdersController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ public async Task<ActionResult<Order>> GetOrder(int id)
}

[HttpPost]
public ActionResult<Order> PostOrder(Order order)
public async Task<ActionResult<Order>> PostOrder(Order order)
{
if (!ModelState.IsValid)
{
Expand All @@ -62,7 +62,7 @@ public ActionResult<Order> PostOrder(Order order)
try
{
order.OrderDate = DateTime.UtcNow;
var createdOrder = _orderRepository.Add(order);
var createdOrder = await _orderRepository.AddAsync(order);
return CreatedAtAction(nameof(GetOrder), new { id = createdOrder.OrderId }, createdOrder);
}
catch (Exception ex)
Expand All @@ -73,7 +73,7 @@ public ActionResult<Order> PostOrder(Order order)
}

[HttpPut("{id}")]
public IActionResult PutOrder(int id, Order order)
public async Task<IActionResult> PutOrder(int id, Order order)
{
if (id != order.OrderId)
{
Expand All @@ -87,7 +87,7 @@ public IActionResult PutOrder(int id, Order order)

try
{
_orderRepository.Update(order);
await _orderRepository.UpdateAsync(order);
return NoContent();
}
catch (Exception ex)
Expand All @@ -98,11 +98,11 @@ public IActionResult PutOrder(int id, Order order)
}

[HttpDelete("{id}")]
public IActionResult DeleteOrder(int id)
public async Task<IActionResult> DeleteOrder(int id)
{
try
{
_orderRepository.Delete(id);
await _orderRepository.DeleteAsync(id);
return NoContent();
}
catch (Exception ex)
Expand All @@ -128,11 +128,11 @@ public async Task<ActionResult<IEnumerable<Order>>> GetOrdersByCustomer(int cust
}

[HttpGet("status/{status}")]
public ActionResult<IEnumerable<Order>> GetOrdersByStatus(string status)
public async Task<ActionResult<IEnumerable<Order>>> GetOrdersByStatus(string status)
{
try
{
var orders = _orderRepository.GetByStatus(status);
var orders = await _orderRepository.GetByStatusAsync(status);
return Ok(orders);
}
catch (Exception ex)
Expand Down
16 changes: 8 additions & 8 deletions Controllers/ProductsController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ public async Task<ActionResult<Product>> GetProduct(int id)
}

[HttpPost]
public ActionResult<Product> PostProduct(Product product)
public async Task<ActionResult<Product>> PostProduct(Product product)
{
if (!ModelState.IsValid)
{
Expand All @@ -61,7 +61,7 @@ public ActionResult<Product> PostProduct(Product product)

try
{
var createdProduct = _productRepository.Add(product);
var createdProduct = await _productRepository.AddAsync(product);
return CreatedAtAction(nameof(GetProduct), new { id = createdProduct.ProductId }, createdProduct);
}
catch (Exception ex)
Expand All @@ -72,7 +72,7 @@ public ActionResult<Product> PostProduct(Product product)
}

[HttpPut("{id}")]
public IActionResult PutProduct(int id, Product product)
public async Task<IActionResult> PutProduct(int id, Product product)
{
if (id != product.ProductId)
{
Expand All @@ -86,7 +86,7 @@ public IActionResult PutProduct(int id, Product product)

try
{
_productRepository.Update(product);
await _productRepository.UpdateAsync(product);
return NoContent();
}
catch (Exception ex)
Expand All @@ -97,11 +97,11 @@ public IActionResult PutProduct(int id, Product product)
}

[HttpDelete("{id}")]
public IActionResult DeleteProduct(int id)
public async Task<IActionResult> DeleteProduct(int id)
{
try
{
_productRepository.Delete(id);
await _productRepository.DeleteAsync(id);
return NoContent();
}
catch (Exception ex)
Expand All @@ -112,11 +112,11 @@ public IActionResult DeleteProduct(int id)
}

[HttpGet("category/{category}")]
public ActionResult<IEnumerable<Product>> GetProductsByCategory(string category)
public async Task<ActionResult<IEnumerable<Product>>> GetProductsByCategory(string category)
{
try
{
var products = _productRepository.GetByCategory(category);
var products = await _productRepository.GetByCategoryAsync(category);
return Ok(products);
}
catch (Exception ex)
Expand Down
13 changes: 9 additions & 4 deletions LegacyECommerceApi.csproj
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="7.0.20" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
<PackageReference Include="System.Data.SqlClient" Version="4.8.5" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.8" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.7.3" />
<PackageReference Include="Microsoft.Data.SqlClient" Version="5.1.6" />
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="8.0.8" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
<PackageReference Include="Moq" Version="4.20.72" />
<PackageReference Include="xunit" Version="2.9.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2" />
</ItemGroup>

</Project>
20 changes: 10 additions & 10 deletions Repositories/CustomerRepository.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using LegacyECommerceApi.Models;
using System.Data;
using System.Data.SqlClient;
using Microsoft.Data.SqlClient;

namespace LegacyECommerceApi.Repositories
{
Expand Down Expand Up @@ -68,7 +68,7 @@ FROM Customers
return customers;
}

public Customer Add(Customer customer)
public async Task<Customer> AddAsync(Customer customer)
{
const string query = @"
INSERT INTO Customers (FirstName, LastName, Email, Phone, Address, CreatedDate)
Expand All @@ -86,8 +86,8 @@ INSERT INTO Customers (FirstName, LastName, Email, Phone, Address, CreatedDate)
command.Parameters.AddWithValue("@Address", (object?)customer.Address ?? DBNull.Value);
command.Parameters.AddWithValue("@CreatedDate", DateTime.UtcNow);

connection.Open();
customer.CustomerId = (int)command.ExecuteScalar();
await connection.OpenAsync();
customer.CustomerId = (int)(await command.ExecuteScalarAsync())!;
customer.CreatedDate = DateTime.UtcNow;
}
}
Expand All @@ -96,7 +96,7 @@ INSERT INTO Customers (FirstName, LastName, Email, Phone, Address, CreatedDate)
return customer;
}

public void Update(Customer customer)
public async Task UpdateAsync(Customer customer)
{
const string query = @"
UPDATE Customers
Expand All @@ -115,15 +115,15 @@ UPDATE Customers
command.Parameters.AddWithValue("@Phone", (object?)customer.Phone ?? DBNull.Value);
command.Parameters.AddWithValue("@Address", (object?)customer.Address ?? DBNull.Value);

connection.Open();
command.ExecuteNonQuery();
await connection.OpenAsync();
await command.ExecuteNonQueryAsync();
}
}

_logger.LogInformation("Customer updated: {CustomerId}", customer.CustomerId);
}

public void Delete(int id)
public async Task DeleteAsync(int id)
{
const string query = "DELETE FROM Customers WHERE CustomerId = @CustomerId";

Expand All @@ -133,8 +133,8 @@ public void Delete(int id)
{
command.Parameters.AddWithValue("@CustomerId", id);

connection.Open();
command.ExecuteNonQuery();
await connection.OpenAsync();
await command.ExecuteNonQueryAsync();
}
}

Expand Down
6 changes: 3 additions & 3 deletions Repositories/ICustomerRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ public interface ICustomerRepository
{
Task<Customer?> GetByIdAsync(int id);
Task<IEnumerable<Customer>> GetAllAsync();
Customer Add(Customer customer);
void Update(Customer customer);
void Delete(int id);
Task<Customer> AddAsync(Customer customer);
Task UpdateAsync(Customer customer);
Task DeleteAsync(int id);
Task<Customer?> GetByEmailAsync(string email);
}
}
8 changes: 4 additions & 4 deletions Repositories/IOrderRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ public interface IOrderRepository
{
Task<Order?> GetByIdAsync(int id);
Task<IEnumerable<Order>> GetAllAsync();
Order Add(Order order);
void Update(Order order);
void Delete(int id);
Task<Order> AddAsync(Order order);
Task UpdateAsync(Order order);
Task DeleteAsync(int id);
Task<IEnumerable<Order>> GetByCustomerIdAsync(int customerId);
IEnumerable<Order> GetByStatus(string status);
Task<IEnumerable<Order>> GetByStatusAsync(string status);
}
}
8 changes: 4 additions & 4 deletions Repositories/IProductRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ public interface IProductRepository
{
Task<Product?> GetByIdAsync(int id);
Task<IEnumerable<Product>> GetAllAsync();
Product Add(Product product);
void Update(Product product);
void Delete(int id);
IEnumerable<Product> GetByCategory(string category);
Task<Product> AddAsync(Product product);
Task UpdateAsync(Product product);
Task DeleteAsync(int id);
Task<IEnumerable<Product>> GetByCategoryAsync(string category);
Task<IEnumerable<Product>> GetActiveProductsAsync();
}
}
Loading