Demo App One Plus One Software Logo
Presentation 02

The business logic
BusinessLogicLayer

The entities, the settings and the shared tools — all of it without ever knowing that a browser exists.

What is this layer for?

  • This is where the entities live (Car, Student…) along with their business rules.
  • It inherits from Record in the data layer — so every entity already knows how to save and load itself.
  • It must not know anything about UI or HTTP: no HttpContext, no HTML, no Session.
  • That is exactly why the same BLL can serve a website, a MAUI application, a nightly Job and the tests.
Web / MAUI / Job UI (generic) BLL DAL + DB

A business entity is a small class

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; }
}
  • Fields = columns. Nothing beyond that.
  • Business logic (calculating an average, validating an ID number) is added as methods on that same class.
  • A link to another entity? A property of that entity's type — stored automatically as a BIGINT holding its ID.

Settings — configuration that lives in the DB

// Reading (cached in memory, loaded once when the application starts):
string url = Settings.Val["MeshulamUrl"];

// Writing:
await Settings.SetAsync("MeshulamUrl", "https://...");

// Seeding the defaults — every project overrides CreateAsync:
public override async Task CreateAsync()
{
    await base.CreateAsync();
    await AddAsync("MeshulamUrl", "Payment gateway URL", "https://sandbox...");
}
  • Why the DB and not appsettings.json? Because it can be changed at run time, per customer, with no deployment.
  • The seeding is idempotent — it runs on every startup and adds only the keys that are missing.

Util — the shared toolbox

  • Dates: DateString, ToDate, Hebrew and Gregorian months, Decode(hebrewYear)=776.
  • Validation: IsValidTz (check digit), IsPhone, IsValidEmail.
  • Text: StripHTML, CleanHiddenChars, UrlEscape.
  • Files: Mime(), extension validation, Compress/Decompress (GZip).
  • Security: ProtectAppState — encrypts the application state that is sent down to the client.
  • Network: SendHttpRequestAsync over a shared HttpClient.

reuse-first: before writing a new helper — make sure it is not already here.

Document export — Hebrew included

  • A single base class: Exporter — it returns byte[]; the controller wraps that in File(...).
  • Excel (NPOI) — a genuine RTL sheet; Word (OpenXML) — BiDi tables.
  • PDF (MigraDoc) — the font is embedded in the DLL, and FlipHebrew arranges Hebrew visually.
Exporter exporter = new ExcelExporter("Students.xlsx", rtl: true);
exporter.AddTable(dataTable);
return File(exporter.GetBytes(), exporter.ContentType, exporter.FileName);

The golden rules of this layer

  • Asynchronous all the way — a method that touches the DB returns a Task.
  • No dependency on the UI — anything that needs environment data receives it as a parameter.
  • Lean entities; shared logic moves up into Util or into a base class.
  • Settings belong in the DB, not in configuration files.
  • Whatever is right for every project gets fixed here first, in the template.