عنوان:

‫حل تعارضات نگارش‌های مختلف اسمبلی‌ها در برنامه‌های NET. با استفاده از ابزار dotnet list package


نویسنده: وحید نصیری
تاریخ: ۱۴۰۴/۰۹/۲۷ ۱۰:۰۵
آدرس: www.dntips.ir
به عنوان توسعه‌دهندگان .NET، گاهی با خطاهای بارگذاری اسمبلی (Assembly Load Errors) روبرو می‌شویم:
Could not load file or assembly XYZ or one of its dependencies. The system cannot find the file specified
که می‌تواند ناشی از تعارضات نسخه وابستگی‌ها باشد. یکی از رایج‌ترین این خطاها، "Could not load file or assembly" است که معمولاً به دلیل عدم تطابق نسخه‌های وابستگی‌های مستقیم یا غیرمستقیم (transitive dependencies) رخ می‌دهد. در محیط‌های پیچیده، جایی که پروژه‌ها از بسته‌های NuGet متعددی استفاده می‌کنند، این تعارضات می‌تواند به "dependency hell" منجر شود؛ وضعیتی که نسخه‌های مختلف یک بسته، یکدیگر را جایگزین می‌کنند و در زمان اجرا، اسمبلی مورد نیاز یافت نمی‌شود.
ابزار .NET CLI، به ویژه فرمان dotnet list package با گزینه --include-transitive، یک راهکار ساده و قدرتمند برای شناسایی این مشکلات ارائه می‌دهد. این ابزار به شما کمک می‌کند تا وابستگی‌های پروژه یا سولوشن را به طور کامل بررسی کنید، بدون نیاز به ابزارهای پیچیده‌ی خارجی. در این مقاله، به بررسی نحوه‌ی استفاده از این فرمان می‌پردازیم، با تمرکز بر دیباگ تعارضات نسخه، و نکاتی برای حل آن‌ها اضافه می‌کنیم. هدف این است که شما بتوانید به سرعت مشکلات را شناسایی و رفع کنید، تا زمان توسعه را بهینه سازید.

توضیح
بیایید ابتدا به درک عمیق‌تری از مشکل بپردازیم. در اکوسیستم .NET، وابستگی‌ها می‌توانند مستقیم (top-level) یا غیرمستقیم (transitive) باشند. وابستگی‌های transitive از طریق بسته‌های دیگر وارد پروژه می‌شوند. برای مثال، اگر پروژه A به بسته B وابسته باشد و B به نسخه خاصی از C نیاز داشته باشد، C یک وابستگی transitive برای A است. مشکل زمانی پیش می‌آید که پروژه‌ی دیگری در سولوشن، مانند D، به نسخه متفاوتی از C وابسته باشد. در زمان ساخت (build)، .NET ممکن است یکی از نسخه‌ها را انتخاب کند، اما در زمان اجرا، اگر نسخه‌ی مورد نیاز موجود نباشد، خطایی مانند زیر رخ می‌دهد:
System.IO.FileLoadException: Could not load file or assembly 'System.Security.Cryptography.Xml, Version=4.0.3.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51' or one of its dependencies. The located assembly's manifest definition does not match the assembly reference. (Exception from HRESULT: 0x80131040)
این خطا نشان‌دهنده‌ی عدم تطابق تعریف مانیفست اسمبلی یافت‌شده، با مرجع مورد انتظار است. برای دیباگ، ابتدا باید وابستگی‌ها را لیست کنیم. فرمان dotnet list package این کار را آسان می‌کند. نکته مهم: قبل از اجرای این فرمان، سولوشن یا پروژه را بسازید (build) کنید تا اطلاعات وابستگی‌ها به‌روز باشد.

نحوه استفاده از فرمان
فرمان پایه به صورت زیر است:
dotnet list package
اما برای دیدن وابستگی‌های transitive، از گزینه --include-transitive استفاده کنید. اگر سولوشن شما چند فریم‌ورک هدف (target framework) دارد، فریم‌ورک را با --framework مشخص کنید. مثالی برای یک سولوشن:
dotnet list MyTestSolution.sln package --include-transitive --framework net5.0
اگر وابستگی‌ها زیاد هستند، خروجی را به فایلی مشخص هدایت کنید تا تحلیل، آسان‌تر شود:
dotnet list MyTestSolution.sln package --include-transitive --framework net5.0 > mytestsln-dependencies.txt
خروجی این فرمان، لیستی ساختاریافته از وابستگی‌ها را ارائه می‌دهد؛ شامل نسخه‌های درخواست‌شده (Requested) و حل‌شده (Resolved) برای هر پروژه. برای مثال، در سناریویی که دو پروژه TestProject-A و TestProject-B به نسخه‌های متفاوتی از System.Security.Cryptography.Xml وابسته هستند، خروجی ممکن است شبیه زیر باشد:
Project 'TestProject-A' has the following package references
   [net5.0]: 
   Top-level Package                                Requested   Resolved
   > SoapCore                                       1.0.0       1.0.0   
   > System.Configuration.ConfigurationManager      4.7.0       4.7.0   

   Transitive Package                                                                   Resolved    
   > System.Security.Cryptography.ProtectedData                                         4.7.0    
   > System.Security.Cryptography.X509Certificates                                      4.3.0    
   > System.Security.Cryptography.Xml                                                   4.5.0  

   # The rest of the dependencies is omitted...

