Mert Özen Her Satırda Daha İleri
Backend .NET SDK, CLI ve MSBuild — #04

NuGet Restore'u Yönetmek: nuget.config, Package Source Mapping, Lock File'lar, Private Feed ve Minimum Publish Age

Mert Özen 15 Kas 2026 12 dk 7 görüntülenme
NuGet Restore'u Yönetmek: nuget.config, Package Source Mapping, Lock File'lar, Private Feed ve Minimum Publish Age

Birden fazla NuGet kaynağı tanımlıysa ve package source mapping yoksa, aynı adı taşıyan dış paket şirket içi paketin yerine seçilebilir; mapping her paketi yalnız izin verilen kaynaktan alır. Bookstore.Contracts'ı kimlik bilgisi ortam değişkeninden gelen bir private feed'e koyup nuget.config hiyerarşisini, lock file ve kilitli modu, .NET 11'in minimum publish age ayarını, NuGetAudit'in feed'e bağımlılığını ve CI'da restore cache'ini gerçek çıktılarla gösteriyoruz.

Sürüm notu: Bu yazıdaki kodlar .NET 10 (SDK 10.0.404) ile derlenip denenmiştir; Reporting örneği ve minimum publish age bölümü SDK 11.0.100 (NuGet 8) ile, şirket içi feed BaGetter 1.6.5 container'ı ile çalıştırılmıştır.

Önce Tahmin Et

Raporlama ekibinin servisi Bookstore.Contracts paketini kullanıyor ve sürümü 1.* olarak yazmış: "1.x'in en yenisi gelsin". Makinede iki paket kaynağı tanımlı: şirket içi feed ve herkese açık bir feed. Şirket içi feed'de Bookstore.Contracts 1.0.0 var. Birisi aynı adla herkese açık feed'e 1.99.0 yüklemiş. Restore hangisini seçer?

dotnet package list --project Bookstore.Reporting
Project 'Bookstore.Reporting' has the following package references
   Top-level Package          Requested   Resolved
   > Bookstore.Contracts      1.*         1.99.0

Herkese açık feed'deki paket kazandı. Build bu sefer kırıldı, çünkü sahte paketin içinde BookSummary tipi yoktu; içinde aynı tipler ve fazladan bir kod parçası olsaydı build geçecekti. Bu saldırının adı dependency confusion (iç paketle aynı adı taşıyan dış paketin seçilmesi). Bu yazıda sitedeki serilerin ortak örnek uygulaması Bookstore (bir online kitapçı) için Bookstore.Contracts'ı şirket içi bir feed'e koyup nuget.org ile birlikte güvenli şekilde kullanıyoruz: nuget.config hiyerarşisi, secret'ı repoya koymadan feed kimlik bilgisi, package source mapping, lock file'lar, .NET 11'in minimum publish age ayarı ve CI'da restore'u hızlandırmak.

Örnekteki "herkese açık feed", nuget.org'a sahte paket yüklemek yerine yerel bir klasör feed'idir; davranış nuget.org'la aynıdır. Paket yayımlamanın kendisi (metadata, sürüm numarası, imzalama) Versioning ve Release Management serisinin konusu; burada tüketen taraftayız.

nuget.config Hiyerarşisi

