Getting started with ASP.NET Core
Під час розповіді я дам вам поради та рекомендації, щоб ви краще розуміли як працює наш технічний стек, і до кінця цього уроку у вас буде веб-додаток на ASP.NET Core. Це - ознайомча лекція, тому:
- деякі речі потрібно буде загуглити власноруч;
- можливо ви вже все це знаєте (повторити все одно не завадить).
Знайомство з .NET
Difficulty: Не хвилюйся. Objectives: Ознайомтеся з основами .NET, тому що ви станете друзями.Як Microsoft придумала те, що ми зараз називаємо .NET
Були часи, коли термін «.NET» означав лише платформу Windows. Ця платформа називається .NET Framework. Це наклало деякі обмеження на розгортання, оскільки більшість машин має Linux як операційну систему. Тож люди з Microsoft зібралися на зустріч і придумали .NET Core. Основною ідеєю фреймворку є кросплатформні програми, що означає, що ви можете розмістити свою програму на різних ОС. Починаючи з .NET 5, версія платформи стала називатися .NET (без використання «Core» у назві). Крім того, .NET є open-source, тому має велику підтримку спільноти.
.NET переваги
- Кросплатформенність
Працює на Windows, Linux, macOS - Гнучке розгортання
Фреймворк можна включити у вашу програму або встановити side-by-side user-або machine-wide - Інструменти командного рядка
.NET has great CLI, .NET має чудовий CLI, тому всі сценарії продукту можна виконувати за допомогою командного рядка - Сумісність
.NET сумісний із .NET Framework, Xamarin і Mono - Open-source
Платформа .NET має відкритий вихідний код і використовує ліцензії MIT і Apache 2. Ви можете зробити внесок у розвиток - Підтримується Microsoft
Величезна корпорація змушує .NET розвиватися та отримувати нові функції
Що можна створити за допомогою .NET
З чого потрібно почати
.NET SDK включає все необхідне для створення та запуску програм .NET. Оскільки ви не обмежені лише Windows, ви можете вибрати будь-яке IDE або текстовий редактор (Visual Studio, Visual Studio Code, JetBrains Rider, Sublime, Atom). Ви можете писати код за допомогою свого улюбленого інструменту та виконувати необхідні дії за допомогою CLI. Деякі корисні команди CLI:
dotnet new
— ініціалізує шаблон консольного проекту C#dotnet restore
— відновлює залежності програмиdotnet build
— створює програму .NETdotnet publish
— публікує портативну або самодостатню програму .NETdotnet run
— запускає програмуdotnet test
— запускає тести за допомогою програми виконання тестів, вказаної в project.jsondotnet pack
— створює пакет NuGet вашого коду
.NET Runtime містить ресурси, необхідні для запуску програм .NET (це середовище виконання включено до SDK)
Створення програми
Difficulty: Стає тепліше, потрібно друкувати. Objectives: Зрозуміти анатомію програми ASP.NET Core.Що таке ASP.NET Core [1] [2]
ASP.NET - популярний фреймворк веб-розробки для створення веб-програм на платформі .NET. ASP.NET Core — це версія ASP.NET із відкритим кодом, яка працює на macOS, Linux і Windows. Була уперше випущена у 2016 році та є оновленням попередніх версій ASP.NET лише для Windows. У порівнянні з ASP.NET, версія Core забезпечує:
- Чистішу та модульну архітектуру
- Посилену безпеку
- Покращену продуктивність
Навіщо використовувати ASP.NET Core
- Інтеграція сучасних клієнтських фреймворків і робочих процесів розробки
- Система конфігурації на основі хмарного середовища
- Вбудований dependency injection
- Новий легкий і модульний конвеєр запитів HTTP
- Створено на платформі .NET, яка підтримує паралельне керування версіями програми
- Поставляється повністю як пакети NuGet
- Нові інструменти, які спрощують сучасну веб-розробку
- Створюйте та запускайте кросплатформенні програми ASP.NET у Windows, Linux і macOS
- Відкритий вихідний код, орієнтований на спільноту
Створіть нову програму, запустивши dotnet new webapi
або використавши функції вашого IDE [1]
- Ось так виглядає точка входу:Тут ми можемо налаштувати сервіси, які використовує програма, і визначити, як програма буде оброблювати запити HTTP (ви можете налаштувати конвеєр, який оброблятиме запити).
public static void Main(string[] args) { var builder = WebApplication.CreateBuilder(args); // Add services to the container. builder.Services.AddControllers(); var app = builder.Build(); // Configure the HTTP request pipeline. app.UseHttpsRedirection(); app.UseAuthentication(); app.UseAuthorization(); app.MapControllers(); app.Run(); }
Контролери та сервіси
Difficulty: No kidding. Objectives: Learn that services are no joke and business logic shouldn't live in controllers.Я покажу вам, як написати API за допомогою ASP.NET Core Web API.
По-перше, давайте створимо просту програму, яка повертає всіх студентів Binary. Створіть нові файли під назвою BinaryStudentsController.cs у папці Controllers та BinaryStudent.cs, де хочете
using Microsoft.AspNetCore.Mvc; using BSA_ASP.NET.Business.Models; namespace BSA_ASP.NET.Controllers; [ApiController] [Route("[controller]")] public class BinaryStudentsController : ControllerBase { public BinaryStudentsController() { } [HttpGet] public IEnumerable<BinaryStudent> GetStudents() => new BinaryStudent[] { new BinaryStudent { Id = 1, FirstName = "Serhii", LastName = "Yanchuk", Age = 21 }, new BinaryStudent { Id = 2, FirstName = "Vadym", LastName = "Kolesnyk", Age = 21 } }; }
namespace BSA_ASP.NET.Business.Models; public class BinaryStudent { public int Id { get; set; } public string? FirstName { get; set; } public string? LastName { get; set; } public int Age { get; set; } }
Для запуску сервера можна використати команду
dotnet run
. Щоб переконатися, що наш API працює, ми повинні надіслати запит доBinaryStudentsController
. Відкрийте https://localhost:7088/binarystudents — ви повинні отримати студентів у форматі JSON (номер порту можна знайти у файлі launchSettings.json).Ви можете використовувати Postman для тестування свого API або, наприклад, використовувати розширення для VSCode ― REST ClientДавайте ще трохи попрактикуємось і ускладнимо наш додаток, додамо більше кінцевих точок (endpoints) і залежностей. Модифікований контролер буде повним CRUD (C - create, R - read, U - update, D - delete) контролером. Писати бізнес-логіку в контролерах – не найкраща ідея, тому було б краще, якби ми створили для цього окремий сервіс.
using BSA_ASP.NET.Business.Models; namespace BSA_ASP.NET.Business.Interfaces; public interface IBinaryStudentService { public List<BinaryStudent> Get(string? filter); public BinaryStudent GetById(int id); public BinaryStudent Add(BinaryStudent student); public BinaryStudent Update(BinaryStudent student); public void Delete(int id); }
using BSA_ASP.NET.Business.Interfaces; using BSA_ASP.NET.Business.Models; namespace BSA_ASP.NET.Business.Services; public class BinaryStudentService: IBinaryStudentService { public BinaryStudentService() { _students = new List<BinaryStudent> { new BinaryStudent { Id = 1, FirstName = "Serhii", LastName = "Yanchuk", Age = 21 }, new BinaryStudent { Id = 2, FirstName = "Vadym", LastName = "Kolesnyk", Age = 21 } }; } public List<BinaryStudent> Get(string? filter) => string.IsNullOrEmpty(filter) ? _students : _students.Where(s => s.LastName.Contains(filter, StringComparison.OrdinalIgnoreCase)).ToList(); public BinaryStudent GetById(int id) => _students.SingleOrDefault(s => s.Id == id); public BinaryStudent Add(BinaryStudent student) { student.Id = _students.Max(s => s.Id) + 1; _students.Add(student); return student; } public BinaryStudent Update(BinaryStudent student) { var studentToUpdate = GetById(student.Id); if (studentToUpdate is not null) { studentToUpdate.FirstName = student.FirstName; studentToUpdate.LastName = student.LastName; studentToUpdate.Age = student.Age; } return studentToUpdate; } public void Delete(int id) { _students = _students.Where(s => s.Id != id).ToList(); } private List<BinaryStudent> _students; }
using BSA_ASP.NET.Business.Interfaces; using BSA_ASP.NET.Business.Models; using Microsoft.AspNetCore.Mvc; namespace BSA_ASP.NET.Controllers; [ApiController] [Route("[controller]")] public class BinaryStudentsController : ControllerBase { public BinaryStudentsController(IBinaryStudentService studentService) { _studentService = studentService; } [HttpGet] // https://localhost:7088/binarystudents?filter=yan public IEnumerable<BinaryStudent> GetStudents([FromQuery] string? filter = default) => _studentService.Get(filter); [HttpGet("{id}")] // https://localhost:7088/binarystudents/1 public BinaryStudent GetStudent(int id) => _studentService.GetById(id); [HttpPost] // https://localhost:7088/binarystudents public BinaryStudent AddStudent([FromBody] BinaryStudent student) => _studentService.Add(student); [HttpPut] // https://localhost:7088/binarystudents public BinaryStudent UpdateStudent([FromBody] BinaryStudent student) => _studentService.Update(student); [HttpDelete("{id}")] // https://localhost:7088/binarystudents/3 public void DeleteStudent(int id) => _studentService.Delete(id); private IBinaryStudentService _studentService; }
- Нам потрібно зареєструвати наш сервіс. Докладніше про це в параграфі Dependency Injection.
using BSA_ASP.NET.Business.Interfaces; using BSA_ASP.NET.Business.Services; namespace BSA_ASP.NET; public class Program { public static void Main(string[] args) { var builder = WebApplication.CreateBuilder(args); // Add services to the container. ConfigureServices(builder.Services); var app = builder.Build(); // Configure the HTTP request pipeline. ConfigurePipeline(app); app.Run(); } private static void ConfigureServices(IServiceCollection services) { services.AddControllers(); services.AddSingleton<IBinaryStudentService, BinaryStudentService>(); } private static void ConfigurePipeline(WebApplication app) { app.UseHttpsRedirection(); app.UseAuthentication(); app.UseAuthorization(); app.MapControllers(); } }
Запустіть програму та перевірте три кінцеві точки, щоб переконатися, що все працює.
- https://localhost:7088/binarystudents?filter=yan - GET запит, отримання тих студентів, прізвища яких містять рядок фільтра;
- https://localhost:7088/binarystudents/1 - GET запит, пошук студента за ідентифікатором;
- https://localhost:7088/binarystudents - POST запит, додавання нового студента, якого ви визначаєте в тілі запиту у форматі JSON.
Додаткові функції
Difficulty: Objectives: Усувайте неполадки, налагоджуйте, розширюйте та краще підключайте свою програму.Короткий огляд Dependency Injection (Singleton, Scoped, Transient)
Як ми можемо прокинути залежності? Відповідь - це DI або Dependency injection.
Dependency injection (DI) — патерн проектування програмного забезпечення, який є технікою для досягнення Inversion of Control (IoC) між класами та їхніми залежностями. У нашому випадку BinaryStudentsController
залежить від BinaryStudentService
. Може бути випадок, коли ми захочемо використати іншу реалізацію. У цьому випадку нам потрібно буде пройти нашу програму та вручну змінити назву класу в усіх місцях, де він використовується. З DI ми можемо легко зробити це в одному місці (в Program.cs). Контейнер .NET DI бере на себе відповідальність за створення екземпляра залежності та видалення його, коли він більше не потрібен. Є кілька опцій тривалості життя сервісу:
- Singleton — сервіс створюється під час першого запиту (або коли екземпляр вказано під час реєстрації іншого сервісу при налаштуванні веб-програми). Кожен наступний запит використовує той самий екземпляр.
- Scoped — сервіс створюється один раз на запит клієнта (connection).
- Transient — сервіс створюється кожного разу, коли він запрошується з DI контейнеру. Найкраще підходить для легких сервісів без збереження стану.
Давайте застосуємо Singleton для нашого сервісу BinaryStudentService
, щоб використовувати ту саму колекцію студентів:
- Це можна зробити за допомогою IServiceCollection в Program.cs:
builder.Services.AddSingleton<BinaryStudentService>();
- Ми можемо створити інтерфейс для
BinaryStudentService
і прив’язати його до реалізації. Це дозволить нам змінювати реалізацію в одному місці.builder.Services.AddSingleton<IBinaryStudentService, BinaryStudentService>();
Middleware
Middleware - це програмне забезпечення, створюване у вигляді конвейєра (pipeline) додатків для обробки запитів і відповідей. Якщо коротко: програма застосовує всі middlewares однин за іншим для кожного запиту.
Наприклад, якщо ви хочете логувати всі запити, ви можете додати у конвеєр (у наведеному нижче прикладі для спрощення використовується Console.WriteLine
, ви можете використовувати будь-який фреймворк для логування). Отже, додайте наступний код до конфігурації конвеєра в Program.cs:
app.Use(async(context, next) =>
{
Console.WriteLine("Started handling request");
await next.Invoke();
Console.WriteLine("Finished handling request");
});
Маршрутизація
Маршрутизація (routing) відповідає за зіставлення URI запитів із селекторами кінцевих точок (endpoints) і відправлення вхідних запитів до кінцевих точок. Маршрути визначаються в програмі та налаштовуються під час запуску програми. Маршрут може додатково отримувати значення з URL-адреси, що міститься в запиті, і ці значення потім можна використовувати для обробки запиту. По суті, маршрутизація — це middleware. Дивлючись на зображення вище, уявіть як запит надходить на сервер, потім він обробляється ланцюгом middleware-ів, а потім middleware маршрутизації співставляє URL з URL-адресою контролеру та іменем метода. У WebAPI ми визначаємо маршрут за допомогою атрибута Route
:
[ApiController]
[Route("[controller]")]
public class BinaryStudentsController : ControllerBase
Minimal API
Minimal API - це новий спосіб створення API без великої кількості коду на основі контролера. Ви можете визначити кінцеві точки за допомогою цих методів розширення в конфігурації конвеєра:
- MapGet;
- MapPost;
- MapPut;
- MapDelete;
- MapMethods.
app.MapGet("/binarystudents", (IBinaryStudentService studentService) => studentService.Get(string.Empty));
Коди стану
Повертати тільки коди 200 або 500 звісно круто. Але є велика кількість інформативних кодів, тому чого їх не використовувати?
Одним із способів, щоб задавати конкретні коди стану при відправленні відповіді клієнту, є використання методів класу ControllerBase
, від якого наслідується створений нами контролер. Наприклад, наступні методи:
- Ok - 200;
- Created - 201;
- BadRequest - 400;
- NotFound - 404;
- Forbid - 403;
- StatusCode - будь-який код.
Для того, щоб використати їх, ми повинні обернути тип значення, що повертається, класом ActionResult
:
[HttpGet("{id}")] // https://localhost:7088/binarystudents/1
public ActionResult<BinaryStudent> GetStudent(int id) => Ok(_studentService.GetById(id));
[HttpPost] // https://localhost:7088/binarystudents
public ActionResult<BinaryStudent> AddStudent([FromBody] BinaryStudent student)
{
var addedStudent = _studentService.Add(student);
return Created($"~binarystudents/{addedStudent.Id}", addedStudent);
}