Getting started with ASP.NET Core

Translated into:
UA
EN

Сергій Янчук

Сергій працює в Binary Studio backend розробником. Він полюбляє кодити та закидувати триочкові в баскетболі. Тому, якщо ти з Києва та хочеш поєднати розмову про програмування з грою у баскетбол, звертайся до нього :D

Привіт! 👋
Під час розповіді я дам вам поради та рекомендації, щоб ви краще розуміли як працює наш технічний стек, і до кінця цього уроку у вас буде веб-додаток на ASP.NET Core. Це - ознайомча лекція, тому:
  1. деякі речі потрібно буде загуглити власноруч;
  2. можливо ви вже все це знаєте (повторити все одно не завадить).
Поїхали!
Level 1 back to top

Знайомство з .NET

Difficulty: Не хвилюйся. Objectives: Ознайомтеся з основами .NET, тому що ви станете друзями.
Як Microsoft придумала те, що ми зараз називаємо .NET

Були часи, коли термін «.NET» означав лише платформу Windows. Ця платформа називається .NET Framework. Це наклало деякі обмеження на розгортання, оскільки більшість машин має Linux як операційну систему. Тож люди з Microsoft зібралися на зустріч і придумали .NET Core. Основною ідеєю фреймворку є кросплатформні програми, що означає, що ви можете розмістити свою програму на різних ОС. Починаючи з .NET 5, версія платформи стала називатися .NET (без використання «Core» у назві). Крім того, .NET є open-source, тому має велику підтримку спільноти.

.NET переваги
  1. Кросплатформенність
    Працює на Windows, Linux, macOS
  2. Гнучке розгортання
    Фреймворк можна включити у вашу програму або встановити side-by-side user-або machine-wide
  3. Інструменти командного рядка
    .NET has great CLI, .NET має чудовий CLI, тому всі сценарії продукту можна виконувати за допомогою командного рядка
  4. Сумісність
    .NET сумісний із .NET Framework, Xamarin і Mono
  5. Open-source
    Платформа .NET має відкритий вихідний код і використовує ліцензії MIT і Apache 2. Ви можете зробити внесок у розвиток
  6. Підтримується Microsoft
    Величезна корпорація змушує .NET розвиватися та отримувати нові функції
Що можна створити за допомогою .NET
Веб-програми, які реалізують патерн Model-View-Controller і використовують Razor для розмітки HTML із фрагментами C#
Backend-програма, яка реалізує бізнес-логіку та може використовуватися будь-яким клієнтом (чи це Angular-, React-, або інший frontend фреймворк, mobile apps, тощо.)
З чого потрібно почати
  • .NET SDK включає все необхідне для створення та запуску програм .NET. Оскільки ви не обмежені лише Windows, ви можете вибрати будь-яке IDE або текстовий редактор (Visual Studio, Visual Studio Code, JetBrains Rider, Sublime, Atom). Ви можете писати код за допомогою свого улюбленого інструменту та виконувати необхідні дії за допомогою CLI. Деякі корисні команди CLI:

    • dotnet new — ініціалізує шаблон консольного проекту C#
    • dotnet restore — відновлює залежності програми
    • dotnet build — створює програму .NET
    • dotnet publish — публікує портативну або самодостатню програму .NET
    • dotnet run — запускає програму
    • dotnet test — запускає тести за допомогою програми виконання тестів, вказаної в project.json
    • dotnet pack — створює пакет NuGet вашого коду
  • .NET Runtime містить ресурси, необхідні для запуску програм .NET (це середовище виконання включено до SDK)

Level 2 back to top

Створення програми

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]
  • Ось так виглядає точка входу:
    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();
    }
    Тут ми можемо налаштувати сервіси, які використовує програма, і визначити, як програма буде оброблювати запити HTTP (ви можете налаштувати конвеєр, який оброблятиме запити).
Level 3 back to top

Контролери та сервіси

Difficulty: No kidding. Objectives: Learn that services are no joke and business logic shouldn't live in controllers.

Я покажу вам, як написати API за допомогою ASP.NET Core Web API.

  1. По-перше, давайте створимо просту програму, яка повертає всіх студентів 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; }
      }
  2. Для запуску сервера можна використати команду dotnet run. Щоб переконатися, що наш API працює, ми повинні надіслати запит до BinaryStudentsController. Відкрийте https://localhost:7088/binarystudents — ви повинні отримати студентів у форматі JSON (номер порту можна знайти у файлі launchSettings.json).

    Ви можете використовувати Postman для тестування свого API або, наприклад, використовувати розширення для VSCode ― REST Client
  3. Давайте ще трохи попрактикуємось і ускладнимо наш додаток, додамо більше кінцевих точок (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();
        }
      }
  4. Запустіть програму та перевірте три кінцеві точки, щоб переконатися, що все працює.

Level 4 back to top

Додаткові функції

Difficulty: Objectives: Усувайте неполадки, налагоджуйте, розширюйте та краще підключайте свою програму.
Короткий огляд Dependency Injection (Singleton, Scoped, Transient)

Як ми можемо прокинути залежності? Відповідь - це DI або Dependency injection. Dependency injection (DI) — патерн проектування програмного забезпечення, який є технікою для досягнення Inversion of Control (IoC) між класами та їхніми залежностями. У нашому випадку BinaryStudentsController залежить від BinaryStudentService. Може бути випадок, коли ми захочемо використати іншу реалізацію. У цьому випадку нам потрібно буде пройти нашу програму та вручну змінити назву класу в усіх місцях, де він використовується. З DI ми можемо легко зробити це в одному місці (в Program.cs). Контейнер .NET DI бере на себе відповідальність за створення екземпляра залежності та видалення його, коли він більше не потрібен. Є кілька опцій тривалості життя сервісу:

  1. Singleton — сервіс створюється під час першого запиту (або коли екземпляр вказано під час реєстрації іншого сервісу при налаштуванні веб-програми). Кожен наступний запит використовує той самий екземпляр.
  2. Scoped — сервіс створюється один раз на запит клієнта (connection).
  3. Transient — сервіс створюється кожного разу, коли він запрошується з DI контейнеру. Найкраще підходить для легких сервісів без збереження стану.

Давайте застосуємо Singleton для нашого сервісу BinaryStudentService, щоб використовувати ту саму колекцію студентів:

  1. Це можна зробити за допомогою IServiceCollection в Program.cs:
    builder.Services.AddSingleton<BinaryStudentService>();
  2. Ми можемо створити інтерфейс для BinaryStudentService і прив’язати його до реалізації. Це дозволить нам змінювати реалізацію в одному місці.
    builder.Services.AddSingleton<IBinaryStudentService, BinaryStudentService>();
Middleware

Middleware - це програмне забезпечення, створюване у вигляді конвейєра (pipeline) додатків для обробки запитів і відповідей. Якщо коротко: програма застосовує всі middlewares однин за іншим для кожного запиту.

php-fpm_nginx

Наприклад, якщо ви хочете логувати всі запити, ви можете додати у конвеєр (у наведеному нижче прикладі для спрощення використовується 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
Ви можете прописати конкретний рядок або використати плейсхолдер **[controller]**. І якщо ви дотримуєтеся угоди про іменування, тобто ім’я вашого класу має префікс **"Controller"** у кінці, тоді замість плейсхолдера буде підставлено ім’я класу без префікса Існує той самий атрибут для методів у контролері.
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);
}

― That's all, folks! 🐷

Ось так коротко про ASP.NET Core. На цьому все, дуже дякую за увагу🎩! Ставте питання та залишайте відгуки на лекцію на сайті академії. Успіхів з домашкою, скоро побачимося, бувай!