AuctionEngine is an ASP.NET Core backend for running timed auctions.
It supports:
- User registration and login with ASP.NET Identity and JWT bearer tokens.
- Creating auction items.
- Listing active auctions.
- Placing bids on auctions.
- Bid validation for closed auctions, expired auctions, own-auction bids, non-positive amounts, and bids below the current highest bid.
- EF Core optimistic concurrency on the auction's
CurrentHighestBidvalue. - Redis caching for the current highest bid.
- SignalR notifications for new bids and closed auctions.
- A background service that closes expired auctions and creates invoices for winning bids.
- Unit tests for bidding and validation logic.
AuctionEngine.API: Minimal API endpoints, authentication setup, SignalR hub, hosted auction closer, and static test page.AuctionEngine.Core: Entities, service interfaces, bid validation, and bid placement service.AuctionEngine.Infrastructure: EF CoreDbContext, PostgreSQL repository, Redis highest-bid cache, and migrations.AuctionEngine.Tests: xUnit tests.
- .NET SDK that supports
net10.0. - PostgreSQL.
- Redis.
The default local settings expect:
- PostgreSQL at
localhost:5432. - Database name:
AuctionEngine. - PostgreSQL user:
postgres. - PostgreSQL password:
postgres. - Redis at
localhost:6379.
The database connection is in AuctionEngine.API/appsettings.json:
"ConnectionStrings": {
"DefaultConnection": "Server=localhost;Port=5432;Database=AuctionEngine;User Id=postgres;Password=postgres;"
}Development settings in AuctionEngine.API/appsettings.Development.json include:
"Jwt": {
"Key": "development-signing-key",
"Issuer": "AuctionEngine",
"Audience": "AuctionEngine"
},
"ConnectionStrings": {
"Redis": "localhost:6379"
}Use a different JWT key outside local development.
Restore dependencies:
dotnet restoreApply the EF Core migrations:
dotnet ef database update \
--project AuctionEngine.Infrastructure \
--startup-project AuctionEngine.APIIf dotnet ef is not installed:
dotnet tool install --global dotnet-efStart the API:
dotnet run --project AuctionEngine.APIThe HTTP launch profile uses:
http://localhost:5187
Open the SignalR test page in a browser:
http://localhost:5187/index.html
curl -X POST http://localhost:5187/register \
-H "Content-Type: application/json" \
-d '{"email":"seller@test.com","password":"Password123!"}'curl -X POST http://localhost:5187/login \
-H "Content-Type: application/json" \
-d '{"email":"seller@test.com","password":"Password123!"}'The response contains a JWT:
{
"token": "..."
}Use the token in authenticated requests:
export TOKEN="paste-token-here"curl -X POST http://localhost:5187/auctions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{
"title": "Keyboard",
"description": "Mechanical keyboard",
"startingPrice": 50,
"endTime": "2026-06-07T12:00:00Z"
}'The response includes the auction id.
curl http://localhost:5187/auctionscurl http://localhost:5187/auctions/{auctionId}Log in as a user who did not create the auction, then place a bid:
curl -X POST http://localhost:5187/auctions/{auctionId}/bids \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{"amount":75}'Bid responses can include:
201 Created: bid accepted.400 Bad Request: bid failed validation.401 Unauthorized: missing or invalid JWT.404 Not Found: auction does not exist.409 Conflict: another bid changed the auction at the same time.
The SignalR hub is available at:
/hubs/auction
Clients can call:
JoinAuction(Guid auctionId)
The server sends:
NewBidwhen a bid is accepted.AuctionClosedwhen the background service closes an expired auction.
The page at wwwroot/index.html connects to the hub, joins an auction group, and displays bid and close events.
AuctionCLoserService runs inside the API process.
Every 30 seconds it:
- Finds up to 50 auctions where
EndTime <= DateTime.UtcNowandIsClosed == false. - Finds the highest bid for each expired auction.
- Creates an invoice when there is a winning bidder.
- Marks the auction as closed.
- Sends an
AuctionClosedSignalR event to clients in that auction group.
Run the test suite:
dotnet testThe tests cover bid validation and bid placement behavior.
- The API uses PostgreSQL through EF Core and Npgsql.
- Redis stores the highest bid value with a two-hour absolute expiration.
CurrentHighestBidis configured as an EF Core concurrency token.- The repository includes
test_concurrency.sh, but it contains a hard-coded token and auction id. Update those values before using it locally.