.NET: Pengobatan Ketergantungan

Siapa yang tidak mengalami masalah karena pengarahan ulang perakitan? Kemungkinan besar, setiap orang yang mengembangkan aplikasi yang relatif besar cepat atau lambat akan menghadapi masalah ini.

Sekarang saya bekerja di JetBrains, dalam proyek JetBrains Rider, dan saya terlibat dalam tugas migrasi Rider ke .NET Core. Sebelumnya terlibat dalam infrastruktur bersama di Circuit, platform hosting aplikasi berbasis cloud.



Di bawah cutscene adalah transkrip laporan saya dari konferensi Moskow DotNext 2019, di mana saya berbicara tentang kesulitan ketika bekerja dengan majelis di .NET dan menunjukkan dengan contoh-contoh praktis apa yang terjadi dan bagaimana menghadapinya.


Di semua proyek di mana saya bekerja sebagai pengembang .NET, saya harus berurusan dengan berbagai masalah dengan menghubungkan dependensi dan memuat rakitan. Kami akan membicarakan ini.

Struktur posting:


  1. Masalah Ketergantungan
  2. Pemuatan rig yang ketat

  3. .NET Core

  4. Unduhan perakitan debug


Apa sajakah masalah ketergantungan?


Ketika mereka mulai mengembangkan .NET Framework pada awal 2000-an, masalah Ketergantungan neraka sudah diketahui, ketika di semua pustaka pengembang mengizinkan perubahan, dan pustaka ini menjadi tidak kompatibel untuk digunakan dengan kode yang sudah dikompilasi. Bagaimana mengatasi masalah seperti itu? Solusi pertama sudah jelas. Selalu pertahankan kompatibilitas ke belakang. Tentu saja, ini tidak terlalu realistis, karena memecah perubahan sangat mudah dimasukkan ke dalam kode. Misalnya:



Memecah perubahan dan pustaka .NET

Ini adalah contoh khusus untuk .NET. Kami memiliki metode, dan kami memutuskan untuk menambahkan parameter dengan nilai default. Kode akan terus dikompilasi jika kita merakitnya kembali, tetapi biner akan menjadi dua metode yang sama sekali berbeda: satu metode memiliki argumen nol, metode kedua memiliki satu argumen. Jika pengembang di dalam dependensi memecah kompatibilitas ke belakang dengan cara ini, maka kami tidak akan dapat menggunakan kode yang dikompilasi dengan dependensi ini pada versi sebelumnya.

Solusi kedua untuk masalah ketergantungan adalah menambahkan versi perpustakaan, majelis - apa pun. Mungkin ada aturan versi yang berbeda, intinya adalah bahwa kita dapat membedakan versi berbeda dari pustaka yang sama satu sama lain, dan Anda dapat memahami apakah pembaruan akan rusak atau tidak rusak. Sayangnya, segera setelah kami memperkenalkan versi-versi tersebut, muncul masalah yang berbeda.



Versi neraka adalah ketidakmampuan untuk menggunakan dependensi yang kompatibel dengan biner, tetapi pada saat yang sama memiliki versi yang tidak sesuai dengan runtime atau komponen lain yang memeriksa versi ini. Dalam. NET, manifestasi khas dari versi neraka adalah FileLoadException, meskipun file tersebut terletak pada disk, tetapi karena alasan tertentu tidak dimuat dengan runtime.



Dalam .NET, majelis memiliki banyak versi berbeda - mereka mencoba memperbaiki versi neraka dengan berbagai cara, dan melihat apa yang terjadi. Kami punya paket System.Collections.Immutable. Banyak orang mengenalnya. Ia memiliki versi terbaru dari paket NuGet 1.6.0. Ini berisi perpustakaan, perakitan dengan versi 1.2.4.0. Anda telah menerima bahwa Anda tidak memiliki versi perpustakaan build 1.2.4.0. Bagaimana memahami bahwa itu terletak pada paket NuGet 1.6.0? Ini tidak akan mudah. Selain Versi Perakitan, perpustakaan ini memiliki beberapa versi lagi. Misalnya, Versi File Perakitan, Versi Informasi Perakitan. Paket NuGet ini sebenarnya berisi tiga majelis berbeda dengan versi yang sama (untuk versi berbeda dari .NET Standard).

.NET Documentation
Opbuild standard

Banyak dokumentasi telah ditulis tentang cara bekerja dengan majelis di .NET. Ada .NET Guide untuk mengembangkan aplikasi modern untuk .NET dengan mempertimbangkan .NET Framework, .NET Standard, .NET Core, open source, dan semua itu. Sekitar 30% dari seluruh dokumen dikhususkan untuk memuat majelis. Kami akan menganalisis masalah dan contoh spesifik yang mungkin muncul.

Mengapa semua ini perlu? Pertama, untuk menghindari menginjak menyapu. Kedua, Anda dapat membuat hidup lebih mudah bagi pengguna perpustakaan Anda karena dengan perpustakaan Anda mereka tidak akan memiliki masalah ketergantungan yang biasa mereka alami. Ini juga akan membantu Anda mengatasi migrasi aplikasi yang kompleks ke .NET Core. Dan yang paling penting, Anda bisa menjadi SRE, ini adalah insinyur Pengarah Senior (Binding), tempat semua orang di tim datang dan bertanya bagaimana cara menulis pengalihan lain.

Pemuatan perakitan yang ketat


Pemuatan perakitan yang ketat adalah masalah utama yang dihadapi pengembang di .NET Framework. Itu diungkapkan dalam FileLoadException. Sebelum pindah ke perakitan ketat memuat sendiri, izinkan saya mengingatkan Anda tentang beberapa hal dasar.

Ketika Anda membangun aplikasi .NET, hasilnya adalah beberapa artefak, yang biasanya terletak di Bin / Debug atau di Bin / Rilis, dan berisi kumpulan rakitan dan file konfigurasi tertentu. Sidang akan merujuk satu sama lain dengan nama, nama Majelis. Penting untuk dipahami bahwa tautan rakitan terletak langsung di rakitan yang mereferensikan rakitan ini, tidak ada file konfigurasi ajaib tempat rujukan rakitan ditulis. Meskipun menurut Anda mungkin ada file seperti itu. Rujukan ada di majelis sendiri dalam bentuk biner.

