11 KiB
.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 (
<Nullable>enable</Nullable>). Şu an açık olanlar:Sozsoft.Platform.Application,Sozsoft.Notifications.*,Sozsoft.Mcp.*, test projeleri. - Entity taşıyan projelerde (
*.Domain) nullable açılacaksa, aynı PR'dadotnet ef migrations addçıktısı gözden geçirilir; şema değişikliği isteniyorsa kabul edilir, istenmiyorsa kolon zorunluluğuPlatformDbContextiçinde.IsRequired(false)ile açıkça sabitlenir. - Kolon zorunluluğu tercihen NRT'ye değil,
PlatformDbContextyapı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):
# 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'tewarningseviyesindedir veEnforceCodeStyleInBuildaçık olduğu için normaldotnet buildde 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<X, Guid> repo) : PlatformAppService— parametre doğrudan kullanılır,private readonly ... = param;şeklinde tekrar tanımlanmaz. - Koleksiyon ifadeleri (
[],[.. items]),is not null,switchifadesi, desen eşleme,Index/Range, hedefi bellinew(). out var, tuple deconstruction; kullanılmayan çıktı için_.asyncmetotlarAsyncile biter veTask/Task<T>döner;async voidyasak. ABP'deConfigureAwaitkullanılmaz.- Biçim:
api/.editorconfiggeçerlidir (4 boşluk girinti,usingblokları 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;
EntityFrameworkCoreyalnızca Domain'e bakar.DbMigratoryalnızcaApplication.Contracts+EntityFrameworkCorereferansı 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ıPermissionDefinitionProvideriçinde tanımlanır. Yetki kontrolü koda gömülmez, izin adı sabitten okunur. - Tenant izolasyonu: sorgular
ICurrentTenantbağlamında çalışır;IMultiTenantentity'lerde manuelTenantIdfiltresi 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
DbContextdoğ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'dePlatformDbContext.ConfigureProviderSpecificModelbunlarıPostgreSqlModelSyntaxile çevirir. Çeviride karşılığı olmayan bir sözdizimi kullanılırsaPostgreSqlModelSyntax'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 daDataSourceType'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 sorgusuSqlCatalogQueries; ikinci bir regex/"public" : "dbo"kopyası yazılmaz. Dapper deposu tek gerçeklemedir (DynamicDataRepository, sağlayıcıDbProviderFactoryile seçilir;Ms/Pganahtarlı sınıflar yalnızca DI kaydıdır).PropertyNameCaseInsensitiveJSON okumasıPlatformJsonOptions.CaseInsensitiveile yapılır, çağrı başınaJsonSerializerOptionsüretilmez. - Mapping: Mapperly (
[Mapper]partial class) kullanılır; AutoMapper eklenmez. Mapper'lar servis klasöründeki{Alan}Mappers.csdosyasındadır (modülde{Modül}ApplicationMappers.cs), ABP'ninMapperBase<TSource, TDest>/TwoWayMapperBase'inden türer veRequiredMappingStrategy.Targetile 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,*Dtoalt nesneleri, audit alanları) herMapoverload'unda[MapperIgnoreTarget(nameof(...))]ile açıkça yazılır; iç içe koleksiyon için ayrıprivate partialeşleme metodu tanımlanır. - Localization: kullanıcıya görünen metin koda gömülmez,
PlatformResourceanahtarı ü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. ServislerdeL["Sas.X.Y", arg]; localizer alamayan statik/yardımcı sınıflardanew BusinessException(PlatformConsts.AppName + ":Sas.X.Y").WithData("name", v)(ABP koduPlatform:ad alanıylaPlatformResource'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ı veMcpProblemmetinleri 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çimlidxGridBox) ile doldurulan bir alan tipli bir DTO'ya bağlanıyorsa propertystringkalır ve[JsonConverter(typeof(MultiValueStringJsonConverter))]ile işaretlenir — UI diziyi olduğu gibi yollar, converterPlatformConsts.MultiValueDelimiterile tek string üretir. Varsayılanlist-form-data/*ucundan geçen ekranlarda bu işQueryHelpertarafı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
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.