W poprzednim artykule zainstalowaliśmy oraz stworzyliśmy pierwszą aplikację webową z wykorzystaniem frameworka .NET Core. Obecnie zajmiemy się zaimplementowaniem prostej aplikacji CRUD pracującej na silniku MS SQL Server zainstalowanym w systemie Linux.
Tworzenie bazy danych
Zanim przejdziemy do właściwej implementacji, przygotujemy bazę danych MS SQL pod aplikację. Na początek logujemy się do serwera, korzystając z polecenia:
sqlcmd -S localhost -U sa
Następnie podajemy hasło administratora (identyczne, jakie wpisaliśmy przy konfiguracji serwera).
Kolejno tworzymy bazę danych i przechodzimy do utworzonej bazy poleceniami:
create database localshop; go use localshop; go
W następnym kroku tworzymy sql login oraz użytkownika poleceniami:
create login shopus with password 'Local123!@'; go create user shopus from login shopus; go
Na samym końcu nowo utworzonemu użytkownikowi nadajemy uprawnienia dostępu do naszej bazy danych:
exec sp_addrolemember 'db_owner', 'shopus'; go
Mamy utworzoną bazę danych i użytkownika, poprzez którego będziemy łączyć się z bazą i wykonywać różne operacje na danych w tabelach.
Naszym celem jest implementacja prostej aplikacji webowej do zarządzania magazynem małego osiedlowego sklepu. Zatem będzie to prosta aplikacja CRUD (Create, Read, Update, Delete – podstawowe operacje wykonywane na tabelach bazy danych).
Krok I (utworzenie connectionString)
Otwieramy plik appsettings.json i wpisujemy:
"ConnectionStrings": { "DefaultConnection": "Server=tcp:localhost,1433;Database=localshop;User Id=shopus; Password = Local123!@" },
Nasz connectionString pozwala na podłączenie się do lokalnego serwera MS SQL nasłuchującego na standardowym porcie TCP 1433 do bazy danych o nazwie localshop, korzystając z użytkownika shopus o podanym haśle do logowania.
Krok II (utworzenie klas modelu)
Tworzymy katalog Models w głównym folderze projektu. Models będzie zawierał wszystkie klasy modelu aplikacji. Następnie tworzymy plik Product.cs (klasę opisującą pojedynczy produkt w sklepie) i implementujemy klasę, korzystając z właściwości:
public class Product { public int ID { get; set; } // ID produktu public string Name { get; set; } // nazwa public string EAN { get; set; } // kod kreskowy public double Price { get; set; } // cena jednostkowa public string Unit { get; set; } // podstawowa jednostka public int Availability { get; set; } // stan magazynowy }
Krok III (utworzenie klasy Database Context)
Tworzymy nowy katalog Data w folderze głównym aplikacji, a w nowym katalogu tworzymy plik ProductContext.cs, który będzie zapewniał interakcję aplikacji z danymi w bazie danych:
using ProductsAspNetCore.Models; using Microsoft.EntityFrameworkCore; public class ProductContext: DbContext { public ProductContext(DbContextOptions<ProductContext> options): base(options) { } public DbSet<Product> Products { get; set; } } }
W tym momencie Visual Studio Code wyświetli błąd o braku dostępnych bibliotek do rozpoznania importowanej przestrzeni nazw EntityFrameworkCore. Musimy przejść do pliku ProductsAspNetCore.csproj i w sekcji dodać wpis:
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="1.1.1" />
Następnie wywołujemy polecenie dotnet restore, które pobierze i zainstaluje brakujące paczki w naszym projekcie.
Krok IV (rejestracja ProductContext jako usługi w aplikacji)
Otwieramy plik Startup.cs i w metodzie ConfigureServices, przed services.AddMvc(); dodajemy wpis:
services.AddDbContext<ProductContext>(options=>options.UseSqlServer( Configuration.GetConnectionString("DefaultConnection")) );
Na początku pliku zaś dodajemy deklarację użycia:
using ProductsAspNetCore.Data; using Microsoft.EntityFrameworkCore;
Krok V (implementacja klasy inicjalizującej bazę danych przykładowymi wartościami)
Tworzymy plik DbInitialize.cs w katalogu Data, który zainicjalizuje tabele bazy danych przykładowymi wartościami w trakcie pierwszego uruchomienia aplikacji. Tworzymy statyczną klasę DbInitialize wraz ze statyczną metodą Initialize:
public static class DbInitialize { public static void Initialize(ProductContext context) { context.Database.EnsureCreated(); if(context.Products.Any()){ // sprawdzenie, czy baza zawiera jakiekolwiek dane // wyjście z funkcji w przypadku wykrycia już danych w bazie } var products=new List<Product>(); // lista przykładowych danych (produktów) products.Add(new Product{ Name="Chleb zwykły", EAN="569856325698", Price=2.65, Unit="sztuka", Availability=15 }); products.Add(new Product{ Name="Bułka codzienna", EAN="653789512364", Price=0.35, Unit="sztuka", Availability=250 }); products.Add(new Product{ Name="Mleko wiejskie", EAN="986321476589", Price=4.98, Unit="litr", Availability=30 }); foreach(Product p in products) { context.Products.Add(p); } context.SaveChanges(); // zapis przykładowych danych do bazy } }
Krok VI (wywołanie metody inicjalizującej bazę danych)
Otwieramy ponownie plik Startup.cs i w nagłówku metody Configure dodajemy nowy parametr: ProductContext context. Natomiast na końcu dopisujemy wywołanie metody z kroku V:
DbInitialize.Initialize(context);
Krok VII (uruchomienie aplikacji)
Uruchamiamy aplikację poleceniem dotnet run. Logujemy się do bazy danych MS SQL i sprawdzamy, czy przykładowe dane zostały wpisane do bazy.
Jak widać, wszystko przebiegło pomyślnie.
Krok VIII (wyświetlenie produktów z bazy danych)
W folderze Controllers tworzymy nowy plik o nazwie ProductsController.cs, który będzie zawierał klasę kontrolera obsługującą wszystkie żądania HTTP do aplikacji odnośnie produktów. W pliku implementujemy klasę dziedziczącą z klasy Controller:
public class ProductsController : Controller { }
Następnie do klasy dodajemy prywatne pole typu ProductContext wraz konstruktorem klasy inicjalizującym pole poprzez mechanizm wstrzykiwania zależności (ang. Dependency Injection):
private readonly ProductContext context; public ProductsController(ProductContext _context) { context=_context; }
Aby wyświetlić listę produktów, musimy dodać metodę zwracającą widok wypełniony danymi z bazy danych:
public IActionResult Index() { return View(context.Products.ToList()); // zwraca widok wraz z listą wszystkich produktów }
Na samym końcu musimy stworzyć widok, który pozwoli wyświetlić dane zwrócone w powyższej metodzie. W katalogu Views tworzymy nowy folder o identycznej nazwie jak nasz kontroler, tzn. Products. W nowym katalogu tworzymy plik o nazwie Index.cshtml (nazwa pliku widoku musi być identyczna z nazwą metody zwracającej dany widok). Kolejno do pliku Index.cshtml wpisujemy:
@model IEnumerable<ProductsAspNetCore.Models.Product> // model danych wykorzystanych w widoku @{ ViewData["Title"]="Lista produktów"; // tytuł strony } <table class="table"> <thead> <tr> <th> @Html.DisplayNameFor(model => model.ID) // nagłówek kolumny </th> <th> @Html.DisplayNameFor(model => model.Name) </th> ............................................ </tr> </thead> <tbody> @foreach (var item in Model) { // pętla przechodząca po wszystkich elementach listy produktów <tr> <td> @Html.DisplayFor(modelItem => item.ID) // dane wyświetlane z bazy, ID produktu </td> <td> @Html.DisplayFor(modelItem => item.Name) // nazwa produktu </td> ........................................... </tr> } </tbody> </table>
Przechodzimy do przeglądarki, wpisujemy adres http://localhost:5000/Products i otrzymujemy listę wszystkich produktów.
Jak widać, nazwy kolumn są identyczne z nazwami pól klasy modelu Product. Jednak można zdefiniować własne wyświetlane nazwy, korzystając z DataAnnotations. Przechodzimy do klasy Product i dodajemy na początku pliku przestrzenie nazw odpowiedzialne za dodatkowe atrybuty pól:
using System.ComponentModel; using System.ComponentModel.DataAnnotations;
Kolejno przy każdym polu dodajemy sekcję [DisplayName(„”)], np.:
[DisplayName("Kod kreskowy EAN")] public string EAN { get; set; }
Po zdefiniowaniu własnych nazw kolumn tabela wyświetla się następująco:
Krok IX (dodawanie nowego produktu)
Na początku w pliku głównym widoku Index.cshtml dodajemy link kierujący do formularza definiowania nowego produktu:
<a asp-action="Create">Dodaj nowy produkt</a>
Tag asp-action odpowiada za wskazanie metody, która będzie wywołana z kontrolera Products po kliknięciu w link.
Kolejno w katalogu Products tworzymy widok wyświetlający formularz dodawania produktu (plik Create.cshtml). W pliku definiujemy formularz:
@model ProductsAspNetCore.Models.Product // model danych <form asp-controller="Products" asp-action="Create" method="POST"> Nazwa: <input asp-for="Name" /> // pole tekstowe o nazwie Name Kod kreskowy: <input asp-for="EAN" /> // pole tekstowe o nazwie EAN Cena jednostkowa: <input asp-for="Price" /> // pole tekstowe o nazwie Price ........................................................ <input type="submit" class="btn btn-primary" value="Dodaj produkt" /> // przycisk formularza </form>
Tag asp-controller wskazuje, do którego kontrolera odnosi się dany formularz. Natomiast asp-action — która metoda ma być wywoływana z kontrolera w trakcie przetwarzania formularza.
Następnie przechodzimy do kontrolera ProductsController i definiujemy dwie metody:
- wyświetlenie formularza:
public IActionResult Create() { return View(); }
- obsługa formularza:
[HttpPost] // wskazuje, że dana akcja obsługunt Availability { get; set5dania POST public IActionResult Create(Product product) // jako parametr otrzymuje obiekt zwrócony z formularza { context.Products.Add(product); // dodanie produktu z danymi z widoku do Product DbSet context.SaveChanges(); // zapisanie produktu w bazie danych return RedirectToAction("Index"); // przekierowanie do widoku Index }
Krok X (edycja wybranego towaru)
Na początku w pliku głównym widoku Index.cshtml dodajemy link do formularza edycji wybranego produktu:
<td> <a asp-action="Edit" asp-route-id="@item.ID">Edycja</a> </td>
Z kolei w katalogu Products tworzymy widok wyświetlający formularz edycji wybranego produktu (plik Edit.cshtml). W pliku definiujemy formularz:
@model ProductsAspNetCore.Models.Product // model danych <form asp-controller="Products" asp-action="Edit" method="POST" class="form-horizontal"> Nazwa: <input asp-for="Name" /> Kod kreskowy: <input asp-for="EAN" /> Cena jednostkowa: <input asp-for="Price" /> Jednostka: <input asp-for="Unit" /> Stan magazynowy: <input asp-for="Availability" /> <input type="submit" class="btn btn-primary" value="Aktualizuj produkt" /> </form>
Formularz będzie obsługiwany przez akcję Edit z kontrolera Products (opisane w tagach asp-controller i asp-action).
Następnie przechodzimy do kontrolera ProductsController i definiujemy dwie metody:
- wyświetlenie formularza edycyjnego:
public IActionResult Edit(int id) { Product product=context.Products.Find(id); // wyszukiwanie produktu o podanym ID return View(product); // zwrócenie wyszukanego produktu do widoku }
- obsługa formularza, aktualizacja danych produktu:
[HttpPost] // wskazuje, że dana akcja obsługuje przychodzące żądania POST public IActionResult Edit(Product product) // jako parametr pobiera obiekt zwrócony z formularza { context.Update(product); // aktualizacja danych produktu w Product DbSet context.SaveChanges(); // zapisanie zmian do bazy danych return RedirectToAction("Index"); // przekierowanie do głównego widoku Index }
Krok XI (usuwanie wybranego towaru)
W pliku głównego widoku Index.cshtml dodajemy link pozwalający usunąć wybrany produkt:
<td> ..................................... <a asp-action="Delete" asp-route-id="@item.ID">Usuń</a> </td>
Kolejno w kontrolerze ProductsController tworzymy metodę usuwającą wybrany produkt:
public IActionResult Delete(int id) { Product product=context.Products.Find(id); // wyszukanie produktu o zadanym ID context.Remove(product); // usunięcie produktu z Product DbSet context.SaveChanges(); // zapisanie zmian do bazy danych return RedirectToAction("Index"); // przekierowanie do głównego widoku Index }
W ten oto sposób napisaliśmy prostą aplikację webową z wykorzystaniem ASP .NET Core, którą później można hostować w systemie Windows, Linux oraz Mac.
Kompletny projekt możecie podejrzeć na GitHubie TUTAJ.
Krzysztof Goljasz
devgeek.pl