Online Shop - ASP.NET Core MVC

 


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

  1. Entities: These represent the data models.

  2. Data Access Layer (DAL): Handles data persistence and retrieval.

  3. Service Layer: Contains business logic.

  4. Controllers: Handle incoming requests and return appropriate views.

  5. 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

  1. Dependency Injection: Utilize dependency injection for better testability and maintainability.

  2. Repository Pattern: Abstract data access logic from business logic.

  3. Service Layer: Encapsulate business logic within a service layer.

  4. Validation: Use data annotations for model validation.

  5. Error Handling: Implement error handling and logging.

  6. 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:

  1. Onion Architecture ensures clear separation of concerns, modularity, and ease of testing by organizing the codebase into concentric layers.

  2. 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.




Post a Comment

Previous Post Next Post