.Net Native Aot Xml ve Json Serileştirme İşlemleri

 Merhaba, bu yazıda Net Native AOT derlemesinde kullanmak üzere JSON ve XML dönüşümlerinin nasıl yapılacağını inceleyeceğiz. Native AOT, dinamik işlemleri desteklemediği için bu dönüşümlerin nasıl gerçekleştirileceğine yakından bakalım. 

Net Native AOT JSON Serialization & Deserialization Nasıl Yapılır?

Örnek bir proje oluşturmak için, resimde gösterildiği gibi “Web API Yerel AOT” seçeneği ile bir proje örneği oluşturuyoruz. Editör kullanmayanlar için:

dotnet new webapiaot -o MinNativeAot 




Projenin tüm kodları program.cs dosyasında bulunmaktadır. Template ile gelen örnek, JsonSerializerContext'in tam olarak nasıl çalıştığını anlamanızı zorlaştırıyor. Sanki sihirli bir değnek var da her şeyi bu context hallediyor gibi görünüyor.


Aslında, bu context'e eklenen modellere karşılık, source generator tarafından bir kod üretimi gerçekleştiriliyor ve üretilen kod sayesinde işlemler yürütülüyor. Ancak, template ile gelen örnekte, harici olarak nasıl kullanılabileceğini göremiyoruz; bunu da inceleyelim.



using System.Text.Json;
using System.Text.Json.Serialization;

var builder = WebApplication.CreateSlimBuilder(args);

// JSON seçeneklerini önceden derlenmiş serileştirici konteksi kullanacak şekilde yapılandırıyoruz.
builder.Services.ConfigureHttpJsonOptions(options =>
{
    // AOT performansını artırmak için önceden derlenmiş metaveriler ekleniyor.
    options.SerializerOptions.TypeInfoResolverChain.Insert(0, MyJsonSerializerContext.Default);
});

var app = builder.Build();

var intList = new List<int> { 1, 2, 3, 4, 5 };
var itemList = new List<Item>
{
    new(1, "Birinci öğe"),
    new(2, "İkinci öğe"),
    new(3, "Üçüncü öğe")
};

var api = app.MapGroup("/demo");

api.MapGet("/ints/serialize", () =>
{
    var json = JsonSerializer.Serialize(intList, MyJsonSerializerContext.Default.ListInt32);
    return Results.Text(json, "application/json");
});

api.MapGet("/items/serialize", () =>
{
    var json = JsonSerializer.Serialize(itemList, MyJsonSerializerContext.Default.ListItem);
    return Results.Text(json, "application/json");
});

api.MapGet("/items/deserialize", () =>
{
    var jsonStr ="{\"Id\":1,\"Description\":\"Test\"}";
    var jsonObj = JsonSerializer.Deserialize<Item>(jsonStr, MyJsonSerializerContext.Default.Item);
    var jsonStr2 = JsonSerializer.Serialize(jsonObj, MyJsonSerializerContext.Default.Item);
    return Results.Text(jsonStr2, "application/json");
});

app.Run();

// Model
public record Item(int Id, string Description);

// AOT (Önceden Derlenmiş) JSON serileştirici contexti
[JsonSerializable(typeof(List<int>))]
[JsonSerializable(typeof(List<Item>))]
[JsonSerializable(typeof(Item))]
internal partial class MyJsonSerializerContext : JsonSerializerContext
{
}



Buradaki önemli nokta, her bir tip için attribute olarak register etmemiz gerektiğidir. Örneğin, modelimiz bir liste ise, liste olarak da register edilmesi gerekmektedir. Editörünüz destekliyorsa, register edilen her model için otomatik olarak MyJsonSerializerContext.Default.TipIsmi kodu oluşturulacak ve tipi seçmenize olanak tanıyacaktır. Editör desteklemiyorsa projeyi bir kere çalıştırın hatalar gidecektir. Source Generator'ler, build işleminden hemen önce kodu üretir ve DLL'in içine dahil eder. JSON dönüşümleri bu kadar basit. Şimdi XML dönüşümlerine bir bakalım.

  

Net Native AOT XML Serialization & Deserialization Nasıl Yapılır?


Xml dönüşümü için gerekli olan kütüphaneleri csproj'a aşağıdaki gibi ekliyoruz.:


<Project Sdk="Microsoft.NET.Sdk.Web">

    <PropertyGroup>
        <TargetFramework>net8.0</TargetFramework>
        <Nullable>enable</Nullable>
        <ImplicitUsings>enable</ImplicitUsings>
        <InvariantGlobalization>true</InvariantGlobalization>
        <PublishAot>true</PublishAot>
    </PropertyGroup>

    <!--Xml Serileştirme için gerekli olan kütüphane başlangıç-->
    <ItemGroup>
        <PackageReference Include="Microsoft.XmlSerializer.Generator" Version="8.0.0" />
    </ItemGroup>
    <ItemGroup>
        <DotNetCliToolReference Include="Microsoft.XmlSerializer.Generator" Version="8.0.0" />
    </ItemGroup>

    <ItemGroup>
        <IlcArg Include="--substitution:$(MSBuildThisFileDirectory)ILLink.Substitutions.xml" />
    </ItemGroup>

    <Target Name="LinkSerializationAssembly" AfterTargets="GenerateSerializationAssembly" BeforeTargets="WriteIlcRspFileForCompilation">
        <ItemGroup>
            <IlcCompileInput Include="$(_SerializerDllIntermediateFolder)" />
            <TrimmerRootAssembly Include="$(_SerializationAssemblyName)" />
        </ItemGroup>
    </Target>
    <!--Xml Serileştirme için gerekli olan kütüphane bitiş-->

</Project>



Daha sonra Program.cs olduğu dizinde aşağıdaki ILLink.Substitutions.xml isimli xml dosyasını dahil ediyoruz.

<linker>
  <assembly fullname="System.Private.Xml">
    <type fullname="System.Xml.Serialization.XmlSerializer">
      <method signature="System.Xml.Serialization.SerializationMode get_Mode()" body="stub" value="2" />
    </type>
  </assembly>
</linker>

Bu ILLink.Substitutions.xml dosyası Microsoft'un System.Private.Xml assemly'sinde proje derlenmeden önce assembly üzerinden değişiklik yapmamıza işe yarıyor. Buradaki kodun amacı private yapılmış bir değişkenin değerini değiştirmek, private olduğu için haliyle kod tarafında değişime izin vermiyor. Microsoft kendi yazdığı kodda dinamik olarak assembly kodunu kendi tip okuyucusuna dahil ediyor ama öncelik olarak dinamik olarak çalışıyor mu diye kontrol ediyor ve öncelik sırasını ona veriyor. bu yüzden bu değeri elle müdahale ederek değiştiriyoruz ve native aot derlendiği zaman düzgün bir şekilde çalışıyor.

Örnek olması için bir product modeli oluşturdum. 


<?xml version="1.0" encoding="UTF-8"?>
<products>
  <product id="001">
    <name>Akıllı Telefon X</name>
    <brand>Acme</brand>
    <price currency="USD">699</price>
    <specs>
      <ram>6GB</ram>
      <storage>128GB</storage>
      <display>6.1 inç</display>
    </specs>
    <features>
      <feature>Yüz Tanıma</feature>
      <feature>Kablosuz Şarj</feature>
    </features>
  </product>
  <product id="002">
    <name>Dizüstü Pro</name>
    <brand>TechCorp</brand>
    <price currency="USD">1299</price>
    <specs>
      <processor>Intel i7</processor>
      <ram>16GB</ram>
      <storage>512GB SSD</storage>
      <display>15.6 inç</display>
    </specs>
    <features>
      <feature>Aydınlatmalı Klavye</feature>
      <feature>Parmak İzi Okuyucu</feature>
    </features>
  </product>
  <product id="003">
    <name>Kablosuz Kulaklık</name>
    <brand>SoundMax</brand>
    <price currency="USD">199</price>
    <specs>
      <connectivity>Bluetooth 5.0</connectivity>
      <battery>5 saat</battery>
    </specs>
    <features>
      <feature>Gürültü Engelleme</feature>
      <feature>Suya Dayanıklı</feature>
    </features>
  </product>
</products>

c# kodu :
using System.Diagnostics.CodeAnalysis;
using System.Xml.Serialization;

namespace MinNativeAot;

[XmlRoot(ElementName="price")]
public class Price { 

    [XmlAttribute(AttributeName="currency")] 
    public string Currency { get; set; } 

    [XmlText] 
    public int Text { get; set; } 
}

[XmlRoot(ElementName="specs")]
public class Specs { 

    [XmlElement(ElementName="ram")] 
    public string Ram { get; set; } 

    [XmlElement(ElementName="storage")] 
    public string Storage { get; set; } 

    [XmlElement(ElementName="display")] 
    public string Display { get; set; } 

    [XmlElement(ElementName="processor")] 
    public string Processor { get; set; } 

    [XmlElement(ElementName="connectivity")] 
    public string Connectivity { get; set; } 

    [XmlElement(ElementName="battery")] 
    public string Battery { get; set; } 
}

[XmlRoot(ElementName="features")]
public class Features { 