Di .NET, ada proses penyelesaian rakitan - ini adalah saat definisi rakitan sudah diubah menjadi rakitan nyata, yang ada di disk atau dimuat di suatu tempat di memori. Penyelesaian perakitan dilakukan dua kali: pada tahap pembuatan, saat Anda memiliki referensi di * .csproj, dan saat runtime, saat Anda memiliki referensi di dalam rakitan, dan berdasarkan aturan mereka berubah menjadi rakitan yang dapat diunduh.

// Nama sederhana
MyAssembly, Versi = 6.0.0.0,
Budaya = netral, PublicKeyToken = null

// Nama kuat
Newtonsoft.Json, Versi = 6.0.0.0,
Budaya = netral, PublicKeyToken = 30ad4fe6b2a6aeed // PublicKey


Mari kita beralih ke masalah. Nama majelis ada dua jenis utama. Jenis pertama nama majelis adalah Nama sederhana. Mereka mudah diidentifikasi oleh fakta bahwa mereka memiliki PublicKeyToken = null. Ada nama yang kuat, mudah untuk mengidentifikasi mereka dengan fakta bahwa PublicKeyToken mereka bukan nol, tetapi beberapa nilai.



Mari kita ambil contoh. Kami memiliki program yang bergantung pada pustaka dengan utilitas MyUtils, dan versi MyUtils adalah 9.0.0.0. Program yang sama memiliki tautan ke perpustakaan lain. Perpustakaan ini juga ingin menggunakan MyUtils, tetapi versi 6.0.0.0. MyUtils versi 9.0.0.0, dan versi 6.0.0.0 memiliki PublicKeyToken = null, yaitu, mereka memiliki nama Sederhana. Versi mana yang akan jatuh ke dalam artefak biner, 6.0.0.0 atau 9.0.0.0? Versi ke-9. Bisakah MyLibrary menggunakan MyUtils versi 9.0.0.0, yang masuk ke dalam artefak biner?



Bahkan, itu bisa, karena MyUtils memiliki nama Sederhana dan, karenanya, memuat perakitan ketat tidak ada untuk itu.



Contoh lain. Alih-alih MyUtils, kami memiliki perpustakaan lengkap dari NuGet, yang memiliki nama yang kuat. Sebagian besar perpustakaan di NuGet memiliki nama yang kuat.



Pada tahap build, versi 9.0.0.0 disalin ke BIN, tetapi saat runtime kita mendapatkan yang terkenal FileLoadException. Agar MyLibrary, yang ingin versi 6.0.0.0 untuk Newtonsoft.Json, untuk dapat menggunakan versi 9.0.0.0, Anda harus pergi dan menulis Binding redirect to App.config.

Pengalihan mengikat





Mengarahkan versi perakitan

Ini menyatakan bahwa perakitan dengan nama seperti itu dan publicKeyToken tersebut harus diarahkan dari berbagai versi ke berbagai versi. Tampaknya ini adalah catatan yang sangat sederhana, tetapi bagaimanapun juga terletak di sini App.config, tetapi bisa juga di file lain. Ada file machine.configdi dalam .NET Framework, di dalam runtime, di mana beberapa set pengalihan standar didefinisikan, yang mungkin berbeda dari versi ke versi .NET Framework. Mungkin terjadi bahwa pada 4.7.1 tidak ada yang berhasil untuk Anda, tetapi pada 4.7.2 itu sudah berfungsi, atau sebaliknya. Anda harus ingat bahwa pengalihan tidak hanya berasal dari Anda .App.config, dan ini harus diperhitungkan saat debugging.

Kami menyederhanakan penulisan arahan ulang


Tidak ada yang mau menulis pengalihan Binding dengan tangan mereka. Mari berikan tugas ini ke MSBuild!



Cara mengaktifkan dan menonaktifkan pengalihan yang mengikat otomatis

Beberapa tips tentang cara menyederhanakan bekerja dengan pengalihan Binding. Tip Satu: Aktifkan Binding pengalihan generasi otomatis di MSBuild. Diaktifkan oleh properti di *.csproj. Saat membangun proyek, itu akan jatuh ke dalam artefak biner App.config, yang menunjukkan pengalihan ke versi perpustakaan yang berada di artefak yang sama. Ini hanya berfungsi untuk menjalankan aplikasi, aplikasi konsol, WinExe. Untuk perpustakaan, ini tidak berfungsi, karena untuk perpustakaanApp.configpaling sering itu tidak relevan, karena relevan untuk aplikasi yang meluncurkan dan memuat rakitan itu sendiri. Jika Anda membuat konfigurasi untuk perpustakaan, maka dalam aplikasi beberapa dependensi mungkin juga berbeda dari yang ketika membangun perpustakaan, dan ternyata konfigurasi untuk perpustakaan tidak masuk akal. Namun demikian, kadang-kadang untuk perpustakaan konfigurasi masih masuk akal.



Situasi ketika kita menulis tes. Tes biasanya ditemukan di ClassLibrary dan mereka juga membutuhkan arahan ulang. Kerangka kerja pengujian dapat mengenali bahwa perpustakaan dengan tes memiliki dll-config, dan menukar pengalihan yang ada di dalamnya untuk kode dari tes. Anda dapat membuat arahan ulang ini secara otomatis. Jika kita memiliki format lama*.csproj, bukan SDK-style, Anda dapat pergi dengan cara sederhana, mengubah OutputType ke Exe dan menambahkan titik entri kosong, ini akan memaksa MSBuild untuk menghasilkan arahan ulang. Anda bisa pergi ke arah lain dan menggunakan retas. Anda dapat menambahkan properti lain ke *.csproj, yang membuat MSBuild mempertimbangkan bahwa untuk OutputType ini Anda masih perlu menghasilkan pengalihan Binding. Metode ini, meskipun terlihat seperti peretasan, akan memungkinkan Anda untuk menghasilkan arahan ulang untuk perpustakaan yang tidak dapat diulang di Exe, dan untuk jenis proyek lainnya (kecuali tes).

Untuk format baru, *.csprojarahan ulang akan dibuat sendiri jika Anda menggunakan Microsoft.NET.Test.Sdk modern.

