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
Recordin 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.
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
BIGINTholding 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:
SendHttpRequestAsyncover 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 returnsbyte[]; the controller wraps that inFile(...). - Excel (NPOI) — a genuine RTL sheet; Word (OpenXML) — BiDi tables.
- PDF (MigraDoc) — the font is embedded in the DLL, and
FlipHebrewarranges 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
Utilor into a base class. Settingsbelong in the DB, not in configuration files.- Whatever is right for every project gets fixed here first, in the template.