# .NET / ABP Kod Standardı Bu doküman `api/` altındaki tüm projeler için bağlayıcıdır. Platform davranışına dair kurallar için `ai.instructions.md` esastır; çelişki olursa o dosya kazanır. Standart derlemede zorlanır: `api/Directory.Build.props` analizörleri açar, `api/.editorconfig` kural şiddetlerini tanımlar. Bu iki dosya standardın tek kaynağıdır; proje dosyalarında tekrar edilmez. ## 1. Hedef sürümler | Katman | Sürüm | Nerede tanımlı | | --- | --- | --- | | SDK | .NET 10 (`10.0.300`) | `api/global.json` | | TargetFramework | `net10.0` | proje dosyaları | | Dil | C# 14 (`LangVersion=latest`) | `api/common.props` | | ABP | 10.0.0 | `Volo.Abp.*` paket referansları | Sürüm yükseltmesi tek noktadan yapılır: `global.json` + `common.props` + paket sürümleri. Proje dosyalarına TFM/LangVersion tekrar yazılmaz. ## 2. Nullable `Directory.Build.props` içinde **çözüm geneli `Nullable` ayarı yapılmaz.** Sebebi EF Core: kolonun zorunlu olup olmadığını, entity assembly'sinin nullable annotation bağlamından türetir. Global bir `enable`/`annotations` anahtarı, bir sonraki `dotnet ef migrations add` çağrısında tüm `string` özelliklerini sessizce NOT NULL kolona çevirir — seeder'ın doldurmadığı her alan da o anda kırılır. Kural: - Nullable, proje bazında ve bilinçli olarak açılır (`enable`). Şu an açık olanlar: `Sozsoft.Platform.Application`, `Sozsoft.Notifications.*`, `Sozsoft.Mcp.*`, test projeleri. - **Entity taşıyan projelerde** (`*.Domain`) nullable açılacaksa, aynı PR'da `dotnet ef migrations add` çıktısı gözden geçirilir; şema değişikliği isteniyorsa kabul edilir, istenmiyorsa kolon zorunluluğu `PlatformDbContext` içinde `.IsRequired(false)` ile açıkça sabitlenir. - Kolon zorunluluğu tercihen NRT'ye değil, `PlatformDbContext` yapılandırmasına yazılır; böylece derleyici anahtarından bağımsız olur. ## 3. Ölü kod politikası Aşağıdaki kurallar ihlal edilmiş kod merge edilmez: | Kural | Anlamı | | --- | --- | | IDE0005 | Kullanılmayan `using` | | IDE0051 | Çağrılmayan private üye | | IDE0052 | Yazılıp hiç okunmayan private alan (kullanılmayan enjekte bağımlılık) | | IDE0059 | Gereksiz değer ataması | | IDE0161 | `namespace X;` (file-scoped) | Tarama ve otomatik düzeltme (kalıcı yapılandırma gerektirmez): ```bash # Rapor dotnet build api/Sozsoft.Platform.sln --no-incremental \ -p:EnforceCodeStyleInBuild=true -p:GenerateDocumentationFile=true # Düzeltme dotnet format api/Sozsoft.Platform.sln --diagnostics IDE0005,IDE0161 --severity warn ``` > Kurallar `.editorconfig`'te `warning` seviyesindedir ve `EnforceCodeStyleInBuild` > açık olduğu için normal `dotnet build` de bunları raporlar. IDE0052 düzeltilirken alan ile birlikte **constructor parametresi de** silinir; yoksa DI hâlâ gereksiz servisi çözer. `CS0162` (erişilemez kod) `Directory.Build.props` içinde gerekçesiyle susturulmuştur: veritabanı sağlayıcısı derleme zamanı sabiti olduğu için diğer sağlayıcının dalları erişilemez görünür. Bu durum dışında erişilemez kod bırakılmaz. Ek kurallar: - Bir tip/dosya silinmeden önce **repo genelinde** (api + ui + configs) referans taraması yapılır. DI ile çözülen tipler (AppService, Manager, Provider, Repository, Seeder, Localizer, Notification sağlayıcıları) düz metin aramasında "referanssız" görünür — bunlar yalnızca modül/DI kaydı incelendikten sonra silinir. - Yorum satırına alınmış kod bloğu bırakılmaz; ihtiyaç varsa git geçmişinden alınır. - "İleride lazım olur" gerekçesiyle kullanılmayan DTO/Input/Consts tutulmaz. - `Migrations/` ve `*.g.cs` üretilmiş koddur, bu kuralların dışındadır. ## 4. Modern C# kullanımı (yeni kod) - File-scoped namespace: `namespace Sozsoft.Platform.ListForms;` - Primary constructor: `public class XAppService(IRepository repo) : PlatformAppService` — parametre doğrudan kullanılır, `private readonly ... = param;` şeklinde tekrar tanımlanmaz. - Koleksiyon ifadeleri (`[]`, `[.. items]`), `is not null`, `switch` ifadesi, desen eşleme, `Index/Range`, hedefi belli `new()`. - `out var`, tuple deconstruction; kullanılmayan çıktı için `_`. - `async` metotlar `Async` ile biter ve `Task`/`Task` döner; `async void` yasak. ABP'de `ConfigureAwait` kullanılmaz. - Biçim: `api/.editorconfig` geçerlidir (4 boşluk girinti, `using` blokları namespace dışında ve System önce, dosya sonunda satır sonu, UTF-8 BOM). ## 5. ABP kuralları - **Katman sınırı**: Domain.Shared → Domain → Application.Contracts → Application → HttpApi → HttpApi.Host. Ters yönde referans verilmez; `EntityFrameworkCore` yalnızca Domain'e bakar. `DbMigrator` yalnızca `Application.Contracts` + `EntityFrameworkCore` referansı taşır; **Application'a referans verilmez** — seeder'ın uygulama servislerine ihtiyaç duyması, kodun yanlış katmanda olduğunun işaretidir. - **Hem seeder'ın hem AppService'in ihtiyaç duyduğu dönüşüm**: ortak kod `Application.Contracts`'a konur (DTO'nun kendi metodu olarak; yeni yardımcı sınıf üretilmez). Dönüşümün entity'ye yazması gerekiyorsa entity'nin **yazılabilir kolon sözleşmesi** Domain.Shared'de bir arayüz olarak tanımlanır (`IListFormArtifact`, `IListFormFieldArtifact`), entity bu arayüzü uygular ve dönüşüm arayüz üzerinden yazar. Böylece Contracts, Domain'i tanımadan entity doldurabilir ve aynı dönüşüm iki yerde kopyalanmaz. - **AppService**: `PlatformAppService`'ten türer, arayüzü Application.Contracts'ta tanımlanır. Controller yazılmaz — ABP otomatik API üretir. Controller sadece ABP konvansiyonunun karşılamadığı durumda (dosya indirme, harici webhook) eklenir. - **Yetki**: her AppService/metot `[Authorize(PlatformConsts.AppCodes....)]` ile korunur; izin adları `PermissionDefinitionProvider` içinde tanımlanır. Yetki kontrolü koda gömülmez, izin adı sabitten okunur. - **Tenant izolasyonu**: sorgular `ICurrentTenant` bağlamında çalışır; `IMultiTenant` entity'lerde manuel `TenantId` filtresi yazılmaz, veri filtreleri devre dışı bırakılacaksa (`ICurrentTenant.Change`, `IDataFilter`) gerekçe yorumla yazılır. - **Repository**: özel sorgu gerekiyorsa Domain'de arayüz + EntityFrameworkCore'da implementasyon. AppService içinde `DbContext` doğrudan kullanılmaz. - **Dinamik SQL**: parametreli çalıştırılır; string birleştirme ile sorgu kurulmaz. - **İki sağlayıcı**: uygulama SQL Server ve PostgreSQL'de çalışır (`DefaultDatabaseProvider`). EF modeli SQL Server sözdizimiyle yazılır (`HasColumnType("nvarchar(max)")`, `HasFilter("[IsDeleted] = 0")`, `HasCheckConstraint`, `HasDefaultValueSql("NEWID()")`); PostgreSQL'de `PlatformDbContext.ConfigureProviderSpecificModel` bunları `PostgreSqlModelSyntax` ile çevirir. Çeviride karşılığı olmayan bir sözdizimi kullanılırsa `PostgreSqlModelSyntax`'a eklenir; immutable olmayan hesaplanan kolon (`GETDATE()`'e bağlı) PG'de yazılamaz, sağlayıcıya göre dallanır. C#'ta üretilen SQL tırnaklı tanımlayıcı ve ANSI ifadelerle yazılır ya da `DataSourceType`'a göre dallanır; yalnızca T-SQL'de çalışan metin eklenmez. Tanımlayıcı doğrulama/tırnaklama ve varsayılan şema tek yerdedir: `Sozsoft.Platform.Data.SqlIdentifier` (Domain.Shared; `IsSafe`, `Quote`, `QuoteQualified`, `DefaultSchema`), nesne kataloğu sorgusu `SqlCatalogQueries`; ikinci bir regex/`"public" : "dbo"` kopyası yazılmaz. Dapper deposu tek gerçeklemedir (`DynamicDataRepository`, sağlayıcı `DbProviderFactory` ile seçilir; `Ms`/`Pg` anahtarlı sınıflar yalnızca DI kaydıdır). `PropertyNameCaseInsensitive` JSON okuması `PlatformJsonOptions.CaseInsensitive` ile yapılır, çağrı başına `JsonSerializerOptions` üretilmez. - **Mapping**: Mapperly (`[Mapper]` partial class) kullanılır; AutoMapper eklenmez. Mapper'lar servis klasöründeki `{Alan}Mappers.cs` dosyasındadır (modülde `{Modül}ApplicationMappers.cs`), ABP'nin `MapperBase` / `TwoWayMapperBase`'inden türer ve `RequiredMappingStrategy.Target` ile işaretlenir. RMG012 uyarısı "hedefte karşılığı olmayan alan" demektir; yeni mapper eklerken bu uyarı bırakılmaz: kaynakta karşılığı olmayan, servisin sonradan doldurduğu hedef alan (`User`, `IsLiked`, `*Dto` alt nesneleri, audit alanları) her `Map` overload'unda `[MapperIgnoreTarget(nameof(...))]` ile açıkça yazılır; iç içe koleksiyon için ayrı `private partial` eşleme metodu tanımlanır. - **Localization**: kullanıcıya görünen metin koda gömülmez, `PlatformResource` anahtarı üzerinden verilir. Bu her tür exception mesajı, uyarı listesi, DTO açıklaması ve kayda yazılan sabit değer (ör. sistem onay açıklaması) için geçerlidir; Türkçe ya da İngilizce statik metin backend'den dönmez. Servislerde `L["Sas.X.Y", arg]`; localizer alamayan statik/yardımcı sınıflarda `new BusinessException(PlatformConsts.AppName + ":Sas.X.Y").WithData("name", v)` (ABP kodu `Platform:` ad alanıyla `PlatformResource`'a eşler, dil anahtarı kodun tamamıdır, yer tutucu `{name}`). Log satırları ve geliştiriciye dönük `[Obsolete]` metinleri İngilizcedir, yerelleştirilmez. MCP araç açıklamaları ve `McpProblem` metinleri modele dönüktür, bu kuralın dışındadır; `ModuleDocRenderer`'ın ürettiği modül dokümanı Türkçe proje belgesidir. - **Çok değerli alan taşıyan girdi DTO'su**: ekranda `dxTagBox` (ya da çoklu seçimli `dxGridBox`) ile doldurulan bir alan tipli bir DTO'ya bağlanıyorsa property `string` kalır ve `[JsonConverter(typeof(MultiValueStringJsonConverter))]` ile işaretlenir — UI diziyi olduğu gibi yollar, converter `PlatformConsts.MultiValueDelimiter` ile tek string üretir. Varsayılan `list-form-data/*` ucundan geçen ekranlarda bu iş `QueryHelper` tarafından yapılır, DTO yoktur; okuma DTO'larına öznitelik eklenmez. - **Sabitler**: bağlantı dizesi, tenant id, anahtar/secret koda yazılmaz; konfigürasyon veya `SettingDefinition` üzerinden okunur. ## 6. Değişiklik öncesi kontrol listesi ```bash dotnet build api/Sozsoft.Platform.sln --no-incremental \ -p:EnforceCodeStyleInBuild=true -p:GenerateDocumentationFile=true # 0 hata, yeni IDE00xx yok dotnet format api/Sozsoft.Platform.sln --verify-no-changes --diagnostics IDE0005,IDE0161 --severity warn dotnet test api/test/Sozsoft.Platform.EntityFrameworkCore.Tests ``` > Not: EntityFrameworkCore testleri şu an SQLite şema oluşturmada > (`nvarchar(max)` → `SQLite Error 1`) kırık; bu kırıklık bu standarttan önce de vardı > ve ayrı bir iş olarak ele alınmalıdır.