Kiat ketiga: jangan gunakan Binding redirect generation dengan NuGet. NuGet memiliki kemampuan untuk menghasilkan pengalihan Binding untuk pustaka yang beralih dari paket ke versi terbaru, tetapi ini bukan pilihan terbaik. Semua pengalihan ini harus ditambahkan App.configdan dikomit, dan jika Anda menghasilkan pengalihan menggunakan MSBuild, maka pengalihan dihasilkan selama membangun. Jika Anda komit, Anda mungkin memiliki menggabungkan konflik. Anda sendiri bisa saja lupa untuk memperbarui pengalihan Binding dalam file, dan jika mereka dihasilkan selama membangun, Anda tidak akan lupa.



Menyelesaikan Referensi Majelis
Menghasilkan Pengalihan Mengikat

Pekerjaan rumah bagi mereka yang ingin lebih memahami bagaimana generasi pengalihan Binding bekerja: cari tahu cara kerjanya, lihat ini dalam kode. Pergi ke direktori .NET, pergilah ke mana-mana dengan properti nama, yang digunakan untuk mengaktifkan pembuatan. Ini umumnya merupakan pendekatan yang umum, jika ada beberapa properti aneh untuk MSBuild, Anda dapat pergi dan mengambil keuntungan dari penggunaannya. Untungnya, properti biasanya digunakan dalam konfigurasi XML, dan Anda dapat dengan mudah menemukan penggunaannya.

Jika Anda memeriksa apa yang ada di target XML ini, Anda akan melihat bahwa properti ini memicu dua tugas MSBuild. Tugas pertama disebut ResolveAssemblyReferences, dan menghasilkan satu set pengalihan yang ditulis ke file. Tugas kedua GenerateBindingRedirectsmenulis hasil tugas pertamaApp.config. Ada logika XML yang sedikit memperbaiki operasi tugas pertama dan menghapus beberapa arahan ulang yang tidak perlu, atau menambahkan yang baru.

Alternatif untuk Konfigurasi XML


Tidak selalu nyaman untuk menjaga pengalihan dalam konfigurasi XML. Kami mungkin memiliki situasi di mana aplikasi mengunduh plugin, dan plugin ini menggunakan perpustakaan lain yang memerlukan pengalihan. Dalam hal ini, kami mungkin tidak menyadari set pengalihan yang kami butuhkan, atau kami mungkin tidak ingin menghasilkan XML. Dalam situasi seperti itu, kita dapat membuat AppDomain dan, ketika itu dibuat, masih mentransfer ke tempat di mana XML dengan pengalihan yang diperlukan berada. Kami juga dapat menangani kesalahan pemuatan perakitan saat runtime. Rantime .NET memberikan kesempatan seperti itu.

AppDomain.CurrentDomain.AssemblyResolve += (sender, eventArgs) => 
{ 
   var name = eventArgs.Name; 
   var requestingAssembly = eventArgs.RequestingAssembly; 
   
   return Assembly.LoadFrom(...); // PublicKeyToken should be equal
};


Itu memiliki sebuah acara, itu disebut CurrentDomain.AssemblyResolve. Dengan berlangganan ke acara ini, kami akan menerima kesalahan tentang semua unduhan perakitan yang gagal. Kami mendapatkan nama majelis yang tidak memuat, dan kami mendapatkan majelis perakitan yang meminta majelis pertama untuk memuat. Di sini kita dapat memuat rakitan secara manual dari tempat yang tepat, misalnya menjatuhkan versi, hanya mengambilnya dari file, dan mengembalikan acara ini dari handler. Atau mengembalikan nol jika kita tidak memiliki apa pun untuk dikembalikan, jika kita tidak dapat memuat rakitan. PublicKeyToken harus sama, majelis dengan PublicKeyToken yang berbeda sama sekali tidak berteman satu sama lain.



Acara ini hanya berlaku untuk satu domain aplikasi. Jika plugin kami membuat AppDomain di dalamnya, maka pengalihan ini di runtime tidak akan berfungsi di dalamnya. Anda harus entah bagaimana berlangganan acara ini di semua AppDomain yang dibuat oleh plugin. Kita dapat melakukan ini menggunakan AppDomainManager.

AppDomainManager adalah majelis terpisah yang berisi kelas yang mengimplementasikan antarmuka spesifik, dan salah satu metode antarmuka ini akan memungkinkan Anda untuk menginisialisasi AppDomain baru yang dibuat dalam aplikasi. Setelah AppDomain dibuat, metode ini akan dipanggil. Di dalamnya Anda dapat berlangganan acara ini.

Pemuatan perakitan ketat & .NET Core


Di .NET Core tidak ada masalah yang disebut "Pemuatan perakitan ketat", yang disebabkan oleh fakta bahwa majelis yang ditandatangani memerlukan versi yang diminta secara tepat. Ada persyaratan lain. Untuk semua majelis, terlepas dari apakah mereka ditandatangani oleh nama yang kuat atau tidak, diperiksa bahwa versi yang dimuat dalam runtime lebih besar atau sama dengan yang sebelumnya. Jika kita berada dalam situasi aplikasi dengan plugin, kita mungkin memiliki situasi seperti itu plugin dibangun, misalnya, dari versi baru SDK, dan aplikasi yang diunduh menggunakan versi lama SDK sejauh ini, dan bukannya berantakan, kita juga dapat berlangganan acara ini, tetapi sudah dalam. NET Core, dan juga memuat perakitan yang kita miliki. Kita dapat menulis kode ini:
AppDomain.CurrentDomain.AssemblyResolve += (s, eventArgs) => 
{ 
     CheckForRecursion(); 
     var name = eventArgs.Name;
     var requestingAssembly = eventArgs.RequestingAssembly; 
    
     name.Version = new Version(0, 0); 
     
     return Assembly.Load(name); 
};


Kami memiliki nama majelis yang tidak bisa boot, kami membatalkan versi dan menyebutnya Assembly.Loaddari versi yang sama. Tidak akan ada rekursi di sini, karena saya sudah memeriksa rekursi.



Itu perlu untuk mengunduh MyUtils versi 0.0.2.0. Di BIN, kami memiliki MyUtils versi 0.0.1.0. Kami membuat pengalihan dari versi 0.0.2.0 ke versi 0.0. Versi 0.0.1.0 tidak akan dimuat bersama kami. Sebuah jalan keluar akan terbang bagi kami bahwa tidak mungkin memuat unit dengan versi 0.0.2 16–1 . 2 16–1 .

