Home / Code Structure

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 plain IAuthorizationFilter applied at the DashboardBaseController class level — ASP.NET Core’s built-in [Authorize] has no HandleUnauthorizedRequest override 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) and Areas/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 in Program.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.