ASP.NET Core MVC (.NET 10) e-commerce platform β N-tier architecture, dual-ORM data access (EF Core + Dapper), Identity + Google OAuth + JWT, RBAC, persistent cart, PDF/Excel/QR generation, AI-powered review moderation, and a production deployment behind Cloudflare.
π Live demo: nej.software π Program archive (all 11 projects): softITO-backend-2026
A note on this README: Our final presentation day was replaced at the last minute with a final exam + class boat trip, so there was no in-person walkthrough β no slide deck, no live demo, no Cloudflare/architecture talk-through in front of the class. Everything that would have been said out loud is written down here instead: the planning, the architectural decisions (and why, not just what), the folder structure, and screenshot evidence for every major feature, organized by area and collapsed by default so this stays readable.
Core Framework
Architecture
Entities β Data β Business β Api / Web, one-directional, compiler-enforcedIRepository<T>, IUnitOfWork)Data Access
Microsoft.EntityFrameworkCore.SqlServer, EF Core Tools/Design (migrations)Restrict on convergent paths, Cascade on single paths)Authentication & Authorization
Customer, AdminEkomart.Api projectIsActive flag), login-blocked across all three auth surfaces (Identity, Google, JWT)Caching
IMemoryCache, selectively applied to read-heavy catalog data with explicit write-path invalidationLogging
IHostedServiceX-Forwarded-For/X-Forwarded-Proto)Documents / Exports
AI / Content Moderation
Helsinki-NLP/opus-mt-tr-en) β toxicity classification (unitary/toxic-bert)IHttpClientFactory-based HTTP client with categorized failure logging (auth, rate-limit, cold-model, network, timeout)Mapping / Validation
API Documentation
Microsoft.AspNetCore.OpenApi (Swashbuckle explicitly not used β incompatible with .NET 10's newer minimal-hosting OpenAPI pipeline)Frontend / UX
Commerce
DevOps / Deployment
Ekomart.Entities β Ekomart.Data β Ekomart.Business β Ekomart.Api / Ekomart (Web)
(POCOs, (EF Core + (services, (thin API (MVC
zero deps) Dapper, caching, controllers, presentation,
Repo/UoW) validation) JWT, Scalar) Areas, Identity)
One-directional, compiler-enforced dependencies. Entities has zero framework dependencies. Only Data touches EF Core/Dapper directly β Business never sees AppDbContext, only IRepository<T>/IUnitOfWork. Both Api and Web depend on Business; Web additionally references Data directly since it owns DI registration (Program.cs) for the EF DbContext and Identity stores β a hosting concern, not a layering violation.
Data pipeline in short: reads flow through IMemoryCache β Dapper read repositories β flat DTOs (no change tracking). Writes flow through the EF Core repository path, wrapped in a Unit of Work transaction, with cache invalidation on success and a separate Serilog business-event log entry.
EKOMART.sln
β
βββ π¦ EKOMART.Entities/ # POCOs only β zero project dependencies
β βββ Concrete/
β β βββ Enums/
β βββ Identity/ # ApplicationUser, ApplicationRole
β
βββ ποΈ EKOMART.Data/ # EF configs + Dapper repos, Repo/UoW impl
β βββ Abstract/ # IRepository<T>, IUnitOfWork interfaces
β βββ Concrete/
β β βββ EfCore/
β β β βββ Configurations/ # EF entity type configs
β β βββ Dapper/ # connection factory, SQL read repos
β βββ Identity/
β βββ Migrations/
β βββ Seed/
β βββ UnitOfWork/
β
βββ βοΈ EKOMART.Business/ # service layer, caching, validation
β βββ Abstract/
β βββ Concrete/
β βββ Caching/
β βββ Dtos/ # β added mid-project, one folder per entity
β β βββ Address/ Brand/ Cart/ Category/ Dashboard/
β β βββ Identity/ Order/ OrderItem/ Payment/
β β βββ Product/ Review/ Transaction/ Vendor/
β βββ Mapping/ # AutoMapper profile
β βββ Validation/ # FluentValidation rules
β
βββ π EKOMART.Api/ # thin API controllers, JWT-secured
β βββ Controllers/
β βββ Middleware/
β βββ Models/
β βββ OpenApi/ # Scalar/OpenAPI config
β βββ Properties/
β βββ Services/
β
βββ π₯οΈ EKOMART/ # MVC presentation layer (Web)
βββ Areas/
β βββ Admin/
β β βββ Controllers/
β β βββ Models/
β β βββ Views/
β β βββ Brands/ Categories/ Dashboard/ Orders/
β β βββ Products/ Reviews/ Transactions/ Users/ Vendors/
β βββ Customer/ # β οΈ empty β kept for symmetry, unused
β βββ Controllers/
β βββ Models/
β βββ Views/
β
βββ Controllers/ # storefront (public, no area)
βββ Views/
β βββ About/ Account/ Cart/ Checkout/
β βββ Favorites/ Home/ Orders/ Shop/
β βββ Shared/ # _StoreLayout, _AdminLayout
β
βββ Models/ # view models, not domain entities
βββ Services/ # PDF/Excel/QR generation
βββ Logs/ # Serilog file-sink fallback
βββ Properties/
βββ wwwroot/
βββ assets/ # storefront CSS/JS/images/fonts
βββ assets-admin/ # admin dashboard CSS/JS/images
βββ css/ js/
βββ lib/ # SweetAlert2, Bootstrap, jQuery
Customer: anonymous storefront browsing (Dapper reads, [AllowAnonymous]) β register/login (Identity or "Sign in with Google", always assigned the Customer role) β My Account (Dashboard, Orders, Track Order, My Address, Account Details) β cart β checkout, wrapped in a single Unit of Work transaction β PDF receipt with embedded QR code linking back to Track Order.
Admin: dedicated /Admin/Login (Identity-only, no OAuth) β /Admin/Dashboard (cached Dapper aggregate stats) β Products/Vendors/Orders/Reviews CRUD (EF Core writes) β Manage Users with server-side search + pagination (handles 100k+ users without loading them all into memory) β Excel export via ClosedXML. Admin role is flat, not row-scoped β a second admin account has identical access to the first, by design.
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
![]() | |
![]() | ![]() |
![]() |
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
![]() |
![]() | ![]() |
![]() | ![]() |
![]() |
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
![]() |
Live at nej.software, proxied through Cloudflare with forwarded-headers trust configured so Serilog logs the real client IP rather than Cloudflare's edge IP.
Program.cs to register AppDbContext/Identity stores. Tradeoff: a hosting concern, not a layering violation β accepted as pragmatic.IRepository<T>/IUnitOfWork interfaces. Tradeoff: real-world dual-ORM pattern over a single-ORM "simpler" build, chosen because it's what the spec was actually testing.GetQueryable() added to IRepository<T> after Business-layer services were found bypassing the repository (injecting AppDbContext directly) to support paging. Tradeoff: extended the abstraction rather than accept the leak β verified via full-codebase grep, not just claimed./Shop's public listing stayed EF-based, not migrated to Dapper, despite being read-heavy. Tradeoff: known, accepted inconsistency β flagged as a backlog item, not silently ignored.IDesignTimeDbContextFactory kept separate from runtime DI, since a class library has no Program.cs of its own for dotnet ef to resolve against.[Authorize(Roles=...)] at controller/Area level, not just hidden nav links. Real bug this caught: an Admin could originally add items to Cart via direct URL navigation, since CartController only had a plain [Authorize] β fixed to Roles = "Customer"..Users.ToList() pattern seen in a reference project, which breaks at real scale.IsActive flag), not hard delete β no cascading removal of Orders/Reviews/Addresses/Cart/Favorites. Tradeoff: preserves historical/financial data integrity over a "clean" full account wipe; email stays permanently claimed (not freed for re-registration) as the simpler of two workable options.CustomSignInManager.CanSignInAsync override plus an explicit IsActive check added to the Api's direct password-check path once that gap was found.OnValidatePrincipal (not a second cookie scheme). Tradeoff: more complex than splitting schemes, but avoids duplicating Identity's cookie infrastructure for a difference of degree, not kind.IMemoryCache applied selectively (Product/Category reads), with explicit invalidation on writes β not blanket-cached everywhere. Tradeoff: demonstrates the requirement meaningfully rather than risking missed-invalidation bugs from over-applying it.IHostedService, not a new job-scheduler dependency.QuestPDF.Settings.License = LicenseType.Community).MappingProfile, not one class per entity β accepted deviation from an original per-entity instruction, since splitting further would be over-engineering at this project's actual size.Microsoft.AspNetCore.OpenApi for API docs, Swashbuckle deliberately dropped β proved incompatible with .NET 10's newer minimal-hosting OpenAPI pipeline.Helsinki-NLP/opus-mt-tr-en, always applied regardless of input language, no detection step) β toxicity scoring (unitary/toxic-bert) β auto Approved/Hidden/Pending, with a manual admin override β no separate moderation queue. Uses IHttpClientFactory, not raw HttpClient, avoiding socket exhaustion. A real endpoint migration bug was found and fixed (the old api-inference.huggingface.co domain stopped resolving; moved to router.huggingface.co), and a real EF Core default-value bug was found and fixed (silently downgrading every new Approved review to Pending).min-width:0 grid-sizing bug recurring in two separate places, the dual-cause Add-to-Cart race condition).Part of the SoftITO Backend Program β 320-hour backend engineering journey, Istanbul Chamber of Commerce.
And this, ladies and gentlemen, concludes our intense backend journey for the time being. Time to catch some sleep and enjoy a well-earned break β but I've already got a running list of things I can't wait to build next!
ASP.NET Core MVC (.NET 10) e-commerce platform β N-tier architecture, dual-ORM data access (EF Core + Dapper), Identity + Google OAuth + JWT, RBAC, persistent cart, PDF/Excel/QR generation, AI-powered review moderation, and a production deployment behind Cloudflare.
π Live demo: nej.software π Program archive (all 11 projects): softITO-backend-2026
A note on this README: Our final presentation day was replaced at the last minute with a final exam + class boat trip, so there was no in-person walkthrough β no slide deck, no live demo, no Cloudflare/architecture talk-through in front of the class. Everything that would have been said out loud is written down here instead: the planning, the architectural decisions (and why, not just what), the folder structure, and screenshot evidence for every major feature, organized by area and collapsed by default so this stays readable.
Core Framework
Architecture
Entities β Data β Business β Api / Web, one-directional, compiler-enforcedIRepository<T>, IUnitOfWork)Data Access
Microsoft.EntityFrameworkCore.SqlServer, EF Core Tools/Design (migrations)Restrict on convergent paths, Cascade on single paths)Authentication & Authorization
Customer, AdminEkomart.Api projectIsActive flag), login-blocked across all three auth surfaces (Identity, Google, JWT)Caching
IMemoryCache, selectively applied to read-heavy catalog data with explicit write-path invalidationLogging
IHostedServiceX-Forwarded-For/X-Forwarded-Proto)Documents / Exports
AI / Content Moderation
Helsinki-NLP/opus-mt-tr-en) β toxicity classification (unitary/toxic-bert)IHttpClientFactory-based HTTP client with categorized failure logging (auth, rate-limit, cold-model, network, timeout)Mapping / Validation
API Documentation
Microsoft.AspNetCore.OpenApi (Swashbuckle explicitly not used β incompatible with .NET 10's newer minimal-hosting OpenAPI pipeline)Frontend / UX
Commerce
DevOps / Deployment
Ekomart.Entities β Ekomart.Data β Ekomart.Business β Ekomart.Api / Ekomart (Web)
(POCOs, (EF Core + (services, (thin API (MVC
zero deps) Dapper, caching, controllers, presentation,
Repo/UoW) validation) JWT, Scalar) Areas, Identity)
One-directional, compiler-enforced dependencies. Entities has zero framework dependencies. Only Data touches EF Core/Dapper directly β Business never sees AppDbContext, only IRepository<T>/IUnitOfWork. Both Api and Web depend on Business; Web additionally references Data directly since it owns DI registration (Program.cs) for the EF DbContext and Identity stores β a hosting concern, not a layering violation.
Data pipeline in short: reads flow through IMemoryCache β Dapper read repositories β flat DTOs (no change tracking). Writes flow through the EF Core repository path, wrapped in a Unit of Work transaction, with cache invalidation on success and a separate Serilog business-event log entry.
EKOMART.sln
β
βββ π¦ EKOMART.Entities/ # POCOs only β zero project dependencies
β βββ Concrete/
β β βββ Enums/
β βββ Identity/ # ApplicationUser, ApplicationRole
β
βββ ποΈ EKOMART.Data/ # EF configs + Dapper repos, Repo/UoW impl
β βββ Abstract/ # IRepository<T>, IUnitOfWork interfaces
β βββ Concrete/
β β βββ EfCore/
β β β βββ Configurations/ # EF entity type configs
β β βββ Dapper/ # connection factory, SQL read repos
β βββ Identity/
β βββ Migrations/
β βββ Seed/
β βββ UnitOfWork/
β
βββ βοΈ EKOMART.Business/ # service layer, caching, validation
β βββ Abstract/
β βββ Concrete/
β βββ Caching/
β βββ Dtos/ # β added mid-project, one folder per entity
β β βββ Address/ Brand/ Cart/ Category/ Dashboard/
β β βββ Identity/ Order/ OrderItem/ Payment/
β β βββ Product/ Review/ Transaction/ Vendor/
β βββ Mapping/ # AutoMapper profile
β βββ Validation/ # FluentValidation rules
β
βββ π EKOMART.Api/ # thin API controllers, JWT-secured
β βββ Controllers/
β βββ Middleware/
β βββ Models/
β βββ OpenApi/ # Scalar/OpenAPI config
β βββ Properties/
β βββ Services/
β
βββ π₯οΈ EKOMART/ # MVC presentation layer (Web)
βββ Areas/
β βββ Admin/
β β βββ Controllers/
β β βββ Models/
β β βββ Views/
β β βββ Brands/ Categories/ Dashboard/ Orders/
β β βββ Products/ Reviews/ Transactions/ Users/ Vendors/
β βββ Customer/ # β οΈ empty β kept for symmetry, unused
β βββ Controllers/
β βββ Models/
β βββ Views/
β
βββ Controllers/ # storefront (public, no area)
βββ Views/
β βββ About/ Account/ Cart/ Checkout/
β βββ Favorites/ Home/ Orders/ Shop/
β βββ Shared/ # _StoreLayout, _AdminLayout
β
βββ Models/ # view models, not domain entities
βββ Services/ # PDF/Excel/QR generation
βββ Logs/ # Serilog file-sink fallback
βββ Properties/
βββ wwwroot/
βββ assets/ # storefront CSS/JS/images/fonts
βββ assets-admin/ # admin dashboard CSS/JS/images
βββ css/ js/
βββ lib/ # SweetAlert2, Bootstrap, jQuery
Customer: anonymous storefront browsing (Dapper reads, [AllowAnonymous]) β register/login (Identity or "Sign in with Google", always assigned the Customer role) β My Account (Dashboard, Orders, Track Order, My Address, Account Details) β cart β checkout, wrapped in a single Unit of Work transaction β PDF receipt with embedded QR code linking back to Track Order.
Admin: dedicated /Admin/Login (Identity-only, no OAuth) β /Admin/Dashboard (cached Dapper aggregate stats) β Products/Vendors/Orders/Reviews CRUD (EF Core writes) β Manage Users with server-side search + pagination (handles 100k+ users without loading them all into memory) β Excel export via ClosedXML. Admin role is flat, not row-scoped β a second admin account has identical access to the first, by design.
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
![]() | |
![]() | ![]() |
![]() |
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
![]() |
![]() | ![]() |
![]() | ![]() |
![]() |
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
![]() |
Live at nej.software, proxied through Cloudflare with forwarded-headers trust configured so Serilog logs the real client IP rather than Cloudflare's edge IP.
Program.cs to register AppDbContext/Identity stores. Tradeoff: a hosting concern, not a layering violation β accepted as pragmatic.IRepository<T>/IUnitOfWork interfaces. Tradeoff: real-world dual-ORM pattern over a single-ORM "simpler" build, chosen because it's what the spec was actually testing.GetQueryable() added to IRepository<T> after Business-layer services were found bypassing the repository (injecting AppDbContext directly) to support paging. Tradeoff: extended the abstraction rather than accept the leak β verified via full-codebase grep, not just claimed./Shop's public listing stayed EF-based, not migrated to Dapper, despite being read-heavy. Tradeoff: known, accepted inconsistency β flagged as a backlog item, not silently ignored.IDesignTimeDbContextFactory kept separate from runtime DI, since a class library has no Program.cs of its own for dotnet ef to resolve against.[Authorize(Roles=...)] at controller/Area level, not just hidden nav links. Real bug this caught: an Admin could originally add items to Cart via direct URL navigation, since CartController only had a plain [Authorize] β fixed to Roles = "Customer"..Users.ToList() pattern seen in a reference project, which breaks at real scale.IsActive flag), not hard delete β no cascading removal of Orders/Reviews/Addresses/Cart/Favorites. Tradeoff: preserves historical/financial data integrity over a "clean" full account wipe; email stays permanently claimed (not freed for re-registration) as the simpler of two workable options.CustomSignInManager.CanSignInAsync override plus an explicit IsActive check added to the Api's direct password-check path once that gap was found.OnValidatePrincipal (not a second cookie scheme). Tradeoff: more complex than splitting schemes, but avoids duplicating Identity's cookie infrastructure for a difference of degree, not kind.IMemoryCache applied selectively (Product/Category reads), with explicit invalidation on writes β not blanket-cached everywhere. Tradeoff: demonstrates the requirement meaningfully rather than risking missed-invalidation bugs from over-applying it.IHostedService, not a new job-scheduler dependency.QuestPDF.Settings.License = LicenseType.Community).MappingProfile, not one class per entity β accepted deviation from an original per-entity instruction, since splitting further would be over-engineering at this project's actual size.Microsoft.AspNetCore.OpenApi for API docs, Swashbuckle deliberately dropped β proved incompatible with .NET 10's newer minimal-hosting OpenAPI pipeline.Helsinki-NLP/opus-mt-tr-en, always applied regardless of input language, no detection step) β toxicity scoring (unitary/toxic-bert) β auto Approved/Hidden/Pending, with a manual admin override β no separate moderation queue. Uses IHttpClientFactory, not raw HttpClient, avoiding socket exhaustion. A real endpoint migration bug was found and fixed (the old api-inference.huggingface.co domain stopped resolving; moved to router.huggingface.co), and a real EF Core default-value bug was found and fixed (silently downgrading every new Approved review to Pending).min-width:0 grid-sizing bug recurring in two separate places, the dual-cause Add-to-Cart race condition).Part of the SoftITO Backend Program β 320-hour backend engineering journey, Istanbul Chamber of Commerce.
And this, ladies and gentlemen, concludes our intense backend journey for the time being. Time to catch some sleep and enjoy a well-earned break β but I've already got a running list of things I can't wait to build next!