Project 'TestProject-B' has the following package references
   [net5.0]: 
   Top-level Package                                Requested   Resolved
   > Newtonsoft.Json                                11.0.2      11.0.2  
   > External.Client.Core                           7.0.3       7.0.3   
   > External.Client.Portal                         7.0.3       7.0.3   
   > System.Data.DataSetExtensions                  4.5.0       4.5.0   
   > System.Security.Permissions                    5.0.0       5.0.0   

   Transitive Package                                                                   Resolved    
   > System.Security.Cryptography.ProtectedData                                         4.7.0    
   > System.Security.Cryptography.X509Certificates                                      4.3.0    
   > System.Security.Cryptography.Xml                                                   4.7.0  

   # The rest of the dependencies is omitted...
از این خروجی، می‌بینیم که TestProject-A به نسخه 4.5.0 و TestProject-B به نسخه 4.7.0 وابسته است. این تعارض می‌تواند دلیل خطای بارگذاری باشد.

گزینه‌های حل تعارض
پس از شناسایی مشکل، چند راهکار وجود دارد:
  • به‌روزرسانی بسته‌ها: سعی کنید همه وابستگی‌ها را به نسخه‌ی سازگار به‌روزرسانی کنید. از NuGet Package Manager در Visual Studio یا فرمان dotnet add package استفاده کنید.
  • استفاده از Binding Redirects: در فایل app.config یا web.config، redirect اضافه کنید تا نسخه‌های قدیمی به نسخه جدید هدایت شوند. مثال:
<dependentAssembly>
  <assemblyIdentity name="System.Security.Cryptography.Xml" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
  <bindingRedirect oldVersion="0.0.0.0-4.7.0.0" newVersion="4.7.0.0" />
</dependentAssembly>
  • مدیریت مرکزی نسخه‌ها: در پروژه‌های بزرگ، از Directory.Packages.props برای مدیریت نسخه‌های مرکزی (Central Package Versioning) استفاده کنید تا تعارضات کاهش یابد.

ابزارهای مکمل: علاوه بر .NET CLI، از Fusion Log Viewer (fuslogvw.exe) برای لاگ‌گیری فرآیند بارگذاری اسمبلی‌ها بهره ببرید، که می‌تواند جزئیات بیشتری ارائه دهد. این ابزار داخلی .NET برای لاگ کردن فرآیند بارگذاری اسمبلی‌ها عالی است و می‌تواند دقیقاً نشان دهد که چرا bind شکست خورده (مانند نسخه اشتباه یا مسیر ناموجود).
مراحل کار با آن:
- Visual Studio Developer Command Prompt را با دسترسی مدیریتی باز کنید.
- fuslogvw.exe را اجرا کنید (به همراه VS‌ نصب می‌شود).
- تنظیمات را روی "Log bind failures to disk" بگذارید و مسیر لاگ را مشخص کنید.
- برنامه را اجرا کنید تا خطا رخ دهد، سپس لاگ‌ها را بررسی کنید.
این روش کمک می‌کند تا مشکلات مربوط به GAC (Global Assembly Cache) یا binding redirects را شناسایی کنید.

این روش‌ها نه تنها مشکل را حل می‌کنند، بلکه پروژه را پایدارتر می‌سازند. برای اطلاعات بیشتر، مستندات رسمی مایکروسافت در مورد dotnet list package را بررسی کنید.

نتیجه‌گیری
ابزار dotnet list package --include-transitive یک ابزار ضروری برای هر توسعه‌دهنده .NET است که به شما امکان می‌دهد وابستگی‌ها را به طور جامع بررسی کنید و تعارضات نسخه‌ها را سریع شناسایی نمایید. با ترکیب این فرمان با راهکارهایی مانند binding redirects یا به‌روزرسانی بسته‌ها، می‌توانید از خطاهای زمان اجرا جلوگیری کنید و کیفیت کد را افزایش دهید.