new Version(0, 0) == new Version(0, 0, -1, -1) 

class Version { 
     readonly int _Build; 
     readonly int _Revision; 
     readonly int _Major; 
     readonly int _Minor; 
} 
(ushort) -1 == 65535


Di kelas Versi, tidak semua komponen wajib, dan bukannya komponen opsional –1 disimpan, tetapi di suatu tempat di dalam, terjadi overflow, dan 2 16-1 diperoleh . Jika tertarik, Anda dapat mencoba menemukan di mana persisnya terjadi overflow.



Jika Anda bekerja dengan rakitan refleksi dan ingin mendapatkan semua jenis, mungkin tidak semua jenis bisa mendapatkan metode GetTypes Anda. Majelis memiliki kelas yang mewarisi dari kelas lain yang ada di perakitan yang tidak dimuat.

static IEnumerable GetTypesSafe(this Assembly assembly) 
{ 
    try 
    { 
        return assembly.GetTypes(); 
    }
    catch (ReflectionTypeLoadException e) 
   { 
        return e.Types.Where(x => x != null); 
    } 
}



Dalam hal ini, masalahnya adalah ReflectionTypeLoadException akan dibuang. Di dalamnya ReflectionTypeLoadExceptionada properti di mana ada jenis-jenis yang masih berhasil dimuat. Tidak semua perpustakaan populer mempertimbangkan hal ini. AutoMapper, setidaknya salah satu versinya, jika dihadapkan dengan ReflectionTypeLoadException, baru saja jatuh, alih-alih pergi dan memilih jenis dari dalam pengecualian.

Penamaan yang kuat


Majelis yang bernama kuat

Mari kita bicara tentang apa yang menyebabkan pemuatan perakitan yang ketat, ini adalah nama yang kuat.
Nama Kuat adalah tanda tangan majelis oleh beberapa kunci pribadi menggunakan enkripsi asimetris. PublicKeyToken adalah hash kunci publik dari majelis ini.

Penamaan Kuat memungkinkan Anda untuk membedakan antara majelis berbeda yang memiliki nama yang sama. Misalnya, MyUtils bukan nama yang unik, mungkin ada beberapa majelis dengan nama itu, tetapi jika Anda menandatangani Nama yang kuat, mereka akan memiliki PublicKeyToken yang berbeda dan kami dapat membedakannya dengan cara ini. Nama yang kuat diperlukan untuk beberapa skenario pemuatan perakitan.

Misalnya, untuk memasang perakitan di Global Assembly Cache atau mengunduh beberapa versi berdampingan sekaligus. Yang paling penting, majelis bernama kuat hanya bisa merujuk majelis bernama kuat lainnya. Karena beberapa pengguna ingin menandatangani bangunan mereka dengan nama yang kuat, pengembang perpustakaan juga menandatangani perpustakaan mereka, sehingga lebih mudah bagi pengguna untuk menginstalnya, sehingga pengguna tidak perlu menandatangani ulang perpustakaan ini.

Nama kuat: Warisan?


Penamaan yang kuat dan perpustakaan .NET

Microsoft secara eksplisit mengatakan pada MSDN bahwa Anda tidak boleh menggunakan nama yang kuat untuk tujuan keamanan, bahwa mereka menyediakan hanya untuk membedakan majelis yang berbeda dengan nama yang sama. Kunci perakitan tidak dapat diubah dengan cara apa pun, jika Anda mengubahnya, maka Anda akan memutuskan pengalihan ke semua pengguna Anda. Jika Anda memiliki bagian pribadi dari kunci untuk Nama kuat bocor ke akses publik, maka Anda tidak dapat menarik tanda tangan ini dengan cara apa pun. Format file SNK tempat Strong name berada tidak memberikan peluang seperti itu, dan format lain untuk menyimpan kunci setidaknya berisi tautan ke Daftar Pencabutan Sertifikat CRL, yang dapat dipahami bahwa sertifikat ini tidak lagi valid. Tidak ada yang seperti itu di SNK.

Panduan Open-source memiliki rekomendasi berikut. Pertama, tambahan untuk tujuan keamanan menggunakan teknologi lain. Kedua, jika Anda memiliki pustaka sumber terbuka, umumnya disarankan agar Anda mengkomit bagian pribadi dari kunci repositori, sehingga lebih mudah bagi orang untuk mem-fork perpustakaan Anda, membangun kembali dan meletakkannya di aplikasi yang sudah jadi. Ketiga, jangan pernah ganti nama Strong. Terlalu merusak. Terlepas dari kenyataan bahwa itu terlalu destruktif dan ditulis tentang hal itu dalam panduan Open-source, Microsoft terkadang memiliki masalah dengan perpustakaannya sendiri.



Ada perpustakaan yang disebut System.Reactive. Sebelumnya, ini adalah beberapa paket NuGet, salah satunya adalah Rx-Linq. Ini hanya contoh, sama untuk sisa paket. Di versi kedua, itu ditandatangani dengan kunci Microsoft. Pada versi ketiga, ia pindah ke repositori di proyek github.com/dotnet dan mulai memiliki tanda tangan .NET Foundation. Perpustakaan, pada kenyataannya, telah mengubah nama yang kuat. Paket NuGet diubah namanya, tetapi rakitan itu disebut di dalam persis sama seperti sebelumnya. Bagaimana cara mengalihkan dari versi kedua ke yang ketiga? Pengalihan ini tidak dapat dilakukan.

Validasi nama yang kuat


Cara: Nonaktifkan fitur bypass nama yang kuat

Argumen lain bahwa nama yang kuat sudah merupakan sesuatu yang merupakan masa lalu dan tetap murni formal adalah bahwa mereka tidak divalidasi. Kami memiliki rakitan yang telah ditandatangani dan kami ingin memperbaiki beberapa jenis bug di dalamnya, tetapi kami tidak memiliki akses ke sumbernya. Kami hanya dapat mengambil dnSpy - ini adalah utilitas yang memungkinkan Anda untuk mendekompilasi dan memperbaiki rakitan yang sudah dikompilasi. Semuanya akan bekerja untuk kita. Karena secara default, memotong validasi nama yang kuat diaktifkan, yaitu, hanya memeriksa bahwa PublicKeyToken sama, dan integritas tanda tangan itu sendiri tidak dicentang. Mungkin ada studi lingkungan di mana tanda tangan masih diverifikasi, dan di sini contoh nyata adalah IIS. Integritas tanda tangan diperiksa pada IIS (Bypass validasi nama kuat dinonaktifkan secara default), dan semuanya akan rusak jika kita mengedit rakitan yang ditandatangani.