    [XmlElement(ElementName="feature")] 
    public List<string> Feature { get; set; } 
}

[XmlRoot(ElementName="product")]
public class Product { 

    [XmlElement(ElementName="name")] 
    public string Name { get; set; } 

    [XmlElement(ElementName="brand")] 
    public string Brand { get; set; } 

    [XmlElement(ElementName="price")] 
    public Price Price { get; set; } 

    [XmlElement(ElementName="specs")] 
    public Specs Specs { get; set; } 

    [XmlElement(ElementName="features")] 
    public Features Features { get; set; } 

    [XmlAttribute(AttributeName="id")] 
    public int Id { get; set; } 

    [XmlText] 
    public string Text { get; set; } 
}

//bu attribute'i ana root objemize ekliyoruz. proje derlenirken trim edilmesin diye.
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]
[XmlRoot(ElementName="products")]
public class Products { 

    [XmlElement(ElementName="product")] 
    public List<Product> Product { get; set; } 
}
Program.cs'e ekleyeceğimiz endpoint ise:

var products = new Products
{
    Product = new List<Product>
    {
        new Product
        {
            Id = 1,
            Name = "Akıllı Telefon X",
            Brand = "Acme",
            Price = new Price
            {
                Currency = "USD",
                Text = 699
            },
            Specs = new Specs
            {
                Ram = "6GB",
                Storage = "128GB",
                Display = "6.1 inç"
            },
            Features = new Features
            {
                Feature = new List<string>
                {
                    "Yüz Tanıma",
                    "Kablosuz Şarj"
                }
            }
        },
        new Product
        {
            Id = 2,
            Name = "Dizüstü Pro",
            Brand = "TechCorp",
            Price = new Price
            {
                Currency = "USD",
                Text = 1299
            },
            Specs = new Specs
            {
                Processor = "Intel i7",
                Ram = "16GB",
                Storage = "512GB SSD",
                Display = "15.6 inç"
            },
            Features = new Features
            {
                Feature = new List<string>
                {
                    "Aydınlatmalı Klavye",
                    "Parmak İzi Okuyucu"
                }
            }
        },
        new Product
        {
            Id = 3,
            Name = "Kablosuz Kulaklık",
            Brand = "SoundMax",
            Price = new Price
            {
                Currency = "USD",
                Text = 199
            },
            Specs = new Specs
            {
                Connectivity = "Bluetooth 5.0",
                Battery = "5 saat"
            },
            Features = new Features
            {
                Feature = new List<string>
                {
                    "Gürültü Engelleme",
                    "Suya Dayanıklı"
                }
            }
        }
    }
};
api.MapGet("/product/xml", () =>
{
    XmlSerializer serializer = new XmlSerializer(typeof(Products));
    using (StringWriter writer = new StringWriter())
    {
        serializer.Serialize(writer, products);
        string xml = writer.ToString();
        return Results.Text(xml, "application/xml");
    }
});

Sonuç: Projeyi derledikten sonra publish klasörü aşağıdaki gibi oluşacaktır. XmlSerializers.dll dosyasına ihtiyacı yok çünkü <Target Name="LinkSerializationAssembly" kısmında eklediğimiz komut ile bu üretilen kod derlenen kodun içine alındı. 

📁 Publish Klasörü
↳ 📄 appsettings.Development.json (1 KB)
↳ 📄 appsettings.json (1 KB)
↳ 📄 MinNativeAot.XmlSerializers.dll (17 KB)
↳ 🖥️ MinNativeAot.exe (16.421 KB)
↳ 📄 MinNativeAot.pdb (83.316 KB)
Projenin tam hali için bu https://github.com/mzuvin/MinNativeAot linkten ulaşabilirsiniz. 

 Gelelim xml dönüşümlerinin sorunlarına. Eğer dönüştüreceğiniz model başka bir proje içinde ise, resimde gözüken üretilen kodun içerisinde dahil edilmiyor. bu da her bir model'i api projesine eklemek zorunda bırakıyor. Bunun için bir çözüm bulamadım. Bunun için aşağıdaki kod ile manuel olarak serileştirme kodunu projeye dahil etmek gerekiyor.

dotnet Microsoft.XmlSerializer.Generator "..\MinNativeAot.Models\bin\Debug\net8.0\MinNativeAot.Models.dll" --force --quiet



Yararlandığım kaynaklar:

https://gist.github.com/filipnavara/1e8831c256498bad53b3aae94af87a20


Mustafa

Software Developer, Blogger

Yorum Gönder

Okuyup geçme yorum yap lütfen :)

Yorumunuz cevaplandığında bildirim almak için Beni bilgilendir'i işaretleyin.

Daha yeni Daha eski