{:} RenderTemplate
Açık kaynak · .NET · MIT Lisansı

Basit, hızlı ve deterministik
bir şablon motoru.

DSO.Core.RenderTemplate, POCO nesnelerinizi ve koleksiyonlarınızı; Razor gibi ağır bir view katmanı kurmadan, öngörülebilir sırayla HTML veya düz metne dönüştüren hafif bir .NET kütüphanesidir. Embed kompozisyonu, pipe tabanlı filtre zinciri, derlenmiş dotted-path erişimi ve varsayılan HTML kaçışı ile üretime hazır çıktı üretir.

Lisans · MIT Dil · C# 10+ Bağımlılık · Yok Thread-safety · Örnek başına değil

Bir veri tokenının anatomisi

{:Price|currency:tr-TR}
{ … }Token sınırlayıcıları
:Veri tokenı işareti (embed'den ayırır)
PriceKolon adı / dotted path
currencyUygulanan filtre
tr-TRFiltre argümanı (kültür kodu)

Bölüm 01

Amaç & Kapsam

Bu bölüm, kütüphanenin neden var olduğunu, hangi problemi çözmeyi hedeflediğini ve sınırlarının nerede başlayıp nerede bittiğini tüm detaylarıyla açıklar.

Amaç

DSO.Core.RenderTemplate; POCO nesneleri veya nesne koleksiyonlarını, tam bir view engine kurmadan hızlıca HTML tablosuna, e-posta gövdesine, rapor metnine veya düz metin çıktıya dönüştürmek amacıyla tasarlanmıştır. Kütüphanenin tasarım felsefesi beş temel hedef üzerine kuruludur:

  • Öngörülebilirlik: Aynı şablon ve aynı veri her zaman aynı çıktıyı üretir; embed'ler eklenme sırasına göre çözülür, veri tokenları akış sırasında yazılır, döngüler cycle guard ile engellenir.
  • Performans: Dotted-path erişimleri şablon seti başına bir kez System.Linq.Expressions ile derlenir; satır başına reflection maliyeti oluşmaz. Bu, yüzlerce/binlerce satırlık tabloların render edilmesi gereken raporlama senaryolarında ölçülebilir fark yaratır.
  • Hafiflik: Harici hiçbir pakete bağımlı değildir. Razor, Handlebars.NET veya Scriban gibi tam teşekküllü template engine'lerin getirdiği derleme adımı, view lokasyonu yönetimi veya sandbox karmaşıklığı yoktur.
  • Güvenli varsayılanlar: Tüm veri tokenları varsayılan olarak HTML olarak kaçışlanır; geliştirici bilinçli olarak |raw demeden ham HTML enjekte edilemez. Bu, kullanıcı girdisinden türeyen verilerin şablonlara basılması sırasında XSS riskini azaltır.
  • Genişletilebilirlik: IFilter arayüzü ile yeni filtreler kayıt edilebilir; eksik şablon/kolon davranışı politika (Throw / KeepToken / Empty) ile yapılandırılabilir.

Kısacası kütüphane, "POCO listesini bir HTML tablosuna dök" ya da "bu nesneden düz metin bir e-posta gövdesi üret" gibi tekrar eden ihtiyaçları, Razor taşımadan, minimum yüzey alanıyla çözmeyi hedefler.

Hedef Kitle & Tipik Senaryolar

Kütüphane özellikle şu profildeki geliştiriciler ve senaryolar için uygundur:

  • Sunucu tarafında POCO/DTO listelerinden HTML rapor veya tablo üreten backend servisleri.
  • Zamanlanmış görevlerde (job/worker) e-posta gövdesi veya bildirim metni oluşturan sistemler.
  • PDF üretiminden önce ara HTML çıktısı hazırlayan pipeline'lar.
  • Log, dışa aktarma (export) veya şablon tabanlı metin üretimi yapan konsol/servis uygulamaları.
  • Razor view engine'inin ek yükünü (derleme, view lokasyon çözümleme) istemeyen mikroservisler.

Kapsam içinde

  • Embed (kısmi şablon) kaydı ve çözümleme
  • Veri tokenı okuma: {:Kolon} ve dotted-path {:A.B.C}
  • Pipe tabanlı, zincirlenebilir filtre sistemi ve yerleşik filtreler
  • Kültüre duyarlı tarih / sayı / para birimi biçimlendirme
  • Eksik şablon/kolon için yapılandırılabilir politika
  • Tekli nesne ve koleksiyon render kalıpları
  • RenderTemplateQuick ile tek satırlık kullanım API'si
  • Varsayılan HTML kaçışı ve kontrollü bypass (raw, nl2br)

Kapsam dışında

  • Şablon içinde koşul/döngü sözdizimi (if/for gibi) — döngüler C# tarafında, AddTemplateForEach ile yönetilir
  • Literal { / } kaçışı (yol haritasında)
  • Bölüm/loop yardımcı sözdizimi (yol haritasında)
  • Engine örneğinin thread-safe olması — eşzamanlı işler için ayrı örnek gerekir
  • Sunucu tarafı görünüm (view) yönlendirme, layout/master page kavramı
  • Resmi bir NuGet paketi olarak yayınlanmış olmak (şu an için proje referansı/kaynak dahil önerilir)

Bölüm 02

Neden RenderTemplate?

Render süreci öngörülebilir, kodu ise hafif ve hızlı olmalıdır. Bu motor, ağır view katmanları yerine küçük, deterministik bir çekirdek kullanır:

  • İki geçişli render: önce embed (kısmi) çözülür, ardından veri tokenları akışta yazılır.
  • Dotted-path erişimleri için önceden derlenen expression accessor'lar — satır başına reflection yok.
  • Deterministik çıktı sırası ve embed'ler için döngü koruması (cycle guard).
  • Varsayılan HTML kaçışı ile güvenli çıktı; gerektiğinde raw filtresiyle bypass.

POCO listelerini tablolaştırmak ya da Razor taşımadan düz metin/HTML raporlar üretmek gerektiğinde idealdir.

Bölüm 03

Özellikler

Deterministik sıra

Eklenme sırasına göre render + cycle guard ile embed döngü koruması.

Embed kompozisyonu

{thead}, {tbody} gibi alt şablonları kısmi olarak birleştirin.

:

Data token

{:Name} ve filtre zinciri: {:Price|currency:tr-TR}.

Dotted path

{:User.Name} — ekleme anında derlenir, satır başına reflection yok.

Cycle guard

Şablon döngülerini yakalar ve net bir istisna fırlatır.

§

Politikalar

Eksik şablon/kolon için Throw, KeepToken, Empty.

Filtre altyapısı

upper, lower, trim, raw, date, number, currency, yesno, truncate, nl2br.

Bağımlılıksız

Harici pakete ihtiyaç duymadan hızlı çalışır.

Minimal sözdizimi

{Name} embed, {:Col|filters} veri.

🌍

Kültür duyarlılığı

Özelleştirilebilir varsayılan kültür ile biçimlendirme.

Özel filtreler

IFilter uygulayıp kendi filtrenizi kayıt edin.

Quick yardımcılar

Tek satırda render için RenderTemplateQuick API'si.

Bölüm 04

Kurulum

Projeyi referans olarak ekleyin veya kaynak dosyaları çözümünüze dahil edin.

using DSO.Core.RenderTemplate;

Not: NuGet paketi olarak henüz yayınlanmamıştır; şu an için proje referansı ya da kaynak dosyaların doğrudan çözüme dahil edilmesi önerilir.

Bölüm 05

Hızlı Başlangıç

var items = new[]
{
    new { Name = "Kahve", Price = 145.75m, InStock = true,  Added = new DateTime(2025, 9, 1) },
    new { Name = "Çay",   Price =  75.00m, InStock = false, Added = new DateTime(2025, 9, 2) },
};

string rowTpl = "<tr><td>{:Name|upper}</td><td>{:Price|currency:tr-TR}</td><td>{:InStock|yesno:Evet,Hayır}</td><td>{:Added|date:yyyy-MM-dd}</td></tr>";
string tableTpl = "<table class=\"tbl\">{thead}{rows}{tfoot}</table>"; // embed'ler

var eng = new RenderTemplate(new RenderTemplateSettings { HtmlEncodeByDefault = true });

// Kısmi şablonlar / embeds
eng.AddTemplate("thead", "<thead><tr><th>Ürün</th><th>Fiyat</th><th>Stok</th><th>Eklendi</th></tr></thead>");
eng.AddTemplate("rows",  rowTpl);
eng.AddTemplate("tfoot", "<tfoot><tr><td colspan=\"4\">Toplam: {:Price|number:#,##0.00}</td></tr></tfoot>");

// Veri tokenı içeren şablona veriyi bağla
eng.AddTemplateForEach("rows", rowTpl, items);
eng.AddTemplate("__root__", tableTpl);

string html = eng.Render("__root__");

Çıktı (gösterim amacıyla biçimlendirilmiştir):

<table class="tbl">
  <thead>...</thead>
  <tr><td>KAHVE</td><td>₺145,75</td><td>Evet</td><td>2025-09-01</td></tr>
  <tr><td>ÇAY</td>  <td>₺75,00</td><td>Hayır</td><td>2025-09-02</td></tr>
  <tfoot>...</tfoot>
</table>

Bölüm 06

Şablon Sözdizimi

Şablon dili yalnızca iki token türü tanır: embed tokenları ve veri tokenları. Aşağıdaki alt bölümler her ikisini de detaylandırır.

Embed tokenları {Name}

Adıyla kayıt ettiğiniz başka bir şablonu (partial) referanslar:

{Header}
<ul>
  {Rows}
</ul>
{Footer}
  • Eksik embed davranışı RenderTemplateSettings.OnMissingTemplate politikasına bağlıdır.
  • Embed çözümü veri tokenlarından önce yapılır (bkz. Performans Notları).

Veri tokenları {:Col|f|f:arg}

Geçerli satır/nesneden bir kolon/özellik okur ve isteğe bağlı pipe filtreleri uygular:

{:Name}
{:Price|currency:tr-TR}
{:CreatedAt|date:yyyy-MM-dd}
{:Comment|trim|truncate(40, ...)}
{:RawHtml|raw}
  • Kolon adı ilk : sonrası kısımdır.
  • Filtre argümanları : veya ( ) ile verilebilir: date:yyyy ya da date(yyyy).
  • Geçersiz kolonlar OnMissingColumn politikasına uyar.

Dotted path {:Customer.Address.City}

İç içe özelliklere satır başına reflection yapmadan erişim sağlar. Motor, şablondan dotted path'leri çıkarır ve bir kez expression ile accessor derler:

var order = new {
  Id = 42,
  Customer = new { Name = "Ada", Address = new { City = "İzmir", Country = "TR" } },
};

string tpl = "Sipariş {:Id} — Müşteri {:Customer.Name} ({:Customer.Address.City})";
var result = RenderTemplateQuick.RenderOne(tpl, order);
// => "Sipariş 42 — Müşteri Ada (İzmir)"

Dotted path hatalı biçimlendirilmişse veya derlenemezse eksik kolon politikası devreye girer.

Filtreler (yerleşikler)

FiltreAçıklama
upperMetni büyük harfe çevirir.
lowerMetni küçük harfe çevirir.
trimBaştaki/sondaki boşlukları temizler.
rawHTML kaçışını bu token için devre dışı bırakır.
dateTarihi belirtilen formatla biçimlendirir, örn. date:yyyy-MM-dd.
numberSayıyı belirtilen sayı formatıyla biçimlendirir.
currencyPara birimi olarak biçimlendirir, kültür argümanı alır.
yesnoBoolean değeri özelleştirilebilir metinlere çevirir, örn. yesno:Evet,Hayır.
truncateMetni belirli uzunlukta keser ve isteğe bağlı sonek ekler.
nl2brSatır sonlarını <br/> etiketine çevirir; önce encode eder.

Kendi filtrenizi de yazabilirsiniz; bkz. Özel Filtreler Yazma.

Bölüm 07

Kullanım Kalıpları

Tek nesne

var settings = new RenderTemplateSettings { DefaultCulture = CultureInfo.GetCultureInfo("tr-TR") };
string tpl = "Merhaba {:Name}, bakiyeniz {:Balance|currency}.";
var data = new { Name = "Ethem", Balance = 1234.5m };

string text = RenderTemplateQuick.RenderOne(tpl, data, settings);

Koleksiyonlar

var rows = new []
{
    new { Name = "A", Score = 98.5 },
    new { Name = "B", Score = 76.3 },
};

string rowTpl = "<li>{:Name} — {:Score|number:#0.0}</li>";
string html = rows.RenderWith(rowTpl);

Kısmi/Embed kayıt etmek

string page = RenderTemplateQuick.RenderMany(
    template: "<ul>{Rows}</ul>",
    data: rows,
    setup: eng =>
    {
        eng.AddTemplate("Rows", "<li>{:Name}</li>");
        eng.AddTemplateForEach("Rows", "<li>{:Name}</li>", rows);
    });

Tek satır yardımcılar (Quick)

  • RenderTemplateQuick.RenderLiteral(template[, settings])
  • RenderTemplateQuick.RenderOne(template, data[, setup][, settings])
  • RenderTemplateQuick.RenderMany(template, data[, setup][, settings])
  • Enumerable extension: data.RenderWith(template[, setup][, settings])

Alt tarafta kısa ömürlü bir engine oluşturur ve __root__'u render eder.

Bölüm 08

Ayarlar & Politikalar

var settings = new RenderTemplateSettings
{
    OnMissingTemplate   = OnMissingTemplatePolicy.Throw,   // KeepToken / Empty
    OnMissingColumn     = OnMissingColumnPolicy.Throw,     // KeepToken / Empty
    DefaultCulture      = CultureInfo.InvariantCulture,
    NameComparer        = StringComparer.Ordinal,
    HtmlEncodeByDefault = true,
};
ThrowKeepTokenEmpty
AyarAçıklama
OnMissingTemplate{Embed} bulunamazsa ne yapılacağını belirler.
OnMissingColumnVeri kolonu/yolu okunamazsa ne yapılacağını belirler.
HtmlEncodeByDefault& < > " ' karakterlerini encode eder; bir filtre rawBypass işaretlerse atlanır (örn. raw, nl2br).
DefaultCulturedate/number/currency filtreleri için kullanılır.
NameComparerŞablon adı eşleşmesinin büyük/küçük harf duyarlılığını belirler.

Bölüm 09

Özel Filtreler Yazma

IFilter uygulayıp kayıt edin:

public sealed class SlugFilter : IFilter
{
    public string Name => "slug";
    public string Apply(string input, string? arg, CultureInfo culture, ref bool rawBypass)
    {
        var s = input ?? string.Empty;
        s = s.Trim().ToLower(culture);
        var sb = new StringBuilder(s.Length);
        foreach (var ch in s)
            sb.Append(char.IsLetterOrDigit(ch) ? ch : '-');
        return sb.ToString().Trim('-');
    }
}

var settings = new RenderTemplateSettings();
settings.Filters.Register(new SlugFilter());

Sonra {:Title|slug} olarak kullanın.

Bölüm 10

Performans Notları

  • İki geçişli hat: önce embed geçişi {Name} tokenlarını çözer ve düz bir şablon dizesi elde eder; ardından veri geçişi her satır için akışta yazar, sadece {:...} yerleri değiştirilir.
  • Dotted-path accessor'lar şablon seti başına bir kez System.Linq.Expressions ile derlenir ve satırlar arasında paylaşılır.
  • Dotted path için satır başına reflection yoktur; basit kolonlar hızlı tablo okuyucusu ile alınır.
  • Cycle guard ile embed döngülerine izin verilmez; net bir istisna fırlatılır.
  • Çıktı deterministiktir: embed ile içerden basılmış olanlar kökte tekrar basılmaz; ekleme sırası korunur.
  • Engine örneği thread-safe değildir; eşzamanlı işler için ayrı örnek oluşturun.

Bölüm 11

Güvenlik / HTML Kaçış

  • Varsayılan olarak her veri tokenı HTML-encode edilir (& < > " ').
  • Sadece ilgili tokenı atlamak için |raw kullanın.
  • nl2br önce encode eder, sonra satır sonlarını <br/> yapar ve dış encode'u bypass eder.

Bölüm 12

API Referansı

RenderTemplate

  • AddTemplate(string name, string template)
  • AddTemplateForEach<T>(string name, string template, IEnumerable<T>? data)
  • AddTemplateFor<T>(string name, string template, T data)
  • string Render(string? root = null)
  • void RenderTo(StringBuilder sb, string? root = null) / void RenderTo(TextWriter writer, string? root = null)
  • RenderTemplateSettings Settings { get; }

RenderTemplateSettings

OnMissingTemplate · OnMissingColumn · DefaultCulture · NameComparer · HtmlEncodeByDefault · FilterRegistry Filters

RenderTemplateQuick

  • RenderLiteral(string template[, Action<RenderTemplate> setup][, RenderTemplateSettings settings])
  • RenderOne<T>(string template, T data[, Action<RenderTemplate> setup][, RenderTemplateSettings settings])
  • RenderMany<T>(string template, IEnumerable<T> data[, Action<RenderTemplate> setup][, RenderTemplateSettings settings])

RenderTemplateQuickExtensions

  • string RenderWith<T>(this IEnumerable<T> data, string template[, Action<RenderTemplate> setup][, RenderTemplateSettings settings])

Bölüm 13

SSS

Literal içinde { veya } nasıl kaçarım?

Henüz literal kaçışı desteklenmiyor; isterseniz string'i bölün veya embed kullanın.

HTML encode olmadan render edebilir miyim?

Global olarak HtmlEncodeByDefault = false yapın ya da ilgili token için |raw / |nl2br kullanın.

StrongType / Free modu nasıl seçiliyor?

AddTemplateForEach<T> çağrısında T bir referans tipi (string hariç) ise tablo StrongType çalışır ve public field/property'leri kolon yapar. Değer tipleri/string için Free moda düşer ve tek kolon (0) kabul edilir.

Bölüm 14

Yol Haritası

Literal kaçışı{{ / }} ile literal süslü parantez desteği.
Bölüm/loop yardımcılarıŞablon sözdiziminde döngü/bölüm ifadeleri.
Daha güvenli null/format işlemeÖzel filtrelerde geliştirilmiş null ve format toleransı.

Bölüm 15

Lisans

Bu proje MIT Lisansı ile lisanslanmıştır. Ayrıntılar için depodaki LICENSE dosyasına bakın.