Tambahan:Anda dapat menonaktifkan verifikasi tanda tangan untuk majelis menggunakan tanda publik. Dengan itu, hanya kunci publik yang digunakan untuk penandatanganan, yang menjamin keamanan nama majelis. Kunci publik yang digunakan oleh Microsoft diposting di sini .
Di Rider, tanda publik dapat diaktifkan di properti proyek.





Kapan mengubah versi fileassembly

Panduan open-source juga menawarkan beberapa kebijakan Versi, yang tujuannya adalah untuk mengurangi jumlah pengalihan Binding yang diperlukan dan perubahannya untuk pengguna di NET Framework. Kebijakan Versi ini adalah bahwa kita tidak boleh mengubah Versi Majelis terus-menerus. Ini, tentu saja, dapat menyebabkan masalah dengan pemasangan di GAC, sehingga gambar asli yang diinstal mungkin tidak sesuai dengan perakitan dan Anda harus melakukan kompilasi JIT lagi, tetapi, menurut saya, ini lebih jahat daripada masalah dengan versi. Dalam kasus CrossGen, majelis asli tidak diinstal secara global - tidak akan ada masalah.

Sebagai contoh, paket NuGet Newtonsoft.Json, ia memiliki beberapa versi: 12.0.1, 12.0.2, dan seterusnya - semua paket ini memiliki rakitan dengan versi 12.0.0.0. Rekomendasi adalah bahwa Versi Majelis harus diperbarui ketika versi utama dari paket NuGet berubah.

temuan


Ikuti tips untuk .NET Framework: hasilkan pengalihan secara manual dan coba gunakan versi dependensi yang sama di semua proyek dalam solusi Anda. Ini harus secara signifikan meminimalkan jumlah arahan ulang. Anda memerlukan penamaan yang kuat hanya jika Anda memiliki skenario pemuatan build khusus di mana diperlukan, atau Anda sedang mengembangkan perpustakaan dan ingin menyederhanakan kehidupan bagi pengguna yang benar-benar membutuhkan penamaan yang kuat. Jangan mengubah nama yang kuat.

.NET Standard


Kami beralih ke .NET Standard. Ini sangat terkait erat dengan Versi hell di .NET Framework. .NET Standard adalah alat untuk menulis pustaka yang kompatibel dengan berbagai implementasi platform .NET. Implementasi merujuk pada .NET Framework, .NET Core, Mono, Unity, dan Xamarin.



* Tautan ke dokumentasi

Ini adalah tabel dukungan .NET Standard untuk berbagai versi dari versi runtimes yang berbeda. Dan di sini kita dapat melihat bahwa .NET Framework sama sekali tidak mendukung .NET Standard versi 2.1. Rilis .NET Framework, yang akan mendukung .NET Standard 2.1 dan yang lebih baru, belum direncanakan. Jika Anda sedang mengembangkan perpustakaan dan ingin itu berfungsi untuk pengguna di .NET Framework, Anda harus memiliki target untuk .NET Standard 2.0. Selain fakta bahwa .NET Framework tidak mendukung versi terbaru dari .NET Standard, mari perhatikan asterisk. .NET Framework 4.6.1 mendukung .NET Standard 2.0, tetapi dengan tanda bintang. Ada catatan kaki secara langsung di dokumentasi, di mana saya mendapatkan tabel ini.



Pertimbangkan contoh proyek. Aplikasi pada .NET Framework yang memiliki satu ketergantungan yang menargetkan .NET Standard. Sesuatu seperti ini: ConsoleApp dan ClassLibrary. Perpustakaan Target .NET Standard. Ketika kami menyatukan proyek ini, akan seperti ini di BIN kami.



Kami akan memiliki seratus DLL di sana, yang hanya satu yang terkait dengan aplikasi, semuanya datang untuk mendukung .NET Standard. Faktanya adalah bahwa .NET Standard 2.0 muncul lebih lambat dari .NET Framework 4.6.1, tetapi pada saat yang sama mereka ternyata kompatibel dengan API, dan para pengembang memutuskan untuk menambahkan dukungan Standard 2.0 ke .NET 4.6.1. Kami melakukannya bukan secara asli (dengan memasukkan netstandard.dlldalam runtime itu sendiri), tetapi sedemikian rupa sehingga .NET Standard * .dll dan semua fasad perakitan lainnya ditempatkan langsung di BIN.



Jika kita melihat dependensi versi .NET Framework yang kita targetkan dan jumlah perpustakaan yang jatuh ke dalam BIN, kita akan melihat bahwa tidak banyak dari mereka di 4.7.1, dan karena 4.7.2 tidak ada perpustakaan sama sekali, dan .NET Standar didukung di sana secara asli.



Ini adalah tweet dari salah satu pengembang .NET, yang menjelaskan masalah ini dan merekomendasikan untuk menggunakan .NET Framework versi 4.7.2 jika kita memiliki perpustakaan .NET Standard. Bahkan dengan versi 2.0 di sini, tetapi dengan versi 1.5.

temuan


Jika memungkinkan, naikkan Kerangka Target dalam proyek Anda ke setidaknya 4.7.1, lebih disukai 4.7.2. Jika Anda mengembangkan perpustakaan untuk membuat hidup lebih mudah bagi pengguna perpustakaan, buat Target terpisah untuk .NET Framework, itu akan menghindari sejumlah besar dll yang dapat bertentangan dengan sesuatu.

.NET Core


Mari kita mulai dengan teori umum. Kami akan membahas bagaimana kami meluncurkan JetBrains Rider di .NET Core, dan mengapa kami harus membicarakannya sama sekali. Rider adalah proyek yang sangat besar, ia memiliki solusi perusahaan besar dengan sejumlah besar proyek yang berbeda, sistem dependensi yang kompleks, Anda tidak bisa begitu saja mengambilnya dan bermigrasi ke runtime lain pada satu waktu. Untuk melakukan ini, kita harus menggunakan beberapa peretasan, yang juga akan kita analisis.

