Creating a complete online shop web portal using ASP.NET Core MVC involves several steps, including setting up the project, designing the architecture, implementing the models, views, and controllers, and following best practices for security, performance, and maintainability. Below is a comprehensive guide to building this project, including explanations and code snippets for each part.
Step 1: Set Up the Project
Create a new ASP.NET Core MVC project:
sh
=====================================
dotnet new mvc -n OnlineShop
cd OnlineShop
Install necessary packages (e.g., for Entity Framework Core):
sh
=====================================
dotnet add package Microsoft.EntityFrameworkCore.SqlServer
dotnet add package Microsoft.EntityFrameworkCore.Tools
Step 2: Design the Architecture
Entities: These represent the data models.
Data Access Layer (DAL): Handles data persistence and retrieval.
Service Layer: Contains business logic.
Controllers: Handle incoming requests and return appropriate views.
Views: The UI components.
Step 3: Implement the Data Models
Create the data models in the Models folder.
csharp
=====================================
// Models/Product.cs
using System.ComponentModel.DataAnnotations;
namespace OnlineShop.Models
{
public class Product
{
public int Id { get; set; }
[Required]
[StringLength(100)]
public string Name { get; set; }
[Required]
[DataType(DataType.Currency)]
public decimal Price { get; set; }
[Required]
public string Description { get; set; }
public string ImageUrl { get; set; }
}
}
// Models/Order.cs
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
namespace OnlineShop.Models
{
public class Order
{
public int Id { get; set; }
public DateTime OrderDate { get; set; }
public List<OrderItem> OrderItems { get; set; }
}
public class OrderItem
{
public int Id { get; set; }
public int ProductId { get; set; }
public int Quantity { get; set; }
public decimal UnitPrice { get; set; }
}
}
Step 4: Set Up the Database Context
Create the database context in the Data folder.
csharp
=====================================
// Data/ApplicationDbContext.cs
using Microsoft.EntityFrameworkCore;
using OnlineShop.Models;
namespace OnlineShop.Data
{
public class ApplicationDbContext : DbContext
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
public DbSet<Product> Products { get; set; }
public DbSet<Order> Orders { get; set; }
public DbSet<OrderItem> OrderItems { get; set; }
}
}
Configure the database connection string in appsettings.json.
json
=====================================
{
"ConnectionStrings": {
"DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=OnlineShopDb;Trusted_Connection=True;MultipleActiveResultSets=true"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}
Update Startup.cs to add the database context.
csharp
=====================================
// Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddControllersWithViews();
}
Step 5: Implement the Repository Pattern
Create a repository interface and class for data access.
csharp
=====================================
// Data/IProductRepository.cs
using System.Collections.Generic;
using OnlineShop.Models;
namespace OnlineShop.Data
{
public interface IProductRepository
{
IEnumerable<Product> GetAllProducts();
Product GetProductById(int id);
void AddProduct(Product product);
void UpdateProduct(Product product);
void DeleteProduct(int id);
}
}
// Data/ProductRepository.cs
using System.Collections.Generic;
using System.Linq;
using OnlineShop.Models;
namespace OnlineShop.Data
{
public class ProductRepository : IProductRepository
{
private readonly ApplicationDbContext _context;
public ProductRepository(ApplicationDbContext context)
{
_context = context;
}
public IEnumerable<Product> GetAllProducts()
{
return _context.Products.ToList();
}
public Product GetProductById(int id)
{
return _context.Products.FirstOrDefault(p => p.Id == id);
}
public void AddProduct(Product product)
{
_context.Products.Add(product);
_context.SaveChanges();
}
public void UpdateProduct(Product product)
{
_context.Products.Update(product);
_context.SaveChanges();
}
public void DeleteProduct(int id)
{
var product = _context.Products.FirstOrDefault(p => p.Id == id);
if (product != null)
{
_context.Products.Remove(product);
_context.SaveChanges();
}
}
}
}
Register the repository in Startup.cs.
csharp
=====================================
// Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddControllersWithViews();
// Register repository
services.AddScoped<IProductRepository, ProductRepository>();
}
Step 6: Implement the Service Layer
Create a service interface and class for business logic.
csharp
=====================================
// Services/IProductService.cs
using System.Collections.Generic;
using OnlineShop.Models;
namespace OnlineShop.Services
{
public interface IProductService
{
IEnumerable<Product> GetAllProducts();
Product GetProductById(int id);
void AddProduct(Product product);
void UpdateProduct(Product product);
void DeleteProduct(int id);
}
}
// Services/ProductService.cs
using System.Collections.Generic;
using OnlineShop.Data;
using OnlineShop.Models;
namespace OnlineShop.Services
{
public class ProductService : IProductService
{
private readonly IProductRepository _productRepository;
public ProductService(IProductRepository productRepository)
{
_productRepository = productRepository;
}
public IEnumerable<Product> GetAllProducts()
{
return _productRepository.GetAllProducts();
}
public Product GetProductById(int id)
{
return _productRepository.GetProductById(id);
}
public void AddProduct(Product product)
{
_productRepository.AddProduct(product);
}
public void UpdateProduct(Product product)
{
_productRepository.UpdateProduct(product);
}
public void DeleteProduct(int id)
{
_productRepository.DeleteProduct(id);
}
}
}
Register the service in Startup.cs.
csharp
=====================================
// Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddControllersWithViews();
// Register repository and service
services.AddScoped<IProductRepository, ProductRepository>();
services.AddScoped<IProductService, ProductService>();
}
Step 7: Implement the Controllers
Create a controller to handle product-related requests.
csharp
=====================================
// Controllers/ProductController.cs
using Microsoft.AspNetCore.Mvc;
using OnlineShop.Models;
using OnlineShop.Services;
namespace OnlineShop.Controllers
{
public class ProductController : Controller
{
private readonly IProductService _productService;
public ProductController(IProductService productService)
{
_productService = productService;
}
public IActionResult Index()
{
var products = _productService.GetAllProducts();
return View(products);
}
public IActionResult Details(int id)
{
var product = _productService.GetProductById(id);
if (product == null)
{
return NotFound();
}
return View(product);
}
public IActionResult Create()
{
return View();
}
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Create(Product product)
{
if (ModelState.IsValid)
{
_productService.AddProduct(product);
return RedirectToAction(nameof(Index));
}
return View(product);
}
public IActionResult Edit(int id)
{
var product = _productService.GetProductById(id);
if (product == null)
{
return NotFound();
}
return View(product);
}
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Edit(int id, Product product)
{
if (id != product.Id)
{
return BadRequest();
}
if (ModelState.IsValid)
{
_productService.UpdateProduct(product);
return RedirectToAction(nameof(Index));
}
return View(product);
}
public IActionResult Delete(int id)
{
var product = _productService.GetProductById(id);
if (product == null)
{
return NotFound();
}
return View(product);
}
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public IActionResult DeleteConfirmed(int id)
{
_productService.DeleteProduct(id);
return RedirectToAction(nameof(Index));
}
}
}
Step 8: Create Views
Create the necessary views for listing, creating, editing, and deleting products.
Views/Product/Index.cshtml:
html
=====================================
@model IEnumerable<OnlineShop.Models.Product>
<h1>Products</h1>
<a asp-action="Create">Create New</a>
<table class="table">
<thead>
<tr>
<th>Name</th>
<th>Price</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model)
{
<tr>
<td>@item.Name</td>
<td>@item.Price</td>
<td>
<a asp-action="Edit" asp-route-id="@item.Id">Edit</a> |
<a asp-action="Details" asp-route-id="@item.Id">Details</a> |
<a asp-action="Delete" asp-route-id="@item.Id">Delete</a>
</td>
</tr>
}
</tbody>
</table>
Views/Product/Create.cshtml:
html
=====================================
@model OnlineShop.Models.Product
<h1>Create Product</h1>
<form asp-action="Create">
<div class="form-group">
<label asp-for="Name"></label>
<input asp-for="Name" class="form-control" />
<span asp-validation-for="Name" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Price"></label>
<input asp-for="Price" class="form-control" />
<span asp-validation-for="Price" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Description"></label>
<input asp-for="Description" class="form-control" />
<span asp-validation-for="Description" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="ImageUrl"></label>
<input asp-for="ImageUrl" class="form-control" />
<span asp-validation-for="ImageUrl" class="text-danger"></span>
</div>
<button type="submit" class="btn btn-primary">Create</button>
</form>
Views/Product/Edit.cshtml:
html
=====================================
@model OnlineShop.Models.Product
<h1>Edit Product</h1>
<form asp-action="Edit">
<div class="form-group">
<label asp-for="Name"></label>
<input asp-for="Name" class="form-control" />
<span asp-validation-for="Name" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Price"></label>
<input asp-for="Price" class="form-control" />
<span asp-validation-for="Price" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Description"></label>
<input asp-for="Description" class="form-control" />
<span asp-validation-for="Description" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="ImageUrl"></label>
<input asp-for="ImageUrl" class="form-control" />
<span asp-validation-for="ImageUrl" class="text-danger"></span>
</div>
<button type="submit" class="btn btn-primary">Save</button>
</form>
Views/Product/Details.cshtml:
html
=====================================
@model OnlineShop.Models.Product
<h1>Product Details</h1>
<div>
<h4>Product</h4>
<hr />
<dl class="row">
<dt class="col-sm-2">
Name
</dt>
<dd class="col-sm-10">
@Model.Name
</dd>
<dt class="col-sm-2">
Price
</dt>
<dd class="col-sm-10">
@Model.Price
</dd>
<dt class="col-sm-2">
Description
</dt>
<dd class="col-sm-10">
@Model.Description
</dd>
<dt class="col-sm-2">
Image URL
</dt>
<dd class="col-sm-10">
@Model.ImageUrl
</dd>
</dl>
</div>
<div>
<a asp-action="Edit" asp-route-id="@Model.Id">Edit</a> |
<a asp-action="Index">Back to List</a>
</div>
Views/Product/Delete.cshtml:
html
=====================================
@model OnlineShop.Models.Product
<h1>Delete Product</h1>
<h3>Are you sure you want to delete this?</h3>
<div>
<h4>Product</h4>
<hr />
<dl class="row">
<dt class="col-sm-2">
Name
</dt>
<dd class="col-sm-10">
@Model.Name
</dd>
<dt class="col-sm-2">
Price
</dt>
<dd class="col-sm-10">
@Model.Price
</dd>
<dt class="col-sm-2">
Description
</dt>
<dd class="col-sm-10">
@Model.Description
</dd>
<dt class="col-sm-2">
Image URL
</dt>
<dd class="col-sm-10">
@Model.ImageUrl
</dd>
</dl>
</div>
<form asp-action="DeleteConfirmed">
<input type="hidden" asp-for="Id" />
<button type="submit" class="btn btn-danger">Delete</button> |
<a asp-action="Index">Back to List</a>
</form>
Step 9: Apply Migrations and Update Database
Run the following commands to apply migrations and update the database:
sh
=====================================
dotnet ef migrations add InitialCreate
dotnet ef database update
Best Practices
Dependency Injection: Utilize dependency injection for better testability and maintainability.
Repository Pattern: Abstract data access logic from business logic.
Service Layer: Encapsulate business logic within a service layer.
Validation: Use data annotations for model validation.
Error Handling: Implement error handling and logging.
Security: Protect sensitive data and use HTTPS.
Summary
This guide covers the fundamental aspects of creating an online shop web portal using ASP.NET Core MVC. It follows a layered architecture with clear separation of concerns, including data models, data access, business logic, and presentation layers. The code provided is a basic example and can be further enhanced with additional features, such as user authentication, shopping cart functionality, and more robust error handling.
=========================================================================
The architecture style used in the provided guide is a combination of Onion Architecture and Domain-Driven Design (DDD) principles. Here's a breakdown of these concepts and how they apply to the architecture:
1. Onion Architecture
Onion Architecture emphasizes the separation of concerns and promotes a domain-centric approach to application design. The core idea is to organize the codebase into concentric layers, each with a specific responsibility, ensuring that dependencies flow inwards. This style is designed to be flexible, maintainable, and testable.
Layers of Onion Architecture
Core Layer (Domain Layer): Contains the business logic and domain entities. This layer is independent of external dependencies.
Entities: Business objects with identity.
Value Objects: Objects defined by their attributes.
Repositories Interfaces: Abstractions for data access.
Application Layer: Contains business logic specific to application use cases. It orchestrates the domain layer and coordinates tasks.
Services: Business logic operations.
DTOs (Data Transfer Objects): Data structures for transferring data between layers.
Infrastructure Layer: Contains implementations for data access, logging, external services, etc. This layer depends on the core layer but not vice versa.
Data Access: Entity Framework Core, repositories.
Logging: Serilog, NLog.
Presentation Layer: Contains the UI logic, such as MVC controllers, views, and view models.
Controllers: Handle HTTP requests and responses.
Views: Render HTML interfaces.
ViewModels: Data structures for views.
2. Domain-Driven Design (DDD)
Domain-Driven Design (DDD) is an approach to software development that emphasizes a rich domain model and a clear separation of concerns. It focuses on the core domain logic and aims to create a common language between developers and domain experts.
Key Concepts in DDD
Entities: Objects that have a distinct identity and lifecycle.
csharp
=====================================
public class Order
{
public int Id { get; set; }
public DateTime OrderDate { get; set; }
public Customer Customer { get; set; }
public List<OrderItem> Items { get; set; }
}
Value Objects: Objects that are defined by their attributes rather than identity.
csharp
=====================================
public class Address
{
public string Street { get; set; }
public string City { get; set; }
public string ZipCode { get; set; }
}
Aggregates: Clusters of entities and value objects treated as a single unit.
csharp
=====================================
public class Order : AggregateRoot
{
public List<OrderItem> Items { get; set; }
// Business logic for Order aggregate
}
Repositories: Interfaces for accessing aggregates.
csharp
=====================================
public interface IOrderRepository
{
Task<Order> GetOrderByIdAsync(int id);
Task AddOrderAsync(Order order);
}
Services: Business logic operations that do not naturally fit within entities or value objects.
csharp
=====================================
public class OrderService
{
private readonly IOrderRepository _orderRepository;
public OrderService(IOrderRepository orderRepository)
{
_orderRepository = orderRepository;
}
public async Task<Order> GetOrderByIdAsync(int id)
{
return await _orderRepository.GetOrderByIdAsync(id);
}
public async Task AddOrderAsync(Order order)
{
await _orderRepository.AddOrderAsync(order);
}
}
Combining Onion Architecture and DDD
Combining these two approaches provides a robust and scalable architecture:
Onion Architecture ensures clear separation of concerns, modularity, and ease of testing by organizing the codebase into concentric layers.
DDD adds depth by focusing on the domain model and business logic, promoting a shared understanding of the domain and creating a rich domain model.
Summary
By using Onion Architecture and DDD principles, the proposed architecture is:
Flexible: Easily adaptable to changing requirements with a clear separation of concerns.
Scalable: Supports growth by modularizing components and focusing on the core domain.
Auditable: Centralized logging and monitoring can be easily integrated, and business logic is well-encapsulated, making it easier to trace and audit actions.
This architecture style provides a strong foundation for building modern, maintainable, and scalable ASP.NET MVC applications.