COM، یک فناوری قدیمی و مختص به ویندوز است؛ هرچند NET Core. به صورت چندسکویی طراحی شدهاست، اما حداقل نگارش ویندوز آن، از کار با اشیاء COM پشتیبانی میکند. البته باید درنظر داشت که نگارش 1x آن اینچنین نیست و پشتیبانی از آن، از نگارش 2x شروع شدهاست.
محدودیتهای کار با اشیاء COM در NET Core 2x.
پیاده سازی پشتیبانی از اشیاء COM در NET Core 2x. به همراه اینترفیس IDispatch نیست. به این معنا که از مفهوم «late binding» پشتیبانی نمیکند. حدود 10 سال قبل در زمان ارائهی C# 4.0، واژهی کلیدی dynamic نیز ارائه شد که یکی از مهمترین اهداف آن، ساده سازی کار با اشیاء COM و پشتیبانی از Late binding بود:
این قطعه کد که در Full .NET Framework بدون مشکل اجرا میشود، در NET Core 2x. با خطای زیر متوقف خواهد شد:
البته اگر به task manager ویندوز در این حالت مراجعه کنید، مشاهده خواهید کرد که Excel.exe واقعا اجرا شدهاست؛ اما چون پیاده سازی IDispatch در اینجا وجود ندارد، امکان کار با واژهی کلیدی dynamic و late binding برای دسترسی به خاصیت Visible پشتیبانی نمیشود.
یک نکته: NET Core 3x. از Late binding پشتیبانی میکند.
روش کار با اشیاء COM در NET Core 2x.
چون NET Core 2x. از late binding اشیاء COM پشتیبانی نمیکند، میتوان در اینجا از روش قدیمیتر کار با اشیاء COM که استفادهی از «Interop assemblies» نام دارد، استفاده کرد. Interop assemblies در حقیقت محصور کنندههای اشیاء COM هستند که امکان کار مستقیم با آنها را از طریق early binding میسر میکنند. در یک چنین حالتی، کدهای فوق برای دسترسی به اشیاء COM کار با اکسل، به صورت زیر که early binding نام دارد، تغییر میکند:
روش تولید Interop assemblies
هنوز خود NET Core. روشی را برای تولید Interop assemblies ارائه ندادهاست و تولید آنها یکی از معدود مواردی است که نیاز به نصب Visual Studio را دارد. برای این منظور یک پروژهی خالی (از هر نوعی) را که بر اساس NET Framework 4x. تهیه میشود، در VS آغاز کنید و سپس در solution explorer بر روی پروژهی ایجاد شده کلیک راست کرده و گزینهی Add > Reference را انتخاب کنید. در صفحهی باز شده، گزینهی COM آنرا باید انتخاب کنید. در اینجا است که میتوانید با انتخاب یکی از موارد، ارجاعی را به آن شیء COM اضافه کنید.
پس از اینکار:
- ابتدا این ارجاع اضافه شده را در solution explorer انتخاب کرده و در پایین صفحه، در قسمت برگهی خواص آن، گزینهی «Embed Interop Types» آنرا به false تنظیم کنید.
- سپس یکبار پروژه را نیز کامپایل کنید.
این مراحل سبب تولید یک فایل dll خواهند شد که Interop assembly نام دارد و هم در برنامههای NET. و هم NET Core.، قابل استفادهاست.
روش استفاده از Interop assemblies در برنامههای NET Core.
اکنون که یک فایل dll را از شیء COM انتخابی، در یک پروژهی مجزای مبتنی بر NET 4x. تولید کردیم، روش استفادهی از آن در یک برنامهی دیگر مبتنی بر NET Core. به صورت زیر است:
فایل csproj را گشوده و ابتدا نام اسمبلی را منهای dll آن در قسمت Reference Include ذکر کنید. سپس مسیر فایل dll تولید شدهی در قسمت قبل را به صورت HintPath مشخص کنید. اگر میخواهید این dll را به صورت جداگانهای به همراه برنامهی خود توزیع نکنید، خاصیت EmbedInteropTypes را در اینجا به true تنظیم کنید. در این حالت کامپایلر، قسمتهایی از Interop.WIA.dll را که در برنامهی شما استفاده شدهاست، جزئی از خروجی نهایی آن میکند.
یک نکته: اگر EmbedInteropTypes را به true تنظیم کردید، نیاز به بستهی Microsoft.CSharp را نیز خواهید داشت:
روش دیگر استفاده از Interop assemblies در برنامههای NET Core.
روش فوق، جهت کار با فایلهای dll ای است که خودمان تولید کردهایم. برای سایر حالاتی که این موارد در سیستم نصب شدهاند (مانند Office Primary Interop Assemblies (PIA))، پس از افزودن ارجاعی به COM reference مدنظر، فایل csproj همان پروژهی NET 4x. را باز کرده و قسمت COMReference آنرا در اینجا (در فایل csproj پروژهی NET Core.) کپی کنید:
اطلاعات COMReference فوق از یک پروژهی NET 4x. و فایل csproj آن پس از افزودن ارجاعی به اشیاء COM آفیس و اکسل، در اینجا کپی شدهاند.
سپس یک نمونه از MS Office automation را توسط اشیاء COM آن به صورت زیر میتوان پیاده سازی کرد:
مثال فوق، معادل NET Core. این مثال قدیمی است:
How to automate Microsoft Excel from Microsoft Visual C#.NET
محدودیتهای کار با اشیاء COM در NET Core 2x.
پیاده سازی پشتیبانی از اشیاء COM در NET Core 2x. به همراه اینترفیس IDispatch نیست. به این معنا که از مفهوم «late binding» پشتیبانی نمیکند. حدود 10 سال قبل در زمان ارائهی C# 4.0، واژهی کلیدی dynamic نیز ارائه شد که یکی از مهمترین اهداف آن، ساده سازی کار با اشیاء COM و پشتیبانی از Late binding بود:
dynamic excel = Activator.CreateInstance(Type.GetTypeFromProgID("Excel.Application", true)); excel.Visible = true; Console.WriteLine("Press Enter to close Excel."); Console.ReadLine(); excel.Quit();
System.__ComObject does not contain a definition for 'Visible'
یک نکته: NET Core 3x. از Late binding پشتیبانی میکند.
روش کار با اشیاء COM در NET Core 2x.
چون NET Core 2x. از late binding اشیاء COM پشتیبانی نمیکند، میتوان در اینجا از روش قدیمیتر کار با اشیاء COM که استفادهی از «Interop assemblies» نام دارد، استفاده کرد. Interop assemblies در حقیقت محصور کنندههای اشیاء COM هستند که امکان کار مستقیم با آنها را از طریق early binding میسر میکنند. در یک چنین حالتی، کدهای فوق برای دسترسی به اشیاء COM کار با اکسل، به صورت زیر که early binding نام دارد، تغییر میکند:
using Excel = Microsoft.Office.Interop.Excel; // ... var excel = new Excel.Application(); excel.Visible = true; Console.WriteLine("Press Enter to close Excel."); Console.ReadLine(); excel.Quit();
روش تولید Interop assemblies
هنوز خود NET Core. روشی را برای تولید Interop assemblies ارائه ندادهاست و تولید آنها یکی از معدود مواردی است که نیاز به نصب Visual Studio را دارد. برای این منظور یک پروژهی خالی (از هر نوعی) را که بر اساس NET Framework 4x. تهیه میشود، در VS آغاز کنید و سپس در solution explorer بر روی پروژهی ایجاد شده کلیک راست کرده و گزینهی Add > Reference را انتخاب کنید. در صفحهی باز شده، گزینهی COM آنرا باید انتخاب کنید. در اینجا است که میتوانید با انتخاب یکی از موارد، ارجاعی را به آن شیء COM اضافه کنید.
پس از اینکار:
- ابتدا این ارجاع اضافه شده را در solution explorer انتخاب کرده و در پایین صفحه، در قسمت برگهی خواص آن، گزینهی «Embed Interop Types» آنرا به false تنظیم کنید.
- سپس یکبار پروژه را نیز کامپایل کنید.
این مراحل سبب تولید یک فایل dll خواهند شد که Interop assembly نام دارد و هم در برنامههای NET. و هم NET Core.، قابل استفادهاست.
روش استفاده از Interop assemblies در برنامههای NET Core.
اکنون که یک فایل dll را از شیء COM انتخابی، در یک پروژهی مجزای مبتنی بر NET 4x. تولید کردیم، روش استفادهی از آن در یک برنامهی دیگر مبتنی بر NET Core. به صورت زیر است:
<ItemGroup> <Reference Include="Interop.WIA"> <HintPath>..\DNTScanner.Core.TypeLibrary\bin\Debug\Interop.WIA.dll</HintPath> <EmbedInteropTypes>True</EmbedInteropTypes> </Reference> </ItemGroup>
یک نکته: اگر EmbedInteropTypes را به true تنظیم کردید، نیاز به بستهی Microsoft.CSharp را نیز خواهید داشت:
<ItemGroup Condition=" '$(TargetFramework)' == 'net40' "> <Reference Include="Microsoft.CSharp" /> </ItemGroup> <ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'"> <PackageReference Include="Microsoft.CSharp" Version="4.5.0" /> </ItemGroup>
روش دیگر استفاده از Interop assemblies در برنامههای NET Core.
روش فوق، جهت کار با فایلهای dll ای است که خودمان تولید کردهایم. برای سایر حالاتی که این موارد در سیستم نصب شدهاند (مانند Office Primary Interop Assemblies (PIA))، پس از افزودن ارجاعی به COM reference مدنظر، فایل csproj همان پروژهی NET 4x. را باز کرده و قسمت COMReference آنرا در اینجا (در فایل csproj پروژهی NET Core.) کپی کنید:
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>Exe</OutputType> <TargetFramework>netcoreapp3.0</TargetFramework> </PropertyGroup> <!-- The following 'COMReference' items were copied from a .NET Framework project. They were added by using the Visual Studio COM References window. See https://docs.microsoft.com/en-us/visualstudio/ide/managing-references-in-a-project?view=vs-2017. Observe the 'EmbedInteropTypes' tag value. See https://docs.microsoft.com/en-us/visualstudio/msbuild/common-msbuild-project-items?view=vs-2017#comreference --> <ItemGroup> <COMReference Include="Microsoft.Office.Core"> <Guid>{2DF8D04C-5BFA-101B-BDE5-00AA0044DE52}</Guid> <VersionMajor>2</VersionMajor> <VersionMinor>8</VersionMinor> <Lcid>0</Lcid> <WrapperTool>primary</WrapperTool> <Isolated>False</Isolated> <EmbedInteropTypes>True</EmbedInteropTypes> </COMReference> <COMReference Include="Microsoft.Office.Interop.Excel"> <Guid>{00020813-0000-0000-C000-000000000046}</Guid> <VersionMajor>1</VersionMajor> <VersionMinor>9</VersionMinor> <Lcid>0</Lcid> <WrapperTool>primary</WrapperTool> <Isolated>False</Isolated> <EmbedInteropTypes>True</EmbedInteropTypes> </COMReference> <COMReference Include="VBIDE"> <Guid>{0002E157-0000-0000-C000-000000000046}</Guid> <VersionMajor>5</VersionMajor> <VersionMinor>3</VersionMinor> <Lcid>0</Lcid> <WrapperTool>primary</WrapperTool> <Isolated>False</Isolated> <EmbedInteropTypes>True</EmbedInteropTypes> </COMReference> </ItemGroup> </Project>
سپس یک نمونه از MS Office automation را توسط اشیاء COM آن به صورت زیر میتوان پیاده سازی کرد:
using System; using System.Reflection; using Excel = Microsoft.Office.Interop.Excel; namespace ExcelDemo { class Program { public static void Main(string[] args) { Excel.Application excel; Excel.Workbook workbook; Excel.Worksheet sheet; Excel.Range range; try { // Start Excel and get Application object. excel = new Excel.Application(); excel.Visible = true; // Get a new workbook. workbook = excel.Workbooks.Add(Missing.Value); sheet = (Excel.Worksheet)workbook.ActiveSheet; // Add table headers going cell by cell. sheet.Cells[1, 1] = "First Name"; sheet.Cells[1, 2] = "Last Name"; sheet.Cells[1, 3] = "Full Name"; sheet.Cells[1, 4] = "Salary"; // Format A1:D1 as bold, vertical alignment = center. sheet.get_Range("A1", "D1").Font.Bold = true; sheet.get_Range("A1", "D1").VerticalAlignment = Excel.XlVAlign.xlVAlignCenter; // Create an array to multiple values at once. string[,] saNames = new string[5, 2]; saNames[0, 0] = "John"; saNames[0, 1] = "Smith"; saNames[1, 0] = "Tom"; saNames[1, 1] = "Brown"; saNames[2, 0] = "Sue"; saNames[2, 1] = "Thomas"; saNames[3, 0] = "Jane"; saNames[3, 1] = "Jones"; saNames[4, 0] = "Adam"; saNames[4, 1] = "Johnson"; // Fill A2:B6 with an array of values (First and Last Names). sheet.get_Range("A2", "B6").Value2 = saNames; // Fill C2:C6 with a relative formula (=A2 & " " & B2). range = sheet.get_Range("C2", "C6"); range.Formula = "=A2 & \" \" & B2"; // Fill D2:D6 with a formula(=RAND()*100000) and apply format. range = sheet.get_Range("D2", "D6"); range.Formula = "=RAND()*100000"; range.NumberFormat = "$0.00"; // AutoFit columns A:D. range = sheet.get_Range("A1", "D1"); range.EntireColumn.AutoFit(); // Make sure Excel is visible and give the user control // of Microsoft Excel's lifetime. excel.Visible = true; excel.UserControl = true; } catch (Exception e) { Console.WriteLine($"Error: {e.Message} Line: {e.Source}"); } } } }
How to automate Microsoft Excel from Microsoft Visual C#.NET
متادیتاها شامل بلوکی از دادههای باینری هستند که شامل چندین جدول شده و جدولها نیز به سه دسته تقسیم میشوند:
جداول تعریف
جدول زیر تعدادی از جداول تعریفها را توضیح میدهد:
جداول ارجاعی
البته جداولی که در بالا هستند فقط تعدادی از آن جداول هستند، ولی قصد ما تنها یک آشنایی کلی با جداول هر قسمت بود. در مورد جداول manifest بعدتر صحبت میکنیم.
- جداول تعاریف Definition Table
- جداول ارجاع References Table
- جداول manifest
جداول تعریف
جدول زیر تعدادی از جداول تعریفها را توضیح میدهد:
ModuleDef | شامل آدرس یا مدخلی است که ماژول در آن تعریف شده است. این آدرس شامل نام ماژول به همراه پسوند آن است؛ بدون ذکر مسیر. در صورتی که کامپایل به صورت GUID انجام گرفته باشد، Version ID ماژول هم همراه آنها خواهد بود. در صورتیکه نام فایل تغییر کند، این جدول باز نام اصلی ماژول را به همراه خواهد داشت. هر چند تغییر نام فایل به شدت رد شده و ممکن است باعث شود CLR نتواند در زمان اجرا آن را پیدا کند. |
TypeDef | شامل یک مدخل ورودی برای هر نوعی است که تعریف شده است. هر آدرس ورودی شامل نام نوع ، پرچمها (همان مجوزهای public و private و ...) میباشد. همچنین شامل اندیس هایی به متدها است که شامل جدول MethodDef میباشند یا فیلدهایی که شامل جدول FieldDef میباشند و الی آخر... |
MethodDef | شامل آدرسی برای هر متد تعریف شده در ماژول است که شامل نام متد و پرچم هاست. همچنین شامل امضای متد و نقطهی آغاز کد IL آن در ماژول هم میشود و آن آدرس هم میتواند ارجاعی به جدول ParamDef جهت شناسایی پارامترها باشد. |
FieldDef | شامل اطلاعاتی در مورد فیلدهاست که این اطلاعات ، پرچم، نام و نوع فیلد را مشخص میکنند. |
ParamDef | حاوی اطلاعات پارامتر متدهاست که این اطلاعات شامل پرچمها (in , out ,retval) ، نوع و نام است. |
PropertyDef | برای هر پراپرتی یا خصوصیت، شامل یک آدرس است که شامل نام، نوع و پرچم میشود. |
EventDef | برای هر رویداد شامل یک آدرس است که این آدرس شامل نام و نوع است. |
جداول ارجاعی
موقعی که کد شما کامپایل میشود، اگر شما به اسمبلی دیگری ارجاع داشته باشید، از جداول ارجاع کمک گرفته میشود که در جدول زیر تعدادی از این جداول فهرست شدهاند:
AssemblyRef | شامل آدرس اسمبلی است که ماژولی به آن ارجاع داده است و این آدرس شامل اطلاعات ضروری جهت اتصال به اسمبلی میشود و این اطلاعات شامل نام اسمبلی (بدون ذکر پسوند و مسیر)، شماره نسخه اسمبلی، سیستم فرهنگی و منطقهای تعیین شده اسمبلی culture و یک کلید عمومی که عموما توسط ناشر ایجاد میگردد که هویت ناشر آن اسمبلی را مشخص میکند. هر آدرس شامل یک پرچم و یک کد هش هست که بری ارزیابی از صحت و بی خطا بودن بیتهای اسمبلی ارجاع شده Checksum استفاده میشود. |
ModuleRef | شامل یک آدرس ورودی به هدر PE ماژول است به نوعهای پیاده سازی شده آن ماژول در آن اسمبلی. هر آدرس شامل نام فایل و پسوند آن بدون ذکر مسیر است. این جدول برای اتصال به نوعهایی استفاده میشود که در یک ماژول متفاوت از ماژول اسمبلی صدا زده شده پیاده سازی شده است. |
TypeRef | شامل یک آدرس یا ورودی برای هر نوعی است که توسط ماژول ارجاع داده شده است. هر آدرس شامل نام نوع و آدرسی است که نوع در آن جا قرار دارد. اگر این نوع داخل نوع دیگری پیاده سازی شود، ارجاعات به سمت یک جدول TypeDef خواهد بود. اگر نوع داخل همان ماژول تعریف شده باشد، ارجاع به سمت جدول ModuleDef خواهد بود و اگر نوع در ماژول دیگری از آن اسمبلی پیاده سازی شده باشد، ارجاع به سمت یک جدول ModuleRef خواهد بود و اگر نوع در یک اسمبلی جداگانه تعریف شده باشد، ارجاع به جدول AssemblyRef خواهد بود. |
MemberRef | شامل یک آدرس ورودی برای هر عضو (فیلد و متدها و حتی پراپرتی و رویدادها) است که توسط آن آن ماژول ارجاع شده باشد. هر آدرس شامل نام عضو، امضاء و یک اشارهگر به جدول TypeRef است، برای نوعهایی که به تعریف عضو پرداختهاند. |
ابزارهای متنوع و زیادی هستند که برای بررسی و آزمایش متادیتاها استفاده میشوند. یکی از این ابزارها ILDasm.exe میباشد. برای دیدن متادیتاهای یک فایل اجرایی فرمان زیر را صادر کنید:
ILDasm Program.exe
View/MetaInfo/Show
=========================================================== ScopeName : Program.exe MVID : {CA73FFE80D424610A8D39276195C35AA} =========================================================== Global functions Global fields Global MemberRefs TypeDef #1 (02000002) TypDefName: Program (02000002) Flags : [Public] [AutoLayout] [Class] [Sealed] [AnsiClass] [BeforeFieldInit] (00100101) Extends : 01000001 [TypeRef] System.Object Method #1 (06000001) [ENTRYPOINT] MethodName: Main (06000001) Flags : [Public] [Static] [HideBySig] [ReuseSlot] (00000096) RVA : 0x00002050 ImplFlags : [IL] [Managed] (00000000) CallCnvntn: [DEFAULT] ReturnType: Void No arguments. Method #2 (06000002) MethodName: .ctor (06000002) Flags : [Public] [HideBySig] [ReuseSlot] [SpecialName] [RTSpecialName] [.ctor] (00001886) RVA : 0x0000205c ImplFlags : [IL] [Managed] (00000000) CallCnvntn: [DEFAULT] hasThis ReturnType: Void No arguments. TypeRef #1 (01000001) Token: 0x01000001 ResolutionScope: 0x23000001 TypeRefName: System.Object MemberRef #1 (0a000004) Member: (0a000004) .ctor: CallCnvntn: [DEFAULT] hasThis ReturnType: Void No arguments. TypeRef #2 (01000002) Token: 0x01000002 ResolutionScope: 0x23000001 TypeRefName: System.Runtime.CompilerServices.CompilationRelaxationsAttribute MemberRef #1 (0a000001) Member: (0a000001) .ctor: CallCnvntn: [DEFAULT] hasThis ReturnType: Void 1 Arguments Argument #1: I4 TypeRef #3 (01000003) Token: 0x01000003 ResolutionScope: 0x23000001 TypeRefName: System.Runtime.CompilerServices.RuntimeCompatibilityAttribute MemberRef #1 (0a000002) Member: (0a000002) .ctor: CallCnvntn: [DEFAULT] hasThis ReturnType: Void No arguments. TypeRef #4 (01000004) Token: 0x01000004 ResolutionScope: 0x23000001 TypeRefName: System.Console MemberRef #1 (0a000003) Member: (0a000003) WriteLine: CallCnvntn: [DEFAULT] ReturnType: Void 1 Arguments Argument #1: String Assembly Token: 0x20000001 Name : Program Public Key : Hash Algorithm : 0x00008004 Version: 0.0.0.0 Major Version: 0x00000000 Minor Version: 0x00000000 Build Number: 0x00000000 Revision Number: 0x00000000 Locale: <null> Flags : [none] (00000000) CustomAttribute #1 (0c000001) CustomAttribute Type: 0a000001 CustomAttributeName: System.Runtime.CompilerServices.CompilationRelaxationsAttribute :: instance void .ctor(int32) Length: 8 Value : 01 00 08 00 00 00 00 00 > < ctor args: (8) CustomAttribute #2 (0c000002) CustomAttribute Type: 0a000002 CustomAttributeName: System.Runtime.CompilerServices.RuntimeCompatibilityAttribute :: instance void .ctor() Length: 30 Value : 01 00 01 00 54 02 16 57 72 61 70 4e 6f 6e 45 78 > T WrapNonEx< : 63 65 70 74 69 6f 6e 54 68 72 6f 77 73 01 >ceptionThrows < ctor args: () AssemblyRef #1 (23000001) Token: 0x23000001 Public Key or Token: b7 7a 5c 56 19 34 e0 89 Name: mscorlib Version: 4.0.0.0 Major Version: 0x00000004 Minor Version: 0x00000000 Build Number: 0x00000000 Revision Number: 0x00000000 Locale: <null> HashValue Blob: Flags: [none] (00000000) User Strings 70000001 : ( 2) L"Hi" Coff symbol name overhead: 0
متد Main یک متد عمومی و ایستا static است که شامل کد IL است و هیچ خروجی ندارد و هیچ آرگومانی را نمیپزیرد. متد سازنده عمومی است و شامل کد IL است، سازنده هیچ نوع خروجی ندارد و هیچ آرگومانی هم نمیپذیرد و یک اشارهگر که به یک object در حافظه که موقع صدا زدن ساخته خواهد شد.
ابزار ILDasm امکاناتی بیشتری از آنچه که دیدید ارائه میکند. به عنوان نمونه اگر مسیر زیر را در منوها طی کنید:
View/statistics
File size : 3584 PE header size : 512 (496 used) (14.29%) PE additional info : 1411 (39.37%) Num.of PE sections : 3 CLR header size : 72 ( 2.01%) CLR metadata size : 612 (17.08%) CLR additional info : 0 ( 0.00%) CLR method headers : 2 ( 0.06%) Managed code : 20 ( 0.56%) Data : 2048 (57.14%) Unaccounted : 1093 (30.50%) Num.of PE sections : 3 .text 1024 .rsrc 1536 .reloc 512 CLR metadata size : 612 Module 1 (10 bytes) TypeDef 2 (28 bytes) 0 interfaces, 0 explicit layout TypeRef 4 (24 bytes) MethodDef 2 (28 bytes) 0 abstract, 0 native, 2 bodies MemberRef 4 (24 bytes) CustomAttribute 2 (12 bytes) Assembly 1 (22 bytes) AssemblyRef 1 (20 bytes) Strings 184 bytes Blobs 68 bytes UserStrings 8 bytes Guids 16 bytes Uncategorized 168 bytes CLR method headers : 2 Num.of method bodies 2 Num.of fat headers 0 Num.of tiny headers 2 Managed code : 20 Ave method size 10
توجه: ILDasm یک باگ دارد که بر نمایش اندازهی فایل تاثیر میگذارد و باعث میشود شما نتوانید به اطلاعات ثبت شده اعتماد داشته باشید.
12 سپتامبر تاریخ انتشار نهایی و عمومی ویژوال استودیو 2012 بود. این خبر مهم از سوی افراد و تیمهای مختلف مایکروسافت پوشش داده شد. برای مطالعهی بیشتر لینکهای زیر پیشنهاد میشوند:
احتمالا تا به امروز در برنامههای جاوا اسکریپتی خود از کتابخانههای ثالث و یا توابع ویژهای برای نمایش شمسی تاریخ، نمایش فارسی اعداد، افزودن جدا کنندهی سه رقمی اعداد (جداکنندهی هزارگان)، نمایش تاریخ نسبی مانند 1 روز قبل و ... استفاده کردهاید. خبر خوب اینکه موتور جاوا اسکریپتی تمام مرورگرهای جدید (از فایرفاکس 29 و کروم 24 به بعد) به صورت توکار یک چنین تبدیلهایی را پشتیبانی میکنند و برای مثال برای تبدیل تاریخ میلادی به شمسی و نمایش آن، در بسیاری از موارد نیازی به کتابخانهی حجیم moment.js (و یا سایر روشهای مرسوم خانگی) نیست.
معرفی API استاندارد بومی سازی JavaScript
Internationalization یا به صورت خلاصه i18n (یعنی یک i به همراه 18 حرف و یک n)، پروسهای که در آن برنامه به نحوی طراحی میشود تا خروجی آن قابلیت استفادهی برای انواع و اقسام فرهنگها را داشته باشد. برای مثال دو متد زیر را در نظر بگیرید:
آیا در همه جای دنیا، تاریخ به صورت ماه، روز و سال نمایش داده میشود؛ آن هم به صورت میلادی؟ و یا آیا خروجی فرمت شدهی یک مقدار پولی، همیشه با دلار شروع میشود و نمایش آن نیز با اعداد انگلیسی است؟
پیشتر جاوا اسکریپت برای مدیریت یک چنین مواردی (i18n-aware formatting) از متد toLocaleString استفاده میکرد (و هنوز هم برای پشتیبانی از برنامههای قدیمی، از API عمومی آن حذف نشدهاست) و خروجی آن از هر مرورگر و پیاده سازی خاصی، به مرورگر دیگری میتواند متفاوت باشد؛ حتی اگر جزئیات دقیقی هم درخواست شود. برای رفع این مشکل، استاندارد ECMAScript Internationalization API ارائه شد تا قابلیتهای توکار i18n جاوا اسکریپت را بهبود بخشیده و همچنین یکدست کند. توسط آن امکان انتخاب یک یا چند منطقهی خاص و سپس فرمت کردن تاریخ، اعداد و یا حتی مرتب سازی واژهها و عبارات با معرفی collations، میسر میباشد. در اینجا حتی امکان سفارشی سازی این فرمتها نیز پیشبینی شدهاست.
معرفی اینترفیس Intl
i18n API در یک شیء سراسری به نام Intl قابل دسترسی است و تعدادی از سازندههای آن Intl.Collator ،Intl.DateTimeFormat و Intl.NumberFormat نام دارند؛ مانند:
برای کار با این شیء، نیازی به import هیچ ماژول و یا کتابخانهای نیست و جزئی از جاوا اسکریپت استاندارد میباشد. به همین جهت کار با آن حجمی را به برنامهی شما اضافه نخواهد کرد.
تمام این سازندهها میتوانند با یک فرهنگ و یا آرایهای از فرهنگهای مدنظر شروع شوند:
در مثال اول فرهنگ فارسی و در مثال دوم فرهنگ پرتغالی که در برزیل و پرتغال مورد استفادهاست، ذکر شدهاند.
پارامتر اختیاری دوم آنها نیز تنظیماتی است که جهت سفارشی سازی این بومی سازی میتوان تعریف کرد.
نمایش شمسی تاریخ میلادی توسط i18n API
پس از معرفی i18n API، اکنون میخواهیم در طی مثالهایی، تمام کتابخانههای ثالث تبدیل تاریخ میلادی به شمسی را کنار گذاشته و با استفاده از جاوا اسکریپت استاندارد، این تبدیل را انجام دهیم. پارامتر دوم سازندهی new Intl.DateTimeFormat که تنظیمات آنرا مشخص میکند، میتواند به همراه ترکیبی از موارد زیر باشد که مقادیر مجاز برای آنها را نیز مشاهده میکنید:
برای نمونه، ذکر Intl.DateTimeFormat بدون هیچ تنظیمی و فقط با تعیین فرهنگ فارسی:
خروجی «۱۳۹۸/۱۲/۱» را نمایش میدهد.
نمایش تاریخ شمسی با فرمت «۱۳۹۸ اسفند ۱, پنجشنبه»
برای تبدیل تاریخ میلادی به شمسی میتوان از سازندهی new Intl.DateTimeFormat با فرهنگ fa استفاده کرد. در اینجا ذکر مقدار long برای نام روز هفته، سبب درج نام روز میشود. نمایش سال به صورت عددی تنظیم شدهاست، ماه را به صورت بلند و نام کامل نمایش میدهد و مقدار روز را به صورت عددی درج میکند. این اعداد نیز فارسی هستند:
که برای نمونه سبب درج خروجی «۱۳۹۸ اسفند ۱, پنجشنبه» در کنسول توسعه دهندگان مرورگر خواهد شد.
اگر فقط نیاز به نمایش «۱ اسفند ۱۳۹۸» بود، میتوان از تنظیمات زیر که در آن ماه، روز و سال ذکر شدهاند و در آن، ماه به صورت کامل و بلند نمایش داده میشود، استفاده کرد:
یک نکته: همین خروجی را با متد قدیمی toLocaleDateString نیز میتوان به دست آورد؛ اما روش توصیه شده برای برنامههای جدید، همان استفاده از new Intl است.
نمایش تاریخ شمسی با فرمت «۹۸/۱۲/۱، ۶:۳۰»
برای اینکار پس از ذکر فرهنگ fa، تمام اجزای تاریخ را به صورت عددی مشخص میکنیم و سال را نیز دو رقمی نمایش خواهیم داد:
در این حالت اگر نیاز بود حتما اعداد ماه و روز، دو رقمی باشند، میتوان تنظیم 2-digit را صریحا ذکر کرد:
با خروجی «۹۸/۱۲/۰۱، ۱۲:۵۹ (+۳:۳۰ گرینویچ)»
و یا اگر «۱ اسفند ۱۳۹۸، ۰۹:۲۹ (UTC)» مدنظر بود، میتوان ماه را به long تنظیم کرد و مقدار timeZone را صریحا ذکر نمود (که البته ذکر تنظیمات timeZone اختیاری است):
با خروجیهای «۱ روز پیش» و «۱ روز بعد»
نمایش اعداد فارسی توسط i18n API
احتمالا برای تبدیل اعداد انگلیسی به فارسی و نمایش آنها، متدهایی را برای replace حروف و اعداد طراحی کردهاید. به کمک شیء استاندارد Intl.NumberFormat دیگر نیازی به آنها نخواهید داشت!
خروجی شیء Intl.NumberFormat به همراه ذکر فرهنگ فارسی و هیچ تنظیم اضافهتری
به صورت «۱۲۳٬۴۵۶» است که هم اعداد آن فارسی شدهاند و هم به همراه جداکنندهی هزارگان خودکار است.
اگر میخواهید این جداکنندهی هزارگان نمایش داده نشود، نیاز است تنظیمات آنرا به همراه useGrouping: false، به صورت زیر ذکر کرد:
این شیء یک مقدار غیرعددی را
به صورت «ناعدد» نمایش میدهد.
و یا برای نمایش واحد پولی، میتوان حالت نمایش را به currency و نوع currency را به IRR که ریال است، تنظیم کرد:
با این خروجی: «ریال ۵٫۲۶۰» که در اینجا امکان تنظیم نمایش تعداد اعشار آن نیز میسر است.
برای نمایش درصد پس از اعداد میتوان از تنظیم زیر استفاده کرد:
که خروجی «۴۳٫۸۰٪» را نمایش میدهد.
و یا برای نمایش ممیز به همراه تنظیم دقت آن داریم:
با این خروجی: «۰۳٫۱۴»
معرفی API استاندارد بومی سازی JavaScript
Internationalization یا به صورت خلاصه i18n (یعنی یک i به همراه 18 حرف و یک n)، پروسهای که در آن برنامه به نحوی طراحی میشود تا خروجی آن قابلیت استفادهی برای انواع و اقسام فرهنگها را داشته باشد. برای مثال دو متد زیر را در نظر بگیرید:
function formatDate(d) { var month = d.getMonth() + 1; var date = d.getDate(); var year = d.getFullYear(); return month + "/" + date + "/" + year; } function formatMoney(amount) { return "$" + amount.toFixed(2); }
پیشتر جاوا اسکریپت برای مدیریت یک چنین مواردی (i18n-aware formatting) از متد toLocaleString استفاده میکرد (و هنوز هم برای پشتیبانی از برنامههای قدیمی، از API عمومی آن حذف نشدهاست) و خروجی آن از هر مرورگر و پیاده سازی خاصی، به مرورگر دیگری میتواند متفاوت باشد؛ حتی اگر جزئیات دقیقی هم درخواست شود. برای رفع این مشکل، استاندارد ECMAScript Internationalization API ارائه شد تا قابلیتهای توکار i18n جاوا اسکریپت را بهبود بخشیده و همچنین یکدست کند. توسط آن امکان انتخاب یک یا چند منطقهی خاص و سپس فرمت کردن تاریخ، اعداد و یا حتی مرتب سازی واژهها و عبارات با معرفی collations، میسر میباشد. در اینجا حتی امکان سفارشی سازی این فرمتها نیز پیشبینی شدهاست.
معرفی اینترفیس Intl
i18n API در یک شیء سراسری به نام Intl قابل دسترسی است و تعدادی از سازندههای آن Intl.Collator ،Intl.DateTimeFormat و Intl.NumberFormat نام دارند؛ مانند:
const result = new Intl.NumberFormat("fa").format(123456)
تمام این سازندهها میتوانند با یک فرهنگ و یا آرایهای از فرهنگهای مدنظر شروع شوند:
const portugueseTime = new Intl.DateTimeFormat(["pt-BR", "pt-PT"], options);
پارامتر اختیاری دوم آنها نیز تنظیماتی است که جهت سفارشی سازی این بومی سازی میتوان تعریف کرد.
نمایش شمسی تاریخ میلادی توسط i18n API
پس از معرفی i18n API، اکنون میخواهیم در طی مثالهایی، تمام کتابخانههای ثالث تبدیل تاریخ میلادی به شمسی را کنار گذاشته و با استفاده از جاوا اسکریپت استاندارد، این تبدیل را انجام دهیم. پارامتر دوم سازندهی new Intl.DateTimeFormat که تنظیمات آنرا مشخص میکند، میتواند به همراه ترکیبی از موارد زیر باشد که مقادیر مجاز برای آنها را نیز مشاهده میکنید:
{ weekday: 'narrow' | 'short' | 'long', era: 'narrow' | 'short' | 'long', year: 'numeric' | '2-digit', month: 'numeric' | '2-digit' | 'narrow' | 'short' | 'long', day: 'numeric' | '2-digit', hour: 'numeric' | '2-digit', minute: 'numeric' | '2-digit', second: 'numeric' | '2-digit', timeZoneName: 'short' | 'long', // Time zone to express it in timeZone: 'Asia/Shanghai', // Force 12-hour or 24-hour hour12: true | false, // Rarely-used options hourCycle: 'h11' | 'h12' | 'h23' | 'h24', formatMatcher: 'basic' | 'best fit' }
var dateFormat = new Intl.DateTimeFormat("fa"); console.log(dateFormat.format(Date.now()));
نمایش تاریخ شمسی با فرمت «۱۳۹۸ اسفند ۱, پنجشنبه»
برای تبدیل تاریخ میلادی به شمسی میتوان از سازندهی new Intl.DateTimeFormat با فرهنگ fa استفاده کرد. در اینجا ذکر مقدار long برای نام روز هفته، سبب درج نام روز میشود. نمایش سال به صورت عددی تنظیم شدهاست، ماه را به صورت بلند و نام کامل نمایش میدهد و مقدار روز را به صورت عددی درج میکند. این اعداد نیز فارسی هستند:
const date = new Date(Date.UTC(2020, 1, 20, 3, 0, 0, 200)); const faDate = new Intl.DateTimeFormat("fa", { weekday: "long", year: "numeric", month: "long", day: "numeric" }).format(date); console.log(faDate);
اگر فقط نیاز به نمایش «۱ اسفند ۱۳۹۸» بود، میتوان از تنظیمات زیر که در آن ماه، روز و سال ذکر شدهاند و در آن، ماه به صورت کامل و بلند نمایش داده میشود، استفاده کرد:
const isoString = new Date().toISOString(); const date = new Date(isoString); console.log( new Intl.DateTimeFormat("fa", { month: "long", day: "numeric", year: "numeric" }).format(date) );
یک نکته: همین خروجی را با متد قدیمی toLocaleDateString نیز میتوان به دست آورد؛ اما روش توصیه شده برای برنامههای جدید، همان استفاده از new Intl است.
console.log( new Date().toLocaleDateString("fa", { month: "long", day: "numeric", year: "numeric" }) );
نمایش تاریخ شمسی با فرمت «۹۸/۱۲/۱، ۶:۳۰»
برای اینکار پس از ذکر فرهنگ fa، تمام اجزای تاریخ را به صورت عددی مشخص میکنیم و سال را نیز دو رقمی نمایش خواهیم داد:
const date = new Date(Date.UTC(2020, 1, 20, 3, 0, 0, 200)); const fmt = new Intl.DateTimeFormat("fa", { year: "2-digit", month: "numeric", day: "numeric", hour: "numeric", minute: "numeric" }); console.log(fmt.format(date));
const faDateTime = new Intl.DateTimeFormat("fa", { year: "2-digit", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", timeZoneName: "short" }).format; const now = Date.now(); console.log(faDateTime(now));
و یا اگر «۱ اسفند ۱۳۹۸، ۰۹:۲۹ (UTC)» مدنظر بود، میتوان ماه را به long تنظیم کرد و مقدار timeZone را صریحا ذکر نمود (که البته ذکر تنظیمات timeZone اختیاری است):
const faTime = new Intl.DateTimeFormat("fa", { year: "numeric", month: "long", day: "numeric", hour: "2-digit", minute: "2-digit", timeZoneName: "short", timeZone: "UTC" }); console.log(faTime.format(now));
نمایش تاریخهای نسبی مانند «1 روز بعد»
const rtf = new Intl.RelativeTimeFormat("en", { localeMatcher: "best fit", // other values: "lookup" numeric: "always", // other values: "auto" style: "long", // other values: "short" or "narrow" }); console.log(rtf.format(-1, "day")); console.log(rtf.format(1, "day"));
احتمالا برای تبدیل اعداد انگلیسی به فارسی و نمایش آنها، متدهایی را برای replace حروف و اعداد طراحی کردهاید. به کمک شیء استاندارد Intl.NumberFormat دیگر نیازی به آنها نخواهید داشت!
خروجی شیء Intl.NumberFormat به همراه ذکر فرهنگ فارسی و هیچ تنظیم اضافهتری
console.log(new Intl.NumberFormat("fa").format(123456));
اگر میخواهید این جداکنندهی هزارگان نمایش داده نشود، نیاز است تنظیمات آنرا به همراه useGrouping: false، به صورت زیر ذکر کرد:
console.log( new Intl.NumberFormat("fa", { useGrouping: false }).format(123456) );
این شیء یک مقدار غیرعددی را
console.log(new Intl.NumberFormat("fa").format("تست"));
و یا برای نمایش واحد پولی، میتوان حالت نمایش را به currency و نوع currency را به IRR که ریال است، تنظیم کرد:
const gasPrice = new Intl.NumberFormat("fa", { style: "currency", currency: "IRR", minimumFractionDigits: 3 }); console.log(gasPrice.format(5.2597));
برای نمایش درصد پس از اعداد میتوان از تنظیم زیر استفاده کرد:
const faPercent = new Intl.NumberFormat("fa", { style: "percent", minimumFractionDigits: 2 }).format; console.log(faPercent(0.438));
و یا برای نمایش ممیز به همراه تنظیم دقت آن داریم:
const persianDecimal = new Intl.NumberFormat("fa", { minimumIntegerDigits: 2, maximumFractionDigits: 2 }); console.log(persianDecimal.format(3.1416));
اشتراکها
NET 8.0.402. منتشر شد
.NET 8.0.402 - September 24, 2024
Today, we are releasing an update to .NET 8.0.400 SDK due to an issue with RestoreTask randomly fails after upgrading to latest version fixed by Allow @@ as a fallback.
The .NET 8.0.402 release is available for download. This SDK includes the previously released .NET 8.0.8 Runtime and is in support of Visual Studio 17.11 release. The latest 8.0 release is always listed at .NET 8.0 Releases.
With the release of Visual Studio 2015 came the (final) release of the Roslyn C# compiler and C# 6. This latest version of C#’s feature list seems to be…less than exciting, but it’s important to keep in mind that before Roslyn, none of these new features would have ever made it into a release. It was simply too hard to add a feature in C#, so higher impact/value features made it in while minor annoyances/enhancements would be deferred, indefinitely
خروجی PDF زیر را در نظر بگیرید:
مشکلی را در آن مشاهده میکنید؟ اصل آن یا صحیح آن باید به شکل زیر باشد:
و این وارونه نمایش دادنها، دقیقا مشکلی است که حین کار با iTextSharp برای نمایش متنی مثلا به همراه یک تاریخ شمسی وجود دارد. البته این مشکل هم اساسا به خود استاندارد یونیکد برمیگرد که یک سری کاراکتر را «کاراکتر ضعیف» معرفی کرده؛ برای مثال کاراکتر اسلش بکار رفته در یک تاریخ هم از این دست است. بنابراین PDF تولیدی توسط iTextSharp از دید استاندارد یونیکد مشکلی ندارد، زیرا یک «نویسه ضعیف» مثل اسلش نمیتواند جهت را تغییر دهد؛ مگر اینکه از یک «نویسه قوی» برای دستکاری آن استفاده شود. برای مثال این نویسهها قوی هستند:
U+202A: LEFT-TO-RIGHT EMBEDDING (LRE)
U+202B: RIGHT-TO-LEFT EMBEDDING (RLE)
U+202D: LEFT-TO-RIGHT OVERRIDE (LRO)
U+202E: RIGHT-TO-LEFT OVERRIDE (RLO)
U+202C: POP DIRECTIONAL FORMATTING (PDF)
برای رسیدن به تصویر صحیح نمایش داده شده در بالا، متد FixWeakCharacters زیر را تهیه کردهام که حداقل با iTextSharp جواب میده:
using System;
using System.Diagnostics;
using System.IO;
using iTextSharp.text;
using iTextSharp.text.pdf;
namespace RleTests
{
class Program
{
const char RightToLeftEmbedding = (char)0x202B;
const char PopDirectionalFormatting = (char)0x202C;
static string FixWeakCharacters(string data)
{
if (string.IsNullOrWhiteSpace(data)) return string.Empty;
var weakCharacters = new[] { @"\", "/", "+", "-", "=", ";", "$" };
foreach (var weakCharacter in weakCharacters)
{
data = data.Replace(weakCharacter, RightToLeftEmbedding + weakCharacter + PopDirectionalFormatting);
}
return data;
}
static void Main(string[] args)
{
using (var pdfDoc = new Document(PageSize.A4))
{
PdfWriter.GetInstance(pdfDoc, new FileStream("Test.pdf", FileMode.Create));
pdfDoc.Open();
FontFactory.Register("c:\\windows\\fonts\\Arial.ttf");
Font tahoma = FontFactory.GetFont("Arial", BaseFont.IDENTITY_H);
var table1 = new PdfPTable(1);
table1.WidthPercentage = 100;
var pdfCell = new PdfPCell
{
RunDirection = PdfWriter.RUN_DIRECTION_RTL,
Border = 0,
Phrase = new Phrase(FixWeakCharacters(
"تاریخ: " + "1390/11/18" + Environment.NewLine +
"شماره پروژه: " + "1/2/3/4/56" + Environment.NewLine +
"اسلش: " + " 12/A/13 " + Environment.NewLine +
"بک اسلش: " + " 12\\13\\14 " + Environment.NewLine +
"مساوی و جمع: " + " 2+3=5 " + Environment.NewLine +
"سمی کولون: " + " 2=1+1; " + Environment.NewLine +
"دلار: " + "12$" + Environment.NewLine +
"کاما: " + "12,34,67" + Environment.NewLine +
"نقطه: " + "12.34" + Environment.NewLine +
"پرانتز: " + "متن (ساده)"
),
tahoma)
};
table1.AddCell(pdfCell);
pdfDoc.Add(table1);
}
Process.Start("Test.pdf");
}
}
}
از این نوع مشکلات حین کار با HTML هم هست؛ وارونه نمایش داده شدن تاریخ فارسی در بین یک متن راست به چپ. البته در آنجا راه حل زیر هم توصیه شده (بدون نیاز به دستکاری نویسهها):
<span dir="ltr" style="display:inline">1390/11/19</span>