فصل ۱۷: Resourceها، Satellite Assemblyها و Localization در .NET
فروش یا انتشار این ترجمه منوط به داشتن مجوز لازم از صاحب حقوق اثر است.
Time Stamping
پس از Expire شدن Certificate دیگر نمیتوانید Programهای جدید را با آن Sign کنید. بااینحال، Programهایی که پیش از Expire شدن Sign کردهاید همچنان معتبر خواهند ماند، به شرط آنکه هنگام Signing با Switch به نام /tr یک Time-Stamping Server مشخص کرده باشید. CA برای این کار یک URI در اختیارتان میگذارد. نمونهٔ زیر مربوط به Comodo یا K Software است:
... /tr http://timestamp.comodoca.com/authenticode /td SHA256
بررسی اینکه Program امضا شده است
سادهترین راه برای مشاهدهٔ Signature مربوط به Authenticode روی یک File، باز کردن Properties آن File در Windows Explorer و رفتن به Tab به نام Digital Signatures است. Utility به نام signtool نیز Optionی برای این کار دارد.
Resourceها و Satellite Assemblyها
یک Application معمولاً فقط Executable Code ندارد و Contentهایی مانند Text، Image یا XML File را هم شامل میشود. چنین Contentی میتواند از طریق Resource داخل Assembly نمایش داده شود. Resourceها دو کاربرد همپوشان دارند:
- قرار دادن Dataای که نمیتواند در Source Code جا بگیرد، مانند Imageها.
- ذخیرهٔ Dataای که ممکن است در یک Application چندزبانه نیاز به Translation داشته باشد.
Assembly Resource در نهایت یک Byte Stream دارای Name است. میتوانید Assembly را مانند Dictionaryای از Byte Arrayها در نظر بگیرید که با String Key شدهاند. اگر Assemblyای را که Resourceی به نام banner.jpg و Resource دیگری به نام data.xml دارد با ildasm Disassemble کنید، چیزی شبیه زیر میبینید:
.mresource public banner.jpg
{
// Offset: 0x00000F58 Length: 0x000004F6
}
.mresource public data.xml
{
// Offset: 0x00001458 Length: 0x0000027E
}
در این حالت، banner.jpg و data.xml مستقیماً در Assembly قرار گرفتهاند، هرکدام بهصورت یک Embedded Resource مستقل. این سادهترین شیوهٔ کار است.
.NET همچنین اجازه میدهد Content را از طریق Containerهای میانی با پسوند .resources اضافه کنید. این Containerها برای Contentهایی طراحی شدهاند که ممکن است لازم باشد به Languageهای مختلف ترجمه شوند. نسخههای Localized از .resources را میتوان به شکل Satellite Assemblyهای جداگانه Package کرد که بر اساس Language مربوط به OS کاربر در Runtime بهصورت خودکار انتخاب میشوند.
شکل 17-2 Assemblyای را نشان میدهد که دو Resource مستقیم Embedشده بهعلاوهٔ یک Container به نام welcome.resources دارد و برای آن دو Satellite Localized ساخته شده است.
شکل 17-2 — Resourceهای مستقیم، Resourceهای Localized و Satellite Assemblyهای آلمانی و فرانسوی.Embed کردن مستقیم Resourceها
برای Embed کردن مستقیم Resource با Visual Studio:
- File را به Project اضافه کنید.
- Build Action آن را روی
Embedded Resource قرار دهید.
Visual Studio همیشه Name مربوط به Resource را با Default Namespace پروژه و سپس Name زیرپوشههایی که File در آن قرار دارد Prefix میکند. پس اگر Default Namespace پروژه Westwind.Reports باشد و File به نام banner.jpg در Folder به نام pictures قرار گرفته باشد، Resource Name به شکل Westwind.Reports.pictures.banner.jpg خواهد بود.
برای دریافت Resource، متد GetManifestResourceStream را روی Assembly حاوی Resource فراخوانی میکنید. این متد Stream برمیگرداند و میتوانید مثل هر Stream دیگری آن را بخوانید:
Assembly a = Assembly.GetEntryAssembly();
using (Stream s = a.GetManifestResourceStream ("TestProject.data.xml"))
using (XmlReader r = XmlReader.Create (s))
...
System.Drawing.Image image;
using (Stream s = a.GetManifestResourceStream ("TestProject.banner.jpg"))
image = System.Drawing.Image.FromStream (s);
Stream برگشتی Seekable است، بنابراین میتوانید چنین نیز بنویسید:
byte[] data;
using (Stream s = a.GetManifestResourceStream ("TestProject.banner.jpg"))
data = new BinaryReader (s).ReadBytes ((int) s.Length);
اگر از Visual Studio برای Embed کردن Resource استفاده کردهاید، باید Prefix مبتنی بر Namespace را به خاطر داشته باشید. برای کاهش احتمال Error میتوانید Prefix را با یک Type در Argument جداگانه مشخص کنید. Namespace مربوط به Type بهعنوان Prefix استفاده میشود:
using (Stream s = a.GetManifestResourceStream (typeof (X), "data.xml"))
X میتواند هر Typeای با Namespace موردنظر Resource باشد؛ معمولاً Typeای در همان Project Folder.
GetManifestResourceNames Name همهٔ Resourceهای داخل Assembly را برمیگرداند.
Fileهای .resources
Fileهای .resources Containerهایی برای Content بالقوه Localizable هستند. File با پسوند .resources در نهایت مانند هر File دیگری بهصورت Embedded Resource داخل Assembly قرار میگیرد. تفاوت این است که باید:
- از ابتدا Content خود را داخل File با پسوند
.resources Package کنید. - بهجای
GetManifestResourceStream، از ResourceManager یا Pack URI برای دسترسی به Content استفاده کنید.
Fileهای .resources ساختار Binary دارند و Human-Editable نیستند؛ بنابراین برای کار با آنها باید به Toolهای .NET و Visual Studio تکیه کنید.
رویکرد استاندارد برای Stringها یا Data Typeهای ساده استفاده از Format با پسوند .resx است که Visual Studio یا Tool به نام resgen میتواند آن را به .resources تبدیل کند. Format با پسوند .resx برای Imageهایی که در Windows Forms یا ASP.NET استفاده میشوند نیز مناسب است.
در یک WPF Application، برای Imageها یا Content مشابهی که باید با URI Reference شوند، باید Build Action با عنوان Resource را در Visual Studio استفاده کنید؛ چه Localization لازم باشد چه نباشد.
Fileهای .resx
File با پسوند .resx یک Design-Time Format برای تولید Fileهای .resources است. .resx از XML استفاده میکند و با جفتهای Name/Value به شکل زیر ساختاربندی میشود:
<root>
<data name="Greeting">
<value>hello</value>
</data>
<data name="DefaultFontSize" type="System.Int32, mscorlib">
<value>10</value>
</data>
</root>
برای ساخت File با پسوند .resx در Visual Studio، یک Project Item از نوع Resources File اضافه کنید. بقیهٔ کار خودکار است:
- Header صحیح ساخته میشود.
- Designer برای افزودن String، Image، File و انواع دیگر Data در اختیار قرار میگیرد.
- هنگام Compilation، File با پسوند
.resx خودکار به Format .resources تبدیل و در Assembly Embed میشود. - کلاسی تولید میشود که بعداً به دسترسی به Data کمک میکند.
خواندن Fileهای .resources
کلاس ResourceManager Fileهای .resources Embedشده در Assembly را میخواند:
ResourceManager r = new ResourceManager ("welcome",
Assembly.GetExecutingAssembly());
اگر Resource در Visual Studio Compile شده باشد، Argument اول باید Namespace Prefix داشته باشد.
سپس با GetString یا GetObject همراه Cast میتوانید به محتوا دسترسی پیدا کنید.
string greeting = r.GetString ("Greeting");
int fontSize = (int) r.GetObject ("DefaultFontSize");
Image image = (Image) r.GetObject ("flag.png");
برای Enumerate کردن محتوای File با پسوند .resources:
ResourceManager r = new ResourceManager (...);
ResourceSet set = r.GetResourceSet (CultureInfo.CurrentUICulture,
true, true);
foreach (System.Collections.DictionaryEntry entry in set)
Console.WriteLine (entry.Key);
ساخت Pack URI Resource در Visual Studio
در WPF Application، Fileهای XAML باید بتوانند از طریق URI به Resourceها دسترسی داشته باشند. برای مثال:
<Button>
<Image Height="50" Source="flag.png"/>
</Button>
یا اگر Resource در Assembly دیگری است:
<Button>
<Image Height="50" Source="UtilsAssembly;Component/flag.png"/>
</Button>
Component در اینجا یک Keyword Literal است.
برای ساخت Resourceهایی که اینگونه Load شوند، نمیتوانید از Fileهای .resx استفاده کنید. در عوض، Fileها را به Project اضافه کرده و Build Action را روی Resource، نه Embedded Resource، قرار دهید. Visual Studio سپس آنها را داخل File با پسوند .resources به نام <AssemblyName>.g.resources Compile میکند؛ همان File همچنین محل Fileهای XAML Compileشده با پسوند .baml است.
برای Load کردن Resource دارای Key از نوع URI بهصورت Programmatic، Application.GetResourceStream را فراخوانی کنید:
Uri u = new Uri ("flag.png", UriKind.Relative);
using (Stream s = Application.GetResourceStream (u).Stream)
در اینجا از Relative URI استفاده شده است. Absolute URI را نیز دقیقاً با Format زیر میتوانید به کار ببرید؛ سه Comma اشتباه تایپی نیستند:
Uri u = new Uri ("pack://application:,,,/flag.png");
اگر ترجیح میدهید یک Assembly Object را مشخص کنید، میتوانید Content را با ResourceManager بگیرید:
Assembly a = Assembly.GetExecutingAssembly();
ResourceManager r = new ResourceManager (a.GetName().Name + ".g", a);
using (Stream s = r.GetStream ("flag.png"))
...
ResourceManager همچنین اجازه میدهد Content یک Container به نام .g.resources را در Assembly مشخص Enumerate کنید.
Satellite Assemblyها
Dataای که در .resources Embed شده است قابل Localization است.
Resource Localization زمانی مهم میشود که Application روی نسخهای از Windows اجرا شود که برای نمایش همهچیز به Language دیگری ساخته شده است. برای Consistency، Application شما نیز باید همان Language را استفاده کند.
Setup معمول چنین است:
- Main Assembly شامل
.resources مربوط به Language پیشفرض یا Fallback است. - Satellite Assemblyهای جداگانه شامل
.resourcesهای Localized هستند که به Languageهای مختلف ترجمه شدهاند.
وقتی Application اجرا میشود، .NET Language مربوط به OS فعلی را از CultureInfo.CurrentUICulture بررسی میکند. هر بار Resourceی را با ResourceManager درخواست میکنید، Runtime بهدنبال Satellite Assembly Localized میگردد. اگر موجود باشد و Resource Key درخواستی شما را داشته باشد، بهجای Version موجود در Main Assembly استفاده میشود.
یعنی با افزودن Satelliteهای جدید میتوانید Language Support را بدون تغییر Main Assembly توسعه دهید.
Satellite Assemblyها در Subdirectoryهای Folder مربوط به Assembly Deploy میشوند:
programBaseFolder\MyProgram.exe
\MyLibrary.exe
\XX\MyProgram.resources.dll
\XX\MyLibrary.resources.dll
XX کد دوحرفی Language مانند de برای German یا کد Language و Region مانند en-GB برای English در Great Britain است. این Naming System به CLR اجازه میدهد Satellite Assembly صحیح را خودکار پیدا و Load کند.
ساخت Satellite Assembly
مثال قبلی .resx شامل این بخش بود:
<root>
...
<data name="Greeting">
<value>hello</value>
</data>
</root>
سپس Greeting را در Runtime چنین میگرفتیم:
ResourceManager r = new ResourceManager ("welcome",
Assembly.GetExecutingAssembly());
Console.Write (r.GetString ("Greeting"));
فرض کنید میخواهیم اگر Application روی نسخهٔ German از Windows اجرا شد، بهجای hello عبارت hallo نوشته شود. اولین گام افزودن File دیگری به نام welcome.de.resx است که hello را با hallo جایگزین کند:
<root>
<data name="Greeting">
<value>hallo<value>
</data>
</root>
در Visual Studio همین کار کافی است. هنگام Rebuild، Satellite Assemblyای به نام MyApp.resources.dll بهصورت خودکار در Subdirectory به نام de ساخته میشود.
تست Satellite Assemblyها
برای شبیهسازی اجرا روی OS با Language دیگر، باید با کلاس Thread مقدار CurrentUICulture را تغییر دهید:
System.Threading.Thread.CurrentThread.CurrentUICulture
= new System.Globalization.CultureInfo ("de");
CultureInfo.CurrentUICulture نسخهٔ Read-Only همین property است.
پشتیبانی Visual Studio Designer
Designerهای Visual Studio برای Localize کردن Componentها و Visual Elementها پشتیبانی بیشتری ارائه میکنند. WPF Designer Workflow خاص خودش را برای Localization دارد. Designerهای Component-Based دیگر از یک Design-Time-Only Property استفاده میکنند تا به نظر برسد Component یا Windows Forms Control یک property به نام Language دارد. برای Customize کردن Language دیگر، کافی است Language را تغییر دهید و سپس Component را ویرایش کنید. همهٔ Propertyهای Controlها که با Localizable Attribute مشخص شدهاند در File با پسوند .resx همان Language ذخیره میشوند. هر زمان میتوانید فقط با تغییر Language بین Languageها جابهجا شوید.
Cultureها و Subcultureها
Cultureها به Culture و Subculture تقسیم میشوند. Culture یک Language مشخص را نمایش میدهد و Subculture یک Variation منطقهای از آن Language را. Runtime مربوط به .NET استاندارد RFC1766 را دنبال میکند که Culture و Subculture را با Codeهای دوحرفی نمایش میدهد. Codeهای English و German چنین هستند:
ادامهٔ بحث Culture، Subculture و سپس Assembly Loading در مقالهٔ بعدی آمده است.