Aplikasi .NET Core


Seperti apa tampilan aplikasi .NET Core? Bergantung pada bagaimana tepatnya itu digunakan, apa yang akhirnya akan terjadi. Kami dapat memiliki beberapa skenario. Yang pertama adalah penyebaran yang bergantung pada Kerangka. Ini sama dengan di .NET Framework ketika aplikasi menggunakan runtime yang sudah diinstal sebelumnya di komputer. Ini bisa menjadi penyebaran mandiri, ini adalah saat aplikasi membawa runtime. Dan mungkin ada penyebaran file tunggal, ini adalah ketika kita mendapatkan satu file exe, tetapi dalam kasus .NET Core di dalam file exe ini ada artefak aplikasi mandiri, ini adalah arsip self-extracting.



Kami hanya akan mempertimbangkan penyebaran yang bergantung pada Kerangka. Kami memiliki dll dengan aplikasi, ada dua file konfigurasi, yang pertama diperlukan, ini runtimeconfig.jsondandeps.json. Dimulai dengan .NET Core 3.0, file exe dihasilkan yang diperlukan untuk membuat aplikasi lebih nyaman untuk dijalankan, sehingga Anda tidak perlu memasukkan perintah .NET jika kita berada di Windows. Ketergantungan jatuh ke dalam artefak ini, dimulai dengan .NET Core 3.0, di .NET Core 2.1 Anda harus menerbitkan atau menggunakan properti lain di *.csproj.

Kerangka berbagi, .runtimeconfig.json





.runtimeconfig.jsonberisi pengaturan runtime yang diperlukan untuk menjalankannya. Ini menunjukkan di bawah Kerangka Bersama mana aplikasi akan diluncurkan, dan terlihat seperti ini. Kami menunjukkan bahwa aplikasi akan berjalan di bawah "Microsoft.NETCore.App" versi 3.0.0, mungkin ada Kerangka Bersama lainnya. Pengaturan lain mungkin juga ada di sini. Misalnya, Anda dapat mengaktifkan pengumpul Sampah server.



.runtimeconfig.jsondihasilkan selama perakitan proyek. Dan jika kita ingin memasukkan server GC, maka kita perlu memodifikasi file ini terlebih dahulu, bahkan sebelum kita merakit proyek, atau menambahkannya dengan tangan. Anda dapat menambahkan pengaturan Anda di sini seperti ini. Kami dapat menyertakan properti di *.csproj, jika properti tersebut disediakan oleh pengembang .NET, atau jika properti tidak disediakan, kami dapat membuat file bernamaruntimeconfig.template.jsondan tulis pengaturan yang diperlukan di sini. Selama perakitan, pengaturan lain yang diperlukan akan ditambahkan ke templat ini, misalnya, Kerangka Bersama yang sama.



Kerangka Bersama adalah seperangkat runtime dan pustaka. Bahkan, hal yang sama dengan runtime .NET Framework, yang dulunya hanya diinstal sekali pada mesin dan untuk semua adalah satu versi. Shared Framework, dan, tidak seperti runtime .NET Framework tunggal, dapat diversi, aplikasi yang berbeda dapat menggunakan versi yang berbeda dari runtime yang diinstal. Kerangka Bersama juga dapat diwarisi. Kerangka Bersama itu sendiri dapat dilihat di lokasi tersebut pada disk seperti yang umumnya diinstal pada sistem.



Ada beberapa Framework Bersama standar, misalnya, Microsoft.NETCore.App, yang menjalankan aplikasi konsol konvensional, AspNetCore.App, untuk aplikasi web, dan WindowsDesktop.App, Framework Bersama baru di .NET Core 3, yang menjalankan aplikasi desktop pada Formulir Windows dan WPF. Dua Framework Bersama terakhir pada dasarnya melengkapi yang pertama diperlukan untuk aplikasi konsol, yaitu, mereka tidak membawa runtime baru, tetapi hanya melengkapi yang sudah ada dengan perpustakaan yang diperlukan. Warisan ini terlihat seperti ada juga dalam direktori Shared Framework runtimeconfig.jsondi mana dasar Shared Framework ditentukan.

Manifes ketergantungan ( .deps.json)



Penjelajahan default - .NET Core

File konfigurasi kedua adalah ini .deps.json. File ini berisi deskripsi semua dependensi aplikasi atau Kerangka Bersama, atau perpustakaan, perpustakaan .deps.jsonjuga memilikinya. Ini berisi semua dependensi, termasuk yang transitif. Dan perilaku runtime .NET Core berbeda tergantung pada apakah .deps.jsonaplikasi memilikinya atau tidak. Jika .deps.jsontidak, aplikasi akan dapat memuat semua majelis yang ada dalam Kerangka Bersama atau dalam direktori BIN-nya. Jika ada .deps.json, maka validasi diaktifkan. Jika salah satu majelis yang terdaftar dalam .deps.jsontidak, maka aplikasi tidak akan dimulai. Anda akan melihat kesalahan yang disajikan di atas. Jika aplikasi mencoba memuat beberapa perakitan di runtime, yang.deps.json jika, misalnya, menggunakan metode beban perakitan atau selama proses penyelesaian rakitan, Anda akan melihat kesalahan yang sangat mirip dengan memuat perakitan ketat.

Pengendara Jetbrains


Rider adalah .NET IDE. Tidak semua orang tahu bahwa Rider adalah IDE yang terdiri dari frontend berdasarkan IntelliJ IDEA dan ditulis dalam Java dan Kotlin, dan backend. Backend pada dasarnya adalah R #, yang dapat berkomunikasi dengan IntelliJ IDEA. Backend ini adalah aplikasi .NET lintas platform sekarang.
Di mana ia berjalan? Windows menggunakan .NET Framework, yang diinstal di komputer pengguna. Pada sistem informasi lain, di Linux dan Mac, Mono digunakan.

Ini bukan solusi ideal ketika ada runtimes berbeda di mana-mana, dan saya ingin datang ke keadaan berikutnya sehingga Rider berjalan di .NET Core. Untuk meningkatkan kinerja, karena dalam. NET Core semua fitur terbaru dikaitkan dengan ini. Untuk mengurangi konsumsi memori. Sekarang ada masalah dengan cara Mono bekerja dengan memori.

