[ Полезный рекламный блок ]
Попробуйте свои силы в игре, где ваши навыки программирования на C# станут решающим фактором. Переходите по ссылке 🔰.
У цій статті, ми продовжимо писати магазин на Asp.Net Core і Entity Framework Core. У четвертій частині ми дізналися яким чином адаптувати додаток GameStore для роботи з більшими обсягами даних. Додали до нього підтримку розбиття на сторінки, упорядкування та пошуку даних. Для ознайомлення з четвертою частиною, перейдіть за посиланням.
У цій статті буде побудовано частини додатка GameStore, які реалізують інтерфейс для покупців, що дасть змогу обирати товари, переглядати кошик для покупок і оформляти замовлення. Функціональні засоби, що додаються, значною мірою пов’язані з інфраструктурою ASP.NET Core MVC і ґрунтуються на фундаменті Entity Framework Core, який було створено в попередніх розділах.
Магазин на Asp.Net Core MVC EF. Частина 5
Додавання початкових даних про товари
Створення та застосування міграції
Відображення товарів для покупця
Створення контролера та подання
Виклад матеріалу в статті буде вестися прискореними темпами, оскільки більша частина роботи стосується побудови засобів з використанням ASP.NET Core МVC поверх фундаменту, сформованого за допомогою інфраструктури Entity Fгamework Core в попередніх розділах.
Додавання імпорту подання
У попередньому розділі клас PagedList застосовувався в поданнях без модифікації моделі подання просто для демонстрації того, що засоби масштабування можна додати, внісши мінімальні зміни. У цьому розділі клас PagedList буде використовуватися в поданнях безпосередньо, тому додамо простір імен, що містить його, у файл імпортування подань Views / _ViewImports.cshtml:
1 2 3 4 |
@using GameStore @using GameStore.Models @using GameStore.Models.Pages @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers |
Модифікація моделі даних
Щоб підготувати модель даних до роботи із засобами інтерфейсу для покупців, додамо в клас Product властивість Description, яка дасть змогу покупцям отримати трохи відомостей про товар:
1 2 3 4 5 6 7 8 9 10 11 |
public class Product { public int Id { get; set; } public string Name { get; set; } public string Description { get; set; } public decimal PurchasePrice { get; set; } public decimal RetailPrice { get; set; } public int CategoryId { get; set; } public Category Category { get; set; } } |
Для полегшення запиту і збереження даних за категорією додамо в клас Сategory навігаційну властивість, яку інфраструктура Entity Framework Core зможе заповнювати пов’язаними об’єктами Product:
1 2 3 4 5 6 7 |
{ public int Id { get; set; } public string Name { get; set; } public string Description { get; set; } public IEnumerable<Product> Products { get; set; } } |
Додавання початкових даних про товари
Нам необхідна можливість перемикання між великими обсягами тестових даних і невеликими обсягами реалістичних даних. З цією метою додамо в контролер Seed код, який забезпечить наявність стандартних категорій і товарів GameStore, створимо метод CreateProductionData:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
public class SeedController : Controller { //... [HttpPost] public IActionResult CreateProductionData() { ClearData(); _context.Categories.AddRange ( new Category { Name = "Шутеры", Description = "Наделай шуму", Products = new Product[] { new Product { Name = "WarZone", Description = "Мультиплатформенная компьютерная игра в жанре многопользовательского шутера от первого лица.", PurchasePrice = 50, RetailPrice = 70}, new Product { Name = "DOOM Eternal", Description = "DOOM Eternal смогла объединить механики традиционного шутера с элементами слэшера.", PurchasePrice = 76, RetailPrice = 90}, new Product { Name = "Battlefield V", Description = "В 2019 году Infinity Ward выпустила, пожалуй, самую топовую Call of Duty за последние несколько лет. ", PurchasePrice = 100, RetailPrice = 120}, new Product { Name = "Wolfenstein 2", Description = "Bulletstorm стала лебединой песней студии People Can Fly. Это накачанный адреналином шутер с отмороженным наёмником в главной роли.", PurchasePrice = 35, RetailPrice = 50}, } }, new Category { Name = "Стратегии", Description = "Думай чем можешь", Products = new Product[] { new Product { Name = "Civilization 5", Description = "Пожалуй, лучшая современная часть «Цивилизации». В меру глубокая и понятная даже для новичка", PurchasePrice = 50, RetailPrice = 70}, new Product { Name = "Age of Empires 2", Description = "Вторую часть AOE можно считать современной классикой стратегий в реальном времени. ", PurchasePrice = 76, RetailPrice = 90}, new Product { Name = "Warcraft 3 ", Description = "История принца Артаса, который буквально продал душу ради своего народа, знакома миллионам игроков.", PurchasePrice = 100, RetailPrice = 120}, new Product { Name = "SimCity 4 ", Description = "Четвёртую часть градостроительного симулятора от создателя The Sims Уилла Райта по праву можно считать одной из лучших игр в жанре.", PurchasePrice = 35, RetailPrice = 50}, } }, new Category { Name = "Фатинги", Description = "Ударил - беги", Products = new Product[] { new Product { Name = "Mortal Kombat 11", Description = "Самое полное издание Mortal Kombat 11. Погружайтесь в две кинематографические сюжетные кампании.", PurchasePrice = 50, RetailPrice = 70}, new Product { Name = "Street Fighter V: Champion Edition", Description = "Последняя часть легендарной серии файтингов, с которой и зародился жанр, получила самое крупное обновление.", PurchasePrice = 76, RetailPrice = 90}, new Product { Name = "SOULCALIBUR VI", Description = "Душа внутри пылает – и пылает она еще ярче и пуще прежнего. Серия возвращается к своим корням с легендарными поединками с оружием.", PurchasePrice = 100, RetailPrice = 120}, new Product { Name = "TEKKEN 7", Description = "И вот, спустя 20 лет, война между членами клана Мисима, наконец, достигла своего апогея.", PurchasePrice = 35, RetailPrice = 50}, } } ); _context.SaveChanges(); return RedirectToAction(nameof(Index)); } } |
Новий метод дії створює послідовність об’єктів Category і встановлює навігаційну властивість Products у колекцію об’єктів Product. Усі об’єкти передаються методу AddRange() і зберігаються в БД методом SaveChanges().
Щоб націлитися на новий метод дії, додамо в подання Index, що застосовується контролером Seed, такий елемент:
1 2 3 |
<div class="text-center p-3"> <button type="submit" asp-action="CreateProductionData" class="btn btn-primary">Заполнить базу продуктами</button> </div> |
Елемент button відправляє НТГР-запит POST, результатом якого буде очищення БД і її заповнення стандартними категоріями і товарами GameStore.
Створення та застосування міграції
Щоб оновити БД, потрібно створити і застосувати до неї міграцію.
Для створення міграції у вікні Package Manager Console введіть таку команду:
1 |
Add-Migration AddCustomer |
Для виконання інструкцій міграції, у вікні Package Manager Console виконайте команду:
1 |
Update-Database |
Перша команда створює нову міграцію на ім’я AddCustomer, яка міститиме команди, необхідні для підготовки БД до зберігання нових об’єктів. Друга команда виконує такі команди для оновлення БД.
Запустіть додаток, перейдіть на сторінку “Початкові дані”, очистіть таблиці та заповніть продуктами. Перевірте роботу програми.
Відображення товарів для покупця
У наступних розділах буде додано підтримку для відображення списку товарів користувачеві, що дасть йому змогу фільтрувати товари за категоріями та переглядати товари, доступні для купівлі. Підтримка будуватиметься на основі засобів створених раніше.
Підготовка моделі даних
Щоб розпочати частину програми, пов’язану з інтерфейсом для покупців, додайте можливість запитування об’єктів Product за їхньою категорією, почавши з інтерфейсу сховища IProduct:
1 2 3 4 5 6 7 8 9 10 |
public interface IProduct { PagedList<Product> GetProducts(QueryOptions options, int category = 0); IEnumerable<Product> GetAllProducts(); Product GetProduct(int id); void AddProduct(Product product); void UpdateProduct(Product product); void UpdateAll(Product[] products); void DeleteProduct(Product product); } |
Внесемо відповідну зміну в клас реалізації, застосовуючи LINQ-метод Where() для запиту на основі властивості зовнішнього ключа, яка асоціює об’єкт Product із пов’язаним об’єктом Category, змінимо реалізацію методу в класі ProductRepository:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
public class ProductRepository : IProduct { //... public PagedList<Product> GetProducts(QueryOptions options, int category = 0) { IQueryable<Product> products = _context.Products.Include(e => e.Category); if (category != 0) { products = products.Where(e => e.CategoryId == category); } return new PagedList<Product>(products, options); } } |
Інтерфейс IQueryable<T> дає змогу формувати запит на основі параметрів методу, створюючи об’єкт, що запитуватиме БД, тільки коли він перераховується. Це перевага роботи з об’єктами IQueryable<T>, хоча є й недолік – легкість, з якою можна ненавмисно ініціювати дубльовані запити.
Створення URL повернення
Нам необхідно знати, на який URL переходити після того, як користувач вибрав товар. Для полегшення процесу створимо папку Infrastructure і додамо в неї файл класу на ім’я UrlExtensions.cs з таким кодом:
1 2 3 4 5 6 7 |
public static class UrlExtensions { public static string PathAndQuery(this HttpRequest request) { return request.QueryString.HasValue ? $"{request.Path}{request.QueryString}" : request.Path.ToString(); } } |
У класі UrlExtensions визначається метод PathAndQuery(), що розширює PathAndQuery(), який буде використовуватися в елементі form.
Перейдемо у файл Views/ViewImports.cshtml, додамо туди такий простір імен:
1 2 3 4 5 |
@using GameStore @using GameStore.Models @using GameStore.Models.Pages @using GameStore.Infrastructure @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers |
Створення контролера Store, уявлень і компонування
Щоб надати контролер, який буде представляти дані покупцеві, додамо в папку Controllers файл на ім’я StoreController.cs і помістимо в нього такий код:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
public class StoreController : Controller { private readonly IProduct _products; private readonly ICategory _categories; public StoreController(IProduct products, ICategory categories) { _products = products; _categories = categories; } public IActionResult Index([FromQuery(Name = "options")] QueryOptions productOptions, QueryOptions catOptions, int category) { ViewBag.Categories = _categories.GetCategories(catOptions); ViewBag.SelectedCategory = category; return View(_products.GetProducts(productOptions, category)); } } |
Для управління відображенням даних Product і Category використовуються два об’єкти QueryOptions. Вони застосовуються для отримання об’єкта PagedList<Product>, який передається поданню як його модель, і об’єкта PagedList<Category>, що додається до ViewBag.
Щоб забезпечити засобам інтерфейсу для покупців компонування, створимо папку Views / Store і помістимо в неї файл на ім’я _Layout.cshtml з таким вмістом:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>@ViewData["Title"] - GameStore</title> <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" /> <link rel="stylesheet" href="~/css/site.css" asp-append-version="true" /> <link rel="stylesheet" href="~/GameStore.styles.css" asp-append-version="true" /> </head> <body> <div class="container-fluid"> <div class="row bg-dark p-4 text-white"> <div class="col-auto"><h4>Магазин игр</h4></div> <div class="col"></div> <div class="col-auto text-right"> (Корзина) </div> </div> </div> @RenderBody() <footer class="border-top footer text-muted"> <div class="container"> © 2022 - GameStore - <a asp-area="" asp-controller="Home" asp-action="Privacy">Privacy</a> </div> </footer> <script src="~/lib/jquery/dist/jquery.min.js"></script> <script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script> <script src="~/js/site.js" asp-append-version="true"></script> @await RenderSectionAsync("Scripts", required: false) </body> </html> |
Компонування представляє стандартний заголовок “Магазин ігор”, із заповнювачем замість зведення по кошику для покупок, яке буде додано в додаток пізніше. Для відображення списку товарів додамо в папку Views / Store файл на ім’я Index.cshtml з таким вмістом:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
class="container-fluid"> <div class="row no-gutters"> <div class="col-auto"> @Html.Partial("Categories", ViewBag.Categories as PagedList<Category>) </div> <div class="col"> <div class="container-fluid"> <div class="row pt-1 pb-1"> <div class="col text-center"> @Html.Partial("Pages", Model) </div> </div> <div class="row pt-1 pb-1"> <div class="col"></div> <div class="col-6 text-center form-group"> <input form="pageform" type="hidden" name="options.searchpropertyname" value="Name" /> <input form="pageform" name="options.searchterm" placeholder="Поиск..." class="form-control" /> </div> <div class="col"> <button form="pageform" class="btn btn-secondary" type="submit">Поиск</button> </div> <div class="col"></div> </div> @foreach (Product p in Model) { <div class="row"> <div class="col"> <div class="car m-1 p-1 bg-light"> <div class="bg-faded p-1"> <h4> @p.Name <span style="float:right"> <small>@p.RetailPrice</small> </span> </h4> </div> <form id="@p.Id" asp-action="AddToCart" asp-controller="Cart" method="post"> <input type="hidden" name="Id" value="@p.Id" /> <input type="hidden" name="Name" value="@p.Name" /> <input type="hidden" name="RetailPrice" value="@p.RetailPrice" /> <input type="hidden" name="returnUrl" value="@ViewContext.HttpContext.Request.PathAndQuery()" /> <span class="card-text p-1"> @(p.Description ?? "Нет описания") <button type="submit" class="btn btn-success btn-sm pull-right" style="float:right">Добавить в корзину</button> </span> </form> </div> </div> </div> } </div> </div> </div> </div> |
У поданні Index зібрано разом кілька засобів для відображення товарів, включно з розбивкою на сторінки та підтримкою пошуку. Щоб відобразити користувачеві список категорій, додамо до папки Views / Store файл на ім’я Categories.cshtml з таким вмістом:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
@model PagedList<Category> <div class="container-fluid mt-4"> <div class="row no-gutters"> <div class="col mt-1"> <button form="pageform" name="category" value="0" type="submit" class="btn btn-block @(ViewBag.SelectedCategory == 0 ? "btn-primary":"btn-outline-primary")"> Все </button> </div> </div> <div class="row no-gutters mt-4"></div> <div class="row no-gutters"> <div class="col mt-1"> <button form="pageform" name="catoptions.currentPage" value="@(Model.CurrentPage - 1)" class="btn btn-block btn-outline-secondary @(Model.HasPreviousPage ? "disabled":"")" type="submit"> Назад </button> </div> </div> @foreach (Category c in Model) { <div class="row no-gutters"> <div class="col mt-1"> <button form="pageform" name="category" value="@c.Id" type="submit" class="btn blockquote @(ViewBag.SelectedCategory == c.Id ? "btn-primary":"btn-outline-primary")"> @c.Name </button> </div> </div> } <div class="row no-gutters"> <div class="col mt-1"> <button form="pageform" name="catoptions.currentPage" value="@(Model.CurrentPage + 1)" class="btn blockquote btn-outline-secondary @(!Model.HasNextPage? "disable" : "")" type="submit"> Вперед </button> </div> </div> </div> |
Подання Categories відображає список доступних категорій і пропонує кнопки Previous (Попередня) і Next (Наступна) для гортання списку. Елементи button, які обирають категорії, застосовують НТМL-форму на ім’я pagesform для націлювання на контролер за допомогою значення первинного ключа обраної категорії.
Для тестування результату, запустимо додаток і перейдемо за адресою:
Перевірте, як працює пошук, навігація, вибір категорії.
Можете так само перевірити, як контролер Store справляється з великими обсягами даних, перейшовши за посиланням https://localhost:7191/seed і згенерувавши великий обсяг даних.
Оскільки модель продукту була змінена, ми додали опис до товару, необхідно змінити функцію наповнення товарів. Для цього перейдемо в контролер Seed, змінимо визначення методу CreateSeedData():
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
public class SeedController : Controller { //... [HttpPost] public IActionResult CreateSeedData(int count) { ClearData(); if (count > 0) { _context.Database.SetCommandTimeout(TimeSpan.FromMinutes(10)); _context.Database.ExecuteSqlRaw("DROP PROCEDURE IF EXISTS CreateSeedData"); _context.Database.ExecuteSqlRaw($@" CREATE PROCEDURE CreateSeedData @RowCount decimal AS BEGIN SET NOCOUNT ON DECLARE @i INT = 0; DECLARE @catId INT; DECLARE @CatCount INT = @RowCount / 10; DECLARE @pprice DECIMAL(5,2); DECLARE @rprice DECIMAL(5,2); BEGIN TRANSACTION WHILE @i < @CatCount BEGIN INSERT INTO Categories (Name,Description) VALUES (CONCAT('Category-',@i), 'Test Data Category'); SET @catId = SCOPE_IDENTITY(); DECLARE @j INT = 1; WHILE @j <= 10 BEGIN SET @pprice = RAND() * (500-5+1); SET @rprice = (RAND() * @pprice) + @pprice; INSERT INTO Products (Name,CategoryId,PurchasePrice,RetailPrice,Description) VALUES (CONCAT('Product',@i,'-',@j),@catId,@pprice,@rprice,CONCAT('Description',@i,'-',@j)) SET @j = @j + 1 END SET @i = @i + 1 END COMMIT END"); _context.Database.BeginTransaction(); _context.Database.ExecuteSqlRaw($"EXEC CreateSeedData @RowCount = {count}"); _context.Database.CommitTransaction(); } return RedirectToAction(nameof(Index)); } } |
Додавання кошика для покупок
Конфігурування сесії
Наступний крок пов’язаний із додаванням підтримки для вибору товарів і їх збереження в кошику, яку потім можна застосовувати для оформлення замовлення. У наступних темах застосунок буде сконфігуровано для збереження даних сеансу і використання цього як тимчасового сховища обраних товарів.
Щоб увімкнути сеанси в БД, додамо в клас Program, наступний код:
1 2 3 4 5 6 7 8 9 10 |
builder.Services.AddSession(options => { options.Cookie.Name = "GameStore.Session"; options.IdleTimeout = System.TimeSpan.FromHours(48); options.Cookie.HttpOnly = false; }); var app = builder.Build(); app.UseSession(); |
Засіб сеансів буде зберігати тільки значення string. Для полегшення роботи з цим засобом додайте в папку Infrastructure файл класу на ім’я SessionExtensions.cs з таким вмістом:
1 2 3 4 5 6 7 8 9 10 11 12 |
public static class SessionExtensions { public static void SetJson(this ISession session, string key, object value) { session.SetString(key, JsonConvert.SerializeObject(value)); } public static T GetJson<T>(this ISession session, string key) { var sessionData = session.GetString(key); return sessionData == null ? default(T) : JsonConvert.DeserializeObject<T>(sessionData); } } |
У класі SessionExtensions визначено розширювальні методи, які серіалізують об’єкти у формат JSON і відновлюють їх знову, даючи змогу легко зберігати прості об’єкти як дані сеансу.
Створення класу моделі Cart
Для представлення обраних покупцем товарів додайте в папку Models файл на ім’я Cart.cs і визначте в ньому клас із таким вмістом:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
public class Cart { private List<OrderLine> selections = new List<OrderLine>(); public IEnumerable<OrderLine> Selections { get => selections; } public Cart AddItem(Product p, int quantity) { OrderLine orderLine = selections.Where(e => e.ProductId == p.Id).FirstOrDefault(); if (orderLine != null) { orderLine.Quantity += quantity; } else { selections.Add(new OrderLine { ProductId = p.Id, Product = p, Quantity = quantity }); } return this; } public Cart RemoveItem(int productId) { selections.RemoveAll(e => e.ProductId == productId); return this; } public void Clear() => selections.Clear(); } |
Клас Cart управляє колекцією об’єктів OrderLine, яка представляє обрані товари і може бути легко збережена в БД при створенні замовлення.
Створення контролера та подання
Щоб надати логіку, що підтримує роботу з об’єктами Cart, додайте в Controllers файл на ім’я CartController.cs і помістіть у нього такий код:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
[ViewComponent(Name = "Cart")] public class CartController : Controller { private readonly IProduct _products; private readonly IOrder _order; public CartController(IProduct products, IOrder order) { _products = products; _order = order; } private Cart GetCart() => HttpContext.Session.GetJson<Cart>("Cart") ?? new Cart(); private void SaveCart(Cart cart) => HttpContext.Session.SetJson("Cart", cart); public IActionResult Index(string returnUrl) { ViewBag.returnUrl = returnUrl; return View(GetCart()); } [HttpPost] public IActionResult AddToCart(Product product, string returnUrl) { SaveCart(GetCart().AddItem(product, 1)); return RedirectToAction(nameof(Index), new { returnUrl }); } [HttpPost] public IActionResult RemoveFromCart(int productId, string returnUrl) { SaveCart(GetCart().RemoveItem(productId)); return RedirectToAction(nameof(Index), new { returnUrl }); } public IActionResult Completed() { return View(); } public IActionResult CreateOrder() { return View(); } [HttpPost] public IActionResult CreateOrder(Order order) { order.Lines = GetCart().Selections.Select(e => new OrderLine { ProductId = e.ProductId, Quantity = e.Quantity }).ToArray(); _order.AddOrder(order); SaveCart(new Cart()); return RedirectToAction(nameof(Completed)); } public IViewComponentResult Invoke(ISession session) { return new ViewViewComponentResult() { ViewData = new ViewDataDictionary<Cart>(ViewData, session.GetJson<Cart>("Cart")) }; } } |
Контролер визначає дії, які додають і видаляють елементи з кошика, відображають вміст кошика і надають покупцеві можливість оформити замовлення. Низка методів приймає параметр returnUrl, що дає змогу користувачеві повернутися до списку товарів, не втрачаючи параметрів рядка запиту, які конфігурують розбивку на сторінки та фільтрацію за категорією. Клас CartController також є компонентом подання, який буде застосовуватися для відображення зведення по кошику в компонуванні інтерфейсу покупців.
Створення уявлень
Щоб забезпечити новий контролер поданням для управління кошиком, створіть папку Views / Cart і додайте в неї файл на ім’я Index.cshtml з таким вмістом:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 |
@model Cart @{ Layout = "~/Views/Store/_Layout.cshtml"; } <h2 class="m-3">Ваша корзина</h2> <div class="container-fluid"> <div class="row"> <div class="col font-weight-bold">Количество</div> <div class="col font-weight-bold">Товар</div> <div class="col font-weight-bold text-right">Цена</div> <div class="col font-weight-bold text-right">Общая сумма</div> <div class="col"></div> </div> @if (Model.Selections.Count() == 0) { <div class="row mt-2"> <div class="col-12"> <h4>Корзина пуста</h4> </div> </div> } else { @foreach (OrderLine line in Model.Selections) { <div class="row mt-1"> <div class="col">@line.Quantity</div> <div class="col">@line.Product.Name</div> <div class="col text-right">@line.Product.RetailPrice.ToString("f2")</div> <div class="col text-right">$@((line.Product.RetailPrice * line.Quantity).ToString("f2"))</div> <div class="col"> <form asp-action="RemoveFromCart"> <button type="submit" name="productId" value="@line.ProductId" class="btn btn-sm btn-outline-danger"> Удалить </button> </form> </div> </div> } } <div class="row mt-2"> <div class="col"></div> <div class="col"></div> <div class="col text-right font-weight-bold">Всего:</div> <div class="col text-right font-weight-bold"> $@(Model.Selections.Sum(e=>e.Product.RetailPrice * e.Quantity).ToString("f2")) </div> <div class="col"></div> </div> </div> <div class="text-align-content-center m-2"> @if (ViewBag.returnUrl != null) { <a href="@ViewBag.returnUrl" class="btn btn-outline-primary">Продолжить покупки</a> } <a asp-action="CreateOrder" class="btn btn-primary">Оформить заказ</a> </div> |
Подання відображає зведення за товарами, обраними покупцем, і пропонує кнопки, які дають змогу повернутися до списку товарів або продовжити оформлення замовлення. Щоб зібрати інформацію для створення замовлення, додамо в папку Views/Cart файл на ім’я CreateOrder.cshtml з таким вмістом:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
@model Order @{ Layout = "~/Views/Store/_Layout.cshtml"; } <h2 class="m-3">Общая информация</h2> <form asp-action="CreateOrder" method="post" class="m-4"> <div class="form-group"> <label>Ваше имя:</label> <input asp-for="CustomerName" class="form-control" /> </div> <div class="form-group"> <label>Ваше адрес:</label> <input asp-for="Address" class="form-control" /> </div> <div class="form-group"> <label>Ваша область:</label> <input asp-for="State" class="form-control" /> </div> <div class="form-group"> <label>Ваш почтовый индекс:</label> <input asp-for="ZipCode" class="form-control" /> </div> <div class="text-center m-2"> <button type="submit" class="btn btn-primary">Оформить заказ</button> <a asp-action="Index" class="btn btn-secondary">Отмена</a> </div> </form> |
Для відображення користувачеві повідомлення про те, що замовлення створено, додамо в папку Views/Cart файл на ім’я Completed.cshtml з таким вмістом:
1 2 3 4 5 6 7 8 9 |
@{ Layout = "~/Views/Store/_Layout.cshtml"; } <div class="text-center m-4"> <h2>Спасибо!</h2> <p>Спасибо за заказ!</p> <p>Мы свяжимся с Вами в ближайшее время.</p> <a asp-action="Index" asp-controller="Store" class="btn btn-primary">Ок</a> </div> |
Щоб створити подання для віджета (графічного елемента) зі зведенням за кошиком, створимо папку Views / Shared / Components / Cart і додамо в неї файл на ім’я Default.cshtml з таким вмістом:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
@model Cart @if (Model?.Selections?.Count() > 0) { <div> @Model.Selections.Count() товаров, $@(Model.Selections.Sum(e=>e.Quantity * e.Product.RetailPrice).ToString("f2")) </div> if (ViewContext.RouteData.Values["controller"] as string != "Cart") { <a asp-action="Index" asp-controller="Cart" class="btn btn-sm btn-light">В корзину</a> } } |
Подання Default відображає кількість елементів у кошику та їхню загальну вартість. Також є кнопка, яка перемістить на контролер Cart, якщо подання було візуалізовано іншим контролером. Щоб відобразити віджет кошика, за допомогою методу Component.InvokeAsync() додамо компонент подання до компонування, використовуваного для функціональних засобів магазину Views/Store/__Layout.cshtml:
1 2 3 |
<div class="col-auto text-right"> @await Component.InvokeAsync("Cart", Context.Session) </div> |
Запустимо додаток і виконаємо тестування. Для цього виберіть один або кілька товарів і додайте їх у кошик.
Після цього перейдіть у кошик, перегляньте додані товари, виконайте видалення будь-якого з них.
Нажмите оформить заказ, заполните данные на форме:
Нажмите кнопку “Оформить заказ”.
Підсумок
У розділі робота над додатком GameStore продовжилася додаванням засобів інтерфейсу для покупців. Було створено список товарів, який користувач може гортати, шукати в ньому товари або фільтрувати їх за категоріями. Обрані товари додаються в кошик, який потім може застосовуватися для оформлення замовлення, що зберігається в БД. Більшість функціональних засобів, доданих у розділі, використовують інфраструктуру МVС для створення надбудови над фундаментом Entity Framework Сare, утвореного в попередніх розділах. Такий шаблон ви бачитимете і у власних проєктах – великий обсяг початкової конфігурації моделі даних і коду, а потім набір засобів користувацького інтерфейсу, що швидко стають на свої місця. У наступному розділі проєкт додатка GameStore завершується створенням веб-служби REST.
На цьому стаття “Магазин на Asp.Net Core MVC EF”, підійшла до кінця, сподіваюся вам було цікаво. Ви можете завантажити вихідний код у моєму репозиторії — Github.
Поділіться вашим досвідом у коментарях, який був ваш перший проєкт, магазин на Asp.Net Core MVC EF або щось інше?
Так само вам може бути цікава попередня стаття:
Ви хочете навчитися писати код мовою програмування C#?
Створювати різні інформаційні системи, що складаються з сайтів, мобільних клієнтів, десктопних додатків, телеграм-ботів тощо.
Переходьте до нас на сторінку Dijix і ознайомтеся з умовами навчання, ми спеціалізуємося тільки на індивідуальних заняттях, як для початківців, так і для просунутих програмістів. Ви можете взяти як одне заняття для опрацювання питання, що вас цікавить, так і кілька, для більш щільної роботи. Завдяки особистому кабінету, кожен студент підвищить якість свого навчання, у вашому розпорядженні:
- Доступ до пройденого матеріалу
- Тематичні статті
- Бібліотека книг
- Онлайн тестування
- Спілкування в закритих групах