NuGet ayarlarını tek bir dosyadan değil, birkaç dosyanın birleşiminden okur: komutun çalıştığı klasörden diske kadar yukarıdaki her nuget.config, sonra kullanıcı düzeyindeki dosya (Windows'ta %AppData%\NuGet\NuGet.Config). Bookstore'un köküne yalnız nuget.org'u tanımlayan bir dosya koyduk:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <packageSources>
    <clear />
    <add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
  </packageSources>
  <packageSourceMapping>
    <packageSource key="nuget.org">
      <package pattern="*" />
    </packageSource>
  </packageSourceMapping>
</configuration>

<clear />, bu dosyadan önce okunan kaynakları, yani kullanıcı düzeyindeki dosyadan gelenleri siler. Bir geliştiricinin makinesine eklediği eski bir feed ya da Visual Studio'nun offline klasörü artık Bookstore'un restore'una karışamaz; ekipteki herkes ve CI aynı kaynakları kullanır.

Bookstore.Contracts'ı kullanacak örnek servis samples/reporting altında. Bu klasörü raporlama ekibinin ayrı reposu gibi düşünebilirsin: kendi global.json'u (SDK 11.0.100), Directory.Build.props'u, Directory.Packages.props'u ve nuget.config'i var. Bu klasördeki nuget.config şirket içi feed'i ekliyor; hangi dosyaların birleştirildiğini NuGet'e sorabilirsin:

cd samples/reporting
dotnet nuget config paths
dotnet nuget list source
C:\src\Bookstore\samples\reporting\NuGet.Config
C:\src\Bookstore\NuGet.Config
C:\Users\user\AppData\Roaming\NuGet\NuGet.Config
Registered Sources:
  1.  nuget.org [Enabled]
      https://api.nuget.org/v3/index.json
  2.  bookstore-internal [Enabled]
      http://127.0.0.1:52404/v3/index.json

Klasöre yakın olan dosya önce gelir ve aynı anahtarı tanımlarsa onu ezer. Reporting'in dosyasında <clear /> yok, bu yüzden nuget.org kökteki dosyadan geliyor ve listeye şirket içi feed ekleniyor.

Private Feed ve Kimlik Bilgileri: Secret'ı Repoya Koymadan

Gerçek hayatta şirket içi feed Azure Artifacts, GitHub Packages ya da kendi kurduğun bir NuGet sunucusudur. Örneği herkesin çalıştırabilmesi için açık kaynak bir NuGet sunucusu olan BaGetter'ı container olarak 127.0.0.1'de çalıştırdık; paket okumak için kullanıcı adı ve şifre, yüklemek için API key istiyor. Değerler repoda değil, ortam değişkenlerinde:

docker run -d --name bookstore-feed -p 127.0.0.1:52404:8080 `
  -e ApiKey=$env:BOOKSTORE_FEED_API_KEY `
  -e Authentication__Credentials__0__Username=$env:BOOKSTORE_FEED_USER `
  -e Authentication__Credentials__0__Password=$env:BOOKSTORE_FEED_PASSWORD `
  -e Database__Type=Sqlite -e "Database__ConnectionString=Data Source=/data/db/bagetter.db" `
  -e Storage__Type=FileSystem -e Storage__Path=/data/packages `
  bagetter/bagetter:1.6.5

Reporting'in nuget.config'i kaynağı ve kimlik bilgisinin nereden okunacağını tanımlıyor; değerlerin kendisi yok:

<packageSources>
  <!-- <clear /> yok: bu dosya repo kökündeki nuget.config ile birleştirilir. -->
  <!-- 72 saatlik minimum publish age için burada yeniden tanımlandı (NuGet 8 / .NET SDK 11). -->
  <add key="nuget.org" value="https://api.nuget.org/v3/index.json" minPublishAgeHours="72" />
  <!-- Şirket feed'i HTTPS; bu örnekteki yerel BaGetter container'ı HTTP. -->
  <add key="bookstore-internal" value="http://127.0.0.1:52404/v3/index.json" allowInsecureConnections="true" />
</packageSources>
<packageSourceCredentials>
  <bookstore-internal>
    <add key="Username" value="%BOOKSTORE_FEED_USER%" />
    <add key="ClearTextPassword" value="%BOOKSTORE_FEED_PASSWORD%" />
  </bookstore-internal>
</packageSourceCredentials>

NuGet %AD% sözdizimini restore sırasında ortam değişkeninin değeriyle değiştirir. Geliştirici değişkenleri kendi oturumunda set eder, CI ise pipeline'ın secret deposundan verir; dosya hiçbir zaman şifre içermez. Değişkenler yoksa sonuç net bir hata:

C:\src\Bookstore\samples\reporting\Bookstore.Reporting\Bookstore.Reporting.csproj : error NU1301: Unable to load the service index for source http://127.0.0.1:52404/v3/index.json. [C:\src\Bookstore\samples\reporting\Bookstore.Reporting.slnx]
C:\src\Bookstore\samples\reporting\Bookstore.Reporting\Bookstore.Reporting.csproj : error NU1301:   Response status code does not indicate success: 401 (Unauthorized). [C:\src\Bookstore\samples\reporting\Bookstore.Reporting.slnx]

allowInsecureConnections yalnız bu yerel örnek için var. Güncel SDK'larda NuGet HTTP kaynaklarını reddediyor; attribute olmadan hem 10.0.404 hem 11.0.100 NU1302 hatası verdi. Gerçek bir feed'de HTTPS kullan ve bu attribute'u koyma. Kimlik bilgisini dotnet nuget add source --username --password ile kaydetmek de bir seçenek, ama değer kullanıcı düzeyindeki dosyaya yazılır ve Windows dışında şifrelenmez; ortam değişkeni hem daha güvenli hem CI'a daha kolay taşınır. Azure Artifacts gibi kimlik doğrulama gerektiren feed'lerde bunun yerine credential provider eklentileri kullanılabilir.

Paketi feed'e yüklemek için Bookstore'un kökünde dotnet pack ile üretilen dosyayı push ettik. --configfile, kaynak tanımını ve kimlik bilgisini Reporting'in dosyasından alıyor:

dotnet pack src/Bookstore.Contracts
dotnet nuget push artifacts/package/release/Bookstore.Contracts.1.0.0.nupkg --source bookstore-internal --api-key $env:BOOKSTORE_FEED_API_KEY --configfile samples/reporting/nuget.config
Pushing Bookstore.Contracts.1.0.0.nupkg to 'http://127.0.0.1:52404/api/v2/package'...
  PUT http://127.0.0.1:52404/api/v2/package/
  Created http://127.0.0.1:52404/api/v2/package/ 213ms
Your package was pushed.

Kaynağı nerede tanımladığın da önemli. İlk denemede şirket içi feed'i Bookstore'un kök nuget.config'ine eklemiştik; Bookstore bu feed'den hiçbir paket kullanmadığı hâlde, kimlik bilgisi olmayan bir oturumda restore şöyle kırıldı:

C:\src\Bookstore\src\Bookstore.Contracts\Bookstore.Contracts.csproj : error NU1900: Warning As Error: Error occurred while getting package vulnerability data: Unable to load the service index for source http://127.0.0.1:52404/v3/index.json.

NuGetAudit (bağımlılıklardaki bilinen güvenlik açıklarını restore sırasında kontrol eden özellik) güvenlik açığı verisini tanımlı bütün kaynaklardan okur. Kaynağa ulaşamayınca NU1900 uyarısı verir, TreatWarningsAsErrors de onu hataya çevirir. Feed'in kapalı olduğu bir sabah bütün build'ler durur. Bu yüzden şirket içi feed'i yalnız onu kullanan projenin yakınına tanımladık.

Package Source Mapping ve Dependency Confusion

Birden fazla kaynak tanımlandığında NuGet bir paketi bütün kaynaklarda arar. Kesin bir sürüm istersen ilk cevap veren kaynaktan alır; 1.* gibi floating bir sürümde ise bütün kaynaklardaki en yüksek sürümü seçer. Baştaki örnekte olan buydu. Package source mapping, hangi paketin hangi kaynaktan gelebileceğini belirler. Kökteki dosya her şeyi nuget.org'a bağlıyor; Reporting'in dosyası ise Bookstore.* ile başlayan paketleri şirket içi feed'e bağlıyor:

<packageSourceMapping>
  <packageSource key="bookstore-internal">
    <package pattern="Bookstore.*" />
  </packageSource>
</packageSourceMapping>

Bir paket birden fazla pattern'e uyuyorsa en uzun, yani en özel pattern kazanır: Bookstore.Contracts için Bookstore.*, *'dan önce gelir. Baştaki deneyi mapping'le tekrarladık:

   Top-level Package          Requested   Resolved
   > Bookstore.Contracts      1.*         1.0.0

Herkese açık feed'deki 1.99.0 hiç sorgulanmadı. Restore'un verbose çıktısında da paket yalnız şirket içi feed'den isteniyor:

GET http://127.0.0.1:52404/v3/package/bookstore.contracts/index.json
OK http://127.0.0.1:52404/v3/package/bookstore.contracts/index.json 7ms
GET http://127.0.0.1:52404/v3/package/bookstore.contracts/1.0.0/bookstore.contracts.1.0.0.nupkg
OK http://127.0.0.1:52404/v3/package/bookstore.contracts/1.0.0/bookstore.contracts.1.0.0.nupkg 5ms

Şirket ön ekini nuget.org'da kendi hesabına rezerve etmek (ID prefix reservation) de ek bir önlem, ama yalnız nuget.org'u korur. Mapping ise kaynaktan bağımsız çalışır.

Lock File'lar ve Kilitli Mod

Restore, aynı Directory.Packages.props'tan her seferinde aynı paket grafını üretmeyebilir: floating sürümler, transitive bağımlılıkların aralıkları ve kaynaklarda sonradan çıkan sürümler sonucu değiştirir. Lock file, restore'un çözdüğü grafı içerik hash'leriyle birlikte kaydeder. Reporting'in Directory.Build.props'unda açtık:

<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
<RestoreLockedMode Condition="'$(CI)' == 'true'">true</RestoreLockedMode>
{
  "version": 2,
  "dependencies": {
    "net10.0": {
      "Bookstore.Contracts": {
        "type": "Direct",
        "requested": "[1.0.0, )",
        "resolved": "1.0.0",
        "contentHash": "t32kTZ2zfbt2XFqKKPAd8ipqX1j7AMDWXa4kf/PBBLtsFIo5I+OW0IRtXgp4NE7Z0vDsJHOHuVyAGGOcsVF9HA=="
      }
    }
  }
}

packages.lock.json repoya commit'lenir. Geliştirici makinesinde restore dosyayı günceller; CI'da ise kilitli modda çalışır ve dosyayla uyuşmayan her şeyi reddeder. GitHub Actions gibi CI sistemleri CI ortam değişkenini true yaptığı için ikinci satır CI'da kilitli modu açar; değişkeni set etmeyen bir sistemde pipeline'da kendin ver. Birisi projeye yeni bir paket ekleyip lock file'ı güncellemeden push ettiğinde:

C:\src\Bookstore\samples\reporting\Bookstore.Reporting\Bookstore.Reporting.csproj : error NU1004: The package references have changed for net10.0. Lock file's package references: Bookstore.Contracts:[1.0.0, ), project's package references: Bookstore.Contracts >= 1.0.0, Microsoft.Extensions.Logging.Abstractions >= 10.0.14.The packages lock file is inconsistent with the project dependencies so restore can't be run in locked mode. Disable the RestoreLockedMode MSBuild property or pass an explicit --force-evaluate option to run restore to update the lock file. [C:\src\Bookstore\samples\reporting\Bookstore.Reporting.slnx]

Aynı kontrolü komut satırından dotnet restore --locked-mode ile de yapabilirsin. İçerik hash'i ayrıca, aynı sürüm numarasıyla farklı içerik sunan bir kaynağı da yakalar. Bookstore.Contracts'ı yeniden pack edip aynı 1.0.0 numarasıyla başka bir feed'e koyduğumuzda, kilitli mod kapalıyken bile restore durdu:

C:\src\Bookstore\samples\reporting\Bookstore.Reporting\Bookstore.Reporting.csproj : error NU1403: Package content hash validation failed for Bookstore.Contracts.1.0.0. The package is different than the last restore. [C:\src\Bookstore\samples\reporting\Bookstore.Reporting.slnx]

dotnet pack her seferinde bit bit aynı paketi üretmediği için senin feed'ine koyacağın paket de repodaki lock file'la uyuşmayacak; örneği kendi makinende çalıştırırken lock file'ı bir kez dotnet restore --force-evaluate ile yeniden üret. Microsoft'un global.json dokümanı lock file kullanan repolar için rollForward'ı disable yapmayı öneriyor, çünkü SDK sürümüne göre değişen package pruning bağımlılık grafını değiştirebiliyor. Bizim küçük grafımızda 11.0.100 ile yazılan lock file 10.0.404 ile kilitli modda sorunsuz restore edildi; bağımlılık sayısı arttıkça bu fark ortaya çıkabilir.

.NET 11: Minimum Publish Age

Ele geçirilmiş bir hesaptan yayımlanan kötü niyetli sürümler genellikle ilk saatlerde ya da günlerde fark edilip kaldırılıyor. NuGet 8 ile gelen minimum publish age, yeni yayımlanan sürümlerin otomatik seçilmesini kaynak bazında geciktiriyor. Reporting'in dosyasında nuget.org'u minPublishAgeHours="72" ile yeniden tanımlamamızın sebebi bu; şirket içi feed'de bekleme yok. Etkisini görmek için süreyi geçici olarak 168 saate (bir hafta) çıkarıp bir paket ekledik:

dotnet package add Microsoft.Extensions.Logging.Abstractions --project Bookstore.Reporting
info : PackageReference for package 'Microsoft.Extensions.Logging.Abstractions' version '10.0.14' added to file 'C:\src\Bookstore\samples\reporting\Directory.Packages.props'.

Ayar olmadan aynı komut 11.0.0'ı seçti. 11.0.0 ve 10.0.15, yazının yazıldığı günden beş gün önce, 10 Kasım 2026'da yayımlandı ve bir haftalık bekleme süresini doldurmadı; komut süresini doldurmuş en yeni sürüm olan 10.0.14'ü seçti. Ayarın sınırlarını bilmek önemli: yalnız dotnet package add, dotnet package update ve floating sürümler gibi NuGet'in sürüm seçtiği durumlarda devreye giriyor. Sürümü elle yazarsan ya da paket transitive olarak gelirse restore yaşına bakmıyor. Floating sürümle birlikte kullanılamıyor:

C:\src\Bookstore\samples\reporting\Bookstore.Reporting\Bookstore.Reporting.csproj : error NU1020: The following PackageReference items cannot specify a floating version because package source cooldown is enabled: Microsoft.Extensions.Logging.Abstractions. [C:\src\Bookstore\samples\reporting\Bookstore.Reporting.slnx]

Bu özelliği SDK 11.0.100 ve NuGet 8 getiriyor. Aynı nuget.config ile 10.0.404'te dotnet package add attribute'u sessizce yok sayıp 11.0.0'ı seçti; dotnet nuget add source'un --min-publish-age-hours parametresini de tanımadı. Reporting klasörünün global.json'unda 11.0.100'ü sabitlememizin sebebi bu: ayar, yalnız onu anlayan SDK ile çalışırsa anlamlı. Bir güvenlik açığını kapatan yeni bir sürümü beklemeden almak istersen sürümü açıkça yaz; süre kontrolü atlanır.

Paketleri Listelemek ve Güncellemek

.NET 10'un isim-önce komutları paket yönetimini tek ailede topluyor: dotnet package add, list, remove, update ve search. list'in en çok kullanılan seçenekleri --outdated, --vulnerable ve --include-transitive. Bookstore.Api'de --outdated Entity Framework Core (EF Core; .NET'in veritabanı erişim library'si) paketleri için en yeni sürüm olarak 11.0.0'ı gösteriyor; .NET 10 hedefleyen bir projede major sürüm atlamak istemiyorsan sınırı daralt:

dotnet package list --project src/Bookstore.Api --outdated --highest-minor
dotnet package list --vulnerable --include-transitive
The given project `Bookstore.Api` has no updates given the current sources.
...
The given project `Bookstore.Api` has no vulnerable packages given the current sources.
The given project `Bookstore.Contracts` has no vulnerable packages given the current sources.
The given project `Bookstore.Api.Tests` has no vulnerable packages given the current sources.
The given project `Bookstore.Contracts.Tests` has no vulnerable packages given the current sources.
The given project `Bookstore.Admin` has no vulnerable packages given the current sources.

dotnet package update ise listelemeyle kalmayıp sürümü değiştirir: paket adı verirsen o paketi, --vulnerable verirsen bilinen açığı olan paketleri güvenli en düşük sürüme yükseltir. Central Package Management kullanan repoda değişiklik Directory.Packages.props'a yazılır. NuGetAudit'in nasıl yapılandırılacağı ve bulguların CI'da nasıl ele alınacağı DevSecOps serisinde.

Global Packages Klasörü, Cache ve CI'da Restore

İndirilen her paket, makine genelindeki global packages klasörüne bir kez açılır ve bütün projeler onu oradan kullanır; HTTP cevapları da ayrı bir cache'te tutulur. Varsayılan yerler Windows'ta %UserProfile%\.nuget\packages ve %LocalAppData%\NuGet\v3-cache. CI'da bu klasörleri NUGET_PACKAGES ve NUGET_HTTP_CACHE_PATH ortam değişkenleriyle cache'lenebilir bir yere taşımak yaygın bir yöntem:

dotnet nuget locals all --list
http-cache: C:\nuget\http-cache
global-packages: C:\nuget\packages
temp: C:\nuget\scratch
plugins-cache: C:\nuget\plugins-cache

Restore yavaşsa önce cache'in kullanılıp kullanılmadığına bak. CI'da global packages klasörünü cache'le ve cache anahtarını packages.lock.json ile Directory.Packages.props'un hash'inden üret: bağımlılık değişmedikçe paketler diskten gelir, değişince anahtar da değişir. Ardından dotnet restore --locked-mode, dotnet build --no-restore sırası, restore'u tek bir adımda yapar. Bozuk bir paket ya da tuhaf bir restore hatası için dotnet nuget locals http-cache --clear genelde yeterli; all --clear bütün paketlerin yeniden indirilmesi demek.

Üretimde Dikkat

Feed kimlik bilgisini CI'da en az yetkiyle ver: restore için yalnız okuma, push için ayrı ve yalnız yayımlama pipeline'ına açık bir API key. İkisini aynı secret'ta tutma.

Şirket içi feed'in çalışmaması bütün build'leri durdurabilir. NU1900 örneğinde gördüğümüz gibi NuGetAudit bile buna yol açabiliyor. Feed'in erişilebilirliğini izle ve kaynağı yalnız ihtiyaç duyan repolara tanımla.

Lock file güncellemelerini kod incelemesinde görmezden gelme. Bir pull request'te beklemediğin bir transitive paket ya da sürüm değişikliği görüyorsan, nedenini sor; dependency confusion ya da ele geçirilmiş bir paket ilk orada fark edilir.

Sık Düşülen Tuzaklar

Kaynak eşlemesi olmadan aynı paket adının iki feed'de bulunması. Floating sürümde en yüksek sürüm, kesin sürümde ilk cevap veren kaynak kazanır; ikisi de senin kontrolünde değil. Birden fazla kaynak tanımladığın her repoda package source mapping kullan.

Lock file'ı commit'leyip CI'da kilitli modda kullanmamak. Kilitli mod kapalıyken restore lock file'ı sessizce günceller ve dosya yalnız bir kayıt olarak kalır. RestoreLockedMode'u CI'da aç ya da --locked-mode ver.

Kimlik bilgisini nuget.config'e düz metin yazmak. Repoya giren şifre geçmişte kalır; değiştirmen gerekir. %AD% ile ortam değişkenine bağla.

Kullanıcı düzeyindeki nuget.config'e güvenmek. Bir geliştiricinin makinesinde çalışan restore, o kişinin eklediği bir kaynak sayesinde çalışıyor olabilir. Repo kökünde <clear /> ile başlayan bir dosya bu farkı ortadan kaldırır.

Minimum publish age'in her şeyi koruduğunu sanmak. Transitive paketlere ve elle yazılan sürümlere uygulanmaz; eski SDK'lar ayarı hiç okumaz.

Küçük Bir Deneme

★☆☆ samples/reporting klasöründe ve repo kökünde dotnet nuget config paths ile dotnet nuget list source çalıştır. Kullanıcı düzeyindeki dosyana bir kaynak ekle ve kökteki <clear />'ın onu nasıl sildiğini gözle.

★★☆ Reporting'in nuget.config'inden packageSourceMapping bölümünü sil, Bookstore.Contracts sürümünü 1.* yap (Central Package Management'ta bunun için CentralPackageFloatingVersionsEnabled gerekir) ve yerel bir klasör feed'ine kendi ürettiğin daha yüksek sürümlü bir paket koy. Restore hangi paketi seçiyor? Mapping'i geri koyup tekrarla.

★★★ Reporting'e yeni bir paket ekle ama packages.lock.json'u güncellemeden CI=true ile dotnet restore çalıştır ve NU1004'ü oku. Sonra global packages klasörünü cache'leyen ve anahtarını lock file'ın hash'inden üreten bir CI adımı tasarla.

Reporting servisi Bookstore.Contracts'ı artık güvenli şekilde alıyor, ama yalnız .NET 10'da. Raporlama ekibinin .NET 8'de kalan eski bir export servisi de aynı paketi kullanmak istiyor. Bir sonraki yazıda Bookstore.Contracts'ı net8.0 ve net10.0 için birlikte derleyeceğiz: TargetFrameworks, framework'e göre koşullu kod ve paket referansları, platform API'leri ve iki framework'te de çalışan testler.