Beralih ke .NET Core akan memungkinkan Anda untuk meninggalkan warisan, teknologi yang tidak didukung dan memungkinkan untuk memperbaiki beberapa perbaikan untuk masalah yang ditemukan di runtime. Beralih ke .NET Core akan memungkinkan Anda untuk mengontrol versi runtime, yaitu, Rider tidak akan lagi berjalan di .NET Framework yang diinstal pada komputer pengguna, tetapi pada versi tertentu .NET Core, yang dapat dilarang, dalam bentuk penyebaran mandiri. Transisi ke .NET Core pada akhirnya akan memungkinkan penggunaan API baru yang diimpor secara khusus di Core.

Sekarang tujuannya adalah untuk meluncurkan prototipe, meluncurkannya, hanya untuk memeriksa bagaimana itu akan bekerja, apa poin potensial kegagalan, komponen mana yang harus ditulis ulang lagi, yang akan membutuhkan pemrosesan global.

Fitur yang membuat menerjemahkan Rider ke .NET Core menjadi sulit


Visual Studio, bahkan jika R # tidak diinstal di dalamnya, lumpuh dari Memori Kehabisan pada solusi besar, di dalamnya ada proyek dengan SDK-style * .csproj . SDK-style * .csproj adalah salah satu syarat utama untuk relokasi .NET Core penuh.

Ini adalah masalah karena Rider didasarkan pada R #, mereka tinggal di repositori yang sama, pengembang R # ingin menggunakan Visual Studio untuk mengembangkan produk mereka sendiri dalam produk mereka untuk membuatnya menjadi makanan. Di R # ada tautan pustaka spesifik untuk kerangka kerja yang dengannya Anda perlu melakukan sesuatu. Di Windows, kita bisa menggunakan Framework untuk aplikasi desktop, dan di Linux dan Mac, Mock sudah digunakan untuk pustaka Windows dengan fungsionalitas minimal.

Keputusan


Kami memutuskan untuk tetap menggunakan yang lama untuk saat ini *.csproj, berkumpul di bawah Kerangka penuh, tetapi karena majelis Kerangka dan Core adalah biner yang kompatibel, jalankan di Core. Kami tidak menggunakan fitur yang tidak kompatibel, menambahkan semua file konfigurasi yang diperlukan secara manual dan mengunduh versi dependensi khusus untuk .NET Core, jika ada.

Peretasan apa yang harus Anda lakukan?


Satu peretasan: kami ingin memanggil metode yang hanya tersedia di Kerangka, misalnya, metode ini diperlukan di R #, tetapi tidak pada Core. Masalahnya adalah bahwa jika tidak ada metode, maka metode yang memanggilnya selama kompilasi JIT akan jatuh lebih awal MissingMethodException. Artinya, metode yang tidak ada telah merusak metode yang memanggilnya.

static void Method() { 
  if (NetFramework) 
     CallNETFrameworkOnlyMethod();

  ... 
} 
[MethodImpl(MethodImplOptions.NoInlining)] 
static void CallNETFrameworkOnlyMethod() { 
  NETFrameworkOnlyMethod(); 
}


Solusinya ada di sini: kami membuat panggilan ke metode yang tidak kompatibel ke metode yang terpisah. Ada satu masalah lagi: metode seperti itu bisa menjadi inline, oleh karena itu kami menandainya dengan atribut NoInlining.

Retas nomor dua: kita harus dapat memuat majelis di jalur relatif. Kami memiliki satu perakitan untuk Framework, ada versi khusus untuk .NET Core. Bagaimana cara mengunduh versi .NET Core untuk .NET Core?



Mereka akan membantu kita .deps.json. Mari kita lihat .deps.jsonpustaka System.Diagnostics.PerformanceCounter. Perpustakaan seperti itu luar biasa dalam hal perpustakaannya.deps.json. Ini memiliki bagian runtime, di mana satu versi perpustakaan dengan jalur relatifnya ditunjukkan. Pustaka ini, rakitan akan dimuat pada semua runtimes, dan itu hanya membuang eksekusi. Jika, misalnya, dimuat di Linux, PerformanceCounter tidak berfungsi pada desain di Linux, dan PlatformNotSupportedException terbang dari sana. Ada juga .deps.jsonbagian runtimeTarget dalam hal ini dan di sini sudah ditunjukkan versi perakitan ini khusus untuk Windows, di mana PerformanceCounter harus bekerja.

Jika kita mengambil bagian runtime dan menulis di dalamnya jalur relatif ke perpustakaan yang ingin kita muat, ini tidak akan membantu kita. Bagian runtime sebenarnya menetapkan jalur relatif di dalam paket NuGet, dan tidak relatif terhadap BIN. Jika kita mencari rakitan ini di BIN, hanya nama file yang akan digunakan dari sana. Bagian runtimeTarget sudah berisi jalur relatif jujur, jalur jujur ​​relatif terhadap BIN. Kami akan meresepkan jalur relatif untuk majelis kami di bagian runtimeTarget. Alih-alih pengidentifikasi runtime, yang β€œmenang” di sini, kita bisa mengambil yang lain yang kita suka. Sebagai contoh, kami akan menulis pengidentifikasi runtime "any", dan rakitan ini akan dimuat secara umum di semua platform. Atau kita akan menulis "unix", dan itu akan boot di Linux, dan di Mac, dan seterusnya.

Peretasan berikutnya: kami ingin mengunduh di Linux dan Mac Mock untuk membangun WindowsBase. Masalahnya adalah bahwa rakitan yang bernama WindowsBase sudah ada dalam Shared Framework Microsoft.NETCore.App, bahkan jika kita tidak di Windows. Pada Windows Shared Framework, Microsoft.WindowsDesktop.AppWindowsBase mendefinisikan ulang versi yang ada di NETCore.App. Mari kita lihat .deps.jsonKerangka ini, lebih tepatnya pada bagian-bagian yang menjelaskan WindowsBase.



Inilah perbedaannya:



