Демоприложение Один плюс один, разработка ПО Логотип
Презентация 02

Бизнес-логика
BusinessLogicLayer

Сущности, настройки и общие инструменты — и всё это, не подозревая о существовании браузера.

Зачем нужен этот слой?

  • Здесь живут сущности (Car, Student…) и их бизнес-правила.
  • Он наследует Record из слоя данных — поэтому каждая сущность уже умеет сохраняться и загружаться.
  • Ему запрещено знать про UI или HTTP: никакого HttpContext, никакого HTML, никакой Session.
  • Благодаря этому один и тот же BLL обслуживает сайт, приложение MAUI, ночной Job и тесты.
Web / MAUI / Job UI (общий) BLL DAL + DB

Бизнес-сущность = небольшой класс

public class Student : Record
{
    [Column(Size = 100)] public string FirstName { get; set; }
    [Column(Size = 100)] public string LastName  { get; set; }
    [Column(Size = 20)]  public string IdNumber  { get; set; }
    [Column] public DateTime BirthDate  { get; set; }
    [Column] public int    Grade        { get; set; }
    [Column] public double Average      { get; set; }
    [Column] public bool   IsActive     { get; set; }
    [Column(Size = 255)] public string Picture { get; set; }
}
  • Поля = столбцы. Ничего сверх этого.
  • Бизнес-логика (расчёт среднего балла, проверка удостоверения личности) добавляется методами того же класса.
  • Связь с другой сущностью? Свойство её типа — автоматически хранится как BIGINT с её ID.

Settings — конфигурация, которая живёт в базе данных

// чтение (кеш в памяти, загружается один раз при старте приложения):
string url = Settings.Val["MeshulamUrl"];

// запись:
await Settings.SetAsync("MeshulamUrl", "https://...");

// заполнение значений по умолчанию — каждый проект переопределяет CreateAsync:
public override async Task CreateAsync()
{
    await base.CreateAsync();
    await AddAsync("MeshulamUrl", "адрес платёжного шлюза", "https://sandbox...");
}
  • Почему база данных, а не appsettings.json? Потому что значения можно менять во время работы, для каждого клиента отдельно, без развёртывания.
  • Заполнение значений по умолчанию идемпотентно — выполняется при каждом старте и добавляет только недостающие ключи.

Util — общий набор инструментов

  • Даты: DateString, ToDate, еврейские и григорианские месяцы, Decode("еврейский год")=776.
  • Проверки: IsValidTz (контрольная цифра), IsPhone, IsValidEmail.
  • Текст: StripHTML, CleanHiddenChars, UrlEscape.
  • Файлы: Mime(), проверка расширений, Compress/Decompress (GZip).
  • Безопасность: ProtectAppState — шифрование состояния приложения, которое уходит клиенту.
  • Сеть: SendHttpRequestAsync с общим HttpClient.

reuse-first: прежде чем писать новую вспомогательную функцию — убедитесь, что её здесь ещё нет.

Экспорт документов — вместе с ивритом

  • Один базовый класс: Exporter — возвращает byte[]; контроллер оборачивает результат в File(...).
  • Excel (NPOI) — настоящий RTL-лист; Word (OpenXML) — таблицы с двунаправленным текстом.
  • PDF (MigraDoc) — шрифт, встроенный в DLL, и FlipHebrew, который раскладывает иврит визуально.
Exporter exporter = new ExcelExporter("Students.xlsx", rtl: true);
exporter.AddTable(dataTable);
return File(exporter.GetBytes(), exporter.ContentType, exporter.FileName);

Золотые правила слоя

  • Асинхронность до самого конца — метод, который трогает базу данных, возвращает Task.
  • Никакой зависимости от UI — всё, чему нужны данные окружения, получает их параметром.
  • Сущности остаются лёгкими; общая логика поднимается в Util или в базовый класс.
  • Settings — в базу данных, а не в файлы конфигурации.
  • То, что верно для всех проектов, исправляется сначала здесь, в шаблоне.