Code Structure
A modular, layered structure. If you’re extending the app, this is where to find things.
Project Structure
Dependency direction flows one way:
eCommerce.Web → eCommerce.Services → eCommerce.Data → eCommerce.Entities
with eCommerce.Shared referenced by all of the above for cross-cutting helpers.
eCommerce.Web
The ASP.NET Core MVC application serving both browser-based and API traffic.
- Controllers in three places: root
Controllers/(storefront),Areas/Dashboard/Controllers/(admin),Areas/API/Controllers/(JWT JSON API) - Dashboard authorization is enforced by
eCommerce.Shared.Attributes.CustomAuthorize, a plainIAuthorizationFilterapplied at theDashboardBaseControllerclass level — ASP.NET Core’s built-in[Authorize]has noHandleUnauthorizedRequestoverride to hook the old “wrong role → redirect to a custom page” behavior, so it’s reimplemented as a filter instead - Views in root
Views/(storefront) andAreas/Dashboard/Views/(admin) - Static assets under
wwwroot/content/(lowercase path segments) - Separate ViewModels per surface: storefront (
ViewModels/), dashboard (Areas/Dashboard/ViewModels/), API (Areas/API/Models/) - Unhandled exceptions and 404s are served as static HTML (
wwwroot/500.html,404.html) wired up inProgram.cs, not a Razor error view
eCommerce.Data
eCommerceContext — an EF Core IdentityDbContext<eCommerceUser> — holds the shared entity model. Schema changes are code-first: edit the entity, add a migration, it applies automatically on next app start.
Which database engine is used is a runtime configuration choice (DatabaseProvider in appsettings.json — see Setup → Database Providers), not a code change. Each provider (SQL Server, MySQL, PostgreSQL, SQLite) has its own migration history under Migrations/<Provider> and its own empty eCommerceContext subclass under Providers/ — a migration scaffolded for one provider bakes in that provider’s column types and auto-increment annotations, so it can’t be replayed against a different engine. DatabaseProviderConfigurator is the single place that wires a provider name and connection string into the right UseSqlServer/UseMySQL/UseNpgsql/UseSqlite call and the matching subclass.
eCommerce.Services
Business/data-access logic, one static class per aggregate (e.g. ProductsService, OrdersService, CategoriesService), each exposed as a singleton via XService.Instance rather than constructor-injected. Every method opens its own fresh DbContext via DataContextHelper.GetNewContext() — there is no shared/injected context passed around. Identity-related operations go through ASP.NET Core Identity’s own UserManager/SignInManager/RoleManager, wrapped where needed (eCommerceUserManager, eCommerceSignInManager, eCommerceRoleManager).
eCommerce.Entities
Plain EF entity classes (Product, Order, Category, eCommerceUser, …), plus APIEntities/ and CustomEntities/ for API-facing and composite shapes. No logic lives here.
eCommerce.Shared
Helpers, extensions, attributes, enums, and constants used everywhere. Notably, three static “holder” classes bridge ASP.NET Core’s DI container into this static-everywhere layer — HttpContextAccessorHolder, WebHostEnvironmentHolder, AppConfigurationHolder — each configured once at startup in Program.cs and read from statically everywhere else, including Razor view extension methods like "key".Localized().
Routing
Nearly every storefront route is registered twice in RouteConfig.cs: once bare, once prefixed with {lang}/, so the same page is reachable in any enabled language. Registration order matters for the Dashboard area — its routes must be registered before the storefront’s catch-all {lang} route, or a bare /dashboard request would incorrectly match the storefront’s single-segment language route instead.
JSON casing
The MVC pipeline is configured with PropertyNamingPolicy = null, so AJAX/storefront JSON responses keep PascalCase (matching the original classic-ASP.NET client-side JS, which expects e.g. response.Success). The separate Areas/API JSON envelope is serialized directly with Newtonsoft.Json and is PascalCase by construction, independent of that MVC-wide setting — see API → Response Envelope.