Jika beberapa konflik perpustakaan dan ada di beberapa .deps.json, maka maksimumnya dipilih untuk pasangan yang terdiri dari assemblyVersiondan fileVersion. Panduan .NET mengatakan bahwa fileVersionitu hanya diperlukan untuk menunjukkannya di Windows Explorer, tetapi tidak, itu masuk ke dalamnya.deps.json. Ini adalah satu-satunya kasus yang saya tahu kapan versi yang ditentukan .deps.json, assemblyVersiondan fileVersion, benar-benar digunakan. Dalam semua kasus lain, saya melihat perilaku yang tidak peduli versi apa .deps.jsonyang ditulis, majelis akan tetap memuat.



Retasan keempat. Tugas: kami memiliki file .deps.json untuk dua peretasan sebelumnya, dan kami hanya membutuhkannya untuk dependensi tertentu. Karena mereka .deps.jsondihasilkan dalam mode semi-manual, kami memiliki skrip yang, menurut beberapa deskripsi tentang apa yang harus ada di sana, menghasilkannya selama pembuatan, kami ingin menyimpan ini .deps.jsonseminimal mungkin sehingga kami dapat memahami apa yang ada di dalamnya. Kami ingin menonaktifkan validasi dan mengizinkan unduhan majelis yang ada di BIN tetapi tidak dijelaskan dalam .deps.json.

Solusi: aktifkan konfigurasi khusus di runtimeconfig. Pengaturan ini sebenarnya diperlukan untuk kompatibilitas dengan .NET Core 1.0.

temuan


Jadi, .runtime.jsondan .deps.jsonpada .NET Core - ini adalah jenis analog App.config. App.configmemungkinkan Anda melakukan hal yang sama, misalnya, memuat rakitan dengan cara yang relatif. Menggunakan .deps.json, menulis ulang secara manual, Anda dapat menyesuaikan pemuatan majelis di .NET Core, jika Anda memiliki skenario yang sangat kompleks.

Unduhan perakitan debug


Saya berbicara tentang beberapa jenis masalah, jadi Anda harus dapat men-debug masalah dengan memuat rakitan. Apa yang bisa membantu dengan ini? Pertama, runtimes menulis log tentang bagaimana mereka memuat rakitan. Kedua, Anda bisa melihat lebih dekat pada eksekusi yang terbang ke Anda. Anda juga dapat fokus pada acara runtime.

Log fusi





Kembali ke Dasar-Dasar: Menggunakan Fusion Log Viewer Ke Debug Errors yang Tidak Jelas
Fusion

Mekanisme untuk memuat rakitan dalam .NET Framework disebut Fusion, dan ia tahu cara mencatat apa yang dilakukannya pada disk. Untuk mengaktifkan pencatatan, Anda perlu menambahkan pengaturan khusus ke registri. Ini sangat tidak nyaman, jadi masuk akal untuk menggunakan utilitas, yaitu Fusion Log Viewer dan Fusion ++. Fusion Log Viewer adalah utilitas standar yang dilengkapi dengan Visual Studio dan dapat diluncurkan dari baris perintah Visual Studio, Visual Studio Developer Command Prompt. Fusion ++ adalah analog open source dari alat ini dengan antarmuka yang lebih bagus.



Fusion Log Viewer terlihat seperti ini. Ini lebih buruk daripada WinDbg karena jendela ini bahkan tidak meregang. Namun demikian, Anda dapat menembus tanda centang di sini, meskipun tidak selalu jelas set tanda centang mana yang benar.



Fusion ++ memiliki satu tombol "Mulai Logging", dan kemudian tombol "Stop Logging" muncul. Di dalamnya, Anda dapat melihat semua catatan tentang memuat rakitan, membaca log tentang apa yang sebenarnya terjadi. Log ini terlihat seperti ini secara ringkas.



Ini adalah pengecualian dari pemuatan perakitan yang ketat. Jika kita melihat log Fusion, kita akan melihat bahwa kita perlu mengunduh versi 9.0.0.0 setelah kita memproses semua konfigurasi. Kami menemukan file di mana diduga bahwa kami memiliki majelis yang kami butuhkan. Kami melihat bahwa versi 6.0.0.0 ada di file ini. Kami memiliki peringatan bahwa kami membandingkan nama lengkap majelis, dan mereka berbeda dalam versi utama. Dan kemudian terjadi kesalahan - versi ketidakcocokan.

Peristiwa runtime





Mencatat Acara Runtime

Di Mono, Anda dapat mengaktifkan pencatatan menggunakan variabel lingkungan, dan log pada akhirnya akan ditulis ke stdoutdan stderr. Tidak begitu nyaman, tetapi solusinya bekerja.



Penjelajahan default - .NET Core
Documentation / design docs / host tracing

.NET Core juga memiliki variabel lingkungan khusus COREHOST_TRACEyang mencakup login stderr. Dengan .NET Core 3.0, Anda bisa menulis log ke file dengan menentukan lintasan ke sana dalam variabel COREHOST_TRACEFILE.


Ada acara yang menyala saat majelis gagal dimuat. Ini sebuah acara AssembleResolve. Ada acara bermanfaat kedua, ini FirstChanceException. Anda dapat berlangganan dan mendapatkan kesalahan tentang memuat majelis, bahkan jika seseorang menulis mencoba .. menangkap dan melewatkan semua eksekusi di tempat di manaFileLoadExceptionterjadi. Jika aplikasi telah dikompilasi, Anda dapat memulainya perfview, dan dapat memantau eksekusi .NET, dan di sana Anda dapat menemukan yang terkait dengan mengunduh file.

temuan


Transfer pekerjaan ke alat, ke alat pengembangan, ke IDE, ke MSBuild, yang memungkinkan Anda untuk menghasilkan arahan ulang. Anda dapat beralih ke .NET Core, lalu Anda akan lupa apa itu Memuat Perakitan Ketat, dan Anda akan dapat menggunakan API baru seperti yang ingin kami capai di Rider. Jika Anda menghubungkan pustaka .NET Standard, maka naikkan versi target .NET Framework ke setidaknya 4.7.1. Jika Anda tampaknya berada dalam situasi tanpa harapan, cari hacks, gunakan, atau buat hack Anda sendiri untuk situasi tanpa harapan. Dan perlengkapi diri Anda dengan alat debugging.

Saya sangat menyarankan Anda membaca tautan berikut:



DotNext 2020 Piter . , 8 JUG Ru Group.

All Articles