sn -T MyAssembly.dll
یکی کردن اسمبلیها با استفاده از Eazfuscator
RSA
یا
MD5
ممکن است استفاده گردد یا صرفا روشها بازگشت پذیرند؟
csc /t:module RUT.cs csc /t:module FUT.cs al /out: MultiFileLibrary.dll /t:library FUT.netmodule RUT.netmodule
این ابزار همچنین میتواند فایلهای CUI ,GUI و ... را با سوئیچهای زیر هم تولید کند:
/t[arget]:exe, /t[arget]:winexe, or /t[arget]:appcontainerexe
البته اینکار تا حدی غیر معمول است که یک فایل exe بخواهد کدهای IL ابتدایی را از ماژولهای جداگانه بخواند. در صورتیکه چنین قصدی را دارید، باید یکی از ماژولها را به عنوان مدخل ورودی Main تعریف کنید تا برنامه از آنجا آغاز به کار کند. نحوهی ساخت یک فایل اجرایی و معرفی ماژول Main به شکل زیر است:
csc /t:module /r:MultiFileLibrary.dll Program.cs al /out:Program.exe /t:exe /main:Program.Main Program.netmodule
.method privatescope static void __EntryPoint$PST06000001() cil managed { .entrypoint // Code size 8 (0x8) .maxstack 8 IL_0000: tail. IL_0002: call void [.module 'Program.netmodule']Program::Main() IL_0007: ret } // end of method 'Global Functions'::__EntryPoint
کد بالا یک کد ساده است که میگوید داخل فایل Program.netmodule در نوع Program متدی وجود دارد به نام Main که محل آغازین برنامه است. البته این روش ایجاد فایلهای EXE، بدین شکل توصیه چندانی نمیشود و ذکر این مطلب فقط اطلاع از وجود چنین قابلیتی بود.
افزونه فارسی به پارسی را قبل از ارائه در سایت، بر روی یک ماشین مجازی هم تست کردم. برای این منظور از Microsoft virtual pc استفاده شد. البته در مقابل امکانات VMware شاید حرفی برای گفتن نداشته باشد ولی خوب جهت مقاصد تست نرم افزار بر روی یک سیستم عاری از وسایل برنامه نویسی مناسب است. (برای نصب یک سیستم عامل توسط آن برای مثال میشود از سی دی آن OS یک فایل ISO تهیه کرد و مسیر این فایل ISO را به ماشین مجازی معرفی کرد . سپس سیستم بوت شده و روال نصب مطابق معمول خواهد بود)
اولین مشکلی که پس از تست بر روی سیستم مجازی رخ داد، پیغام یافت نشدن اسمبلی مربوط به SQLite بود. نرم افزار word هنگام اجرای افزونههای دات نت، آنها را در مسیری با یک نامگذاری منحصربفرد کپی میکند و تنها هم همان اسمبلی افزونه را کپی میکند و نه سایر موارد همراه را. برای پیدا کردن این مسیر میشود از روش زیر استفاده کرد:
using System.Reflection;
Assembly.GetExecutingAssembly().Location
برای نصب اسمبلی در GAC استفاده از برنامه gacutil توصیه شده است. این برنامه به همراه SDK دات نت فریم ورک ارائه میشود و الزامی ندارد که کاربر نهایی این برنامه را داشته باشد. خوشبختانه با استفاده از برنامه نویسی هم میشود یک نمونه از برنامه Gacutil را خودمان ایجاد کنیم (برای مثال ایجاد یک برنامه کنسول و دریافت مسیر از طریق آرگومانهای ارسالی به آن):
new System.EnterpriseServices.Internal.Publish().GacInstall(path);
این روش در مورد اسمبلی SQLite که دارای امضای دیجیتال است کار خواهد کرد. اما اگر قصد داشته باشید به صورت عمومی از آن استفاده کنید، باید ابتدا بررسی کرد که آیا فایل اسمبلی دارای امضای دیجیتال است یا خیر. برای این منظور میتوان مقدار عبارت زیر را ارزیابی کرد:
Assembly.LoadFile(path).GetName().GetPublicKey().Length
لازم به ذکر است که متد معرفی شده برای نصب در GAC در صورت عدم موفقیت هیچ پیغام خطا یا exception ایی را در برنامه تولید نخواهد کرد. اما پیغام خطای حاصل را در event log ویندوز میتوان مشاهده کرد.
نسخه جدید برنامه Eazfuscator به همراه دو قابلیت جالب یکی کردن و همچنین مدفون نمودن اسمبلیها ارائه شده است:
یکی کردن چند اسمبلی با هم
Eazfuscator برای یکی کردن اسمبلیها از برنامه معروف ILmerge استفاده میکند با این تفاوت که دیگر نیازی نیست تا پارامترهای آنرا تنظیم کرد و بسیاری از مسایل را به صورت خودکار مدیریت میکند.
جهت فعال کردن این قابلیت، یکی از روشهای کار به صورت زیر است:
فایلی به نام ObfuscationSettings.cs را به پروژه خود اضافه کرده، سپس محتویات آنرا حذف نموده و با چند سطر زیر جایگزین و کامپایل کنید:
using System;
using System.Reflection;
[assembly: Obfuscation(Feature = "merge with file1.dll", Exclude = false)]
[assembly: Obfuscation(Feature = "merge with file2.dll", Exclude = false)]
[assembly: Obfuscation(Feature = "merge with file3.dll", Exclude = false)]
همانطور که ملاحظه میکنید این چند سطر حاوی نام اسمبلیهایی میباشند که قرار است با اسمبلی جاری یکی شوند.
سپس اسمبلی جاری را (میخواهد فایل exe باشد یا یک dll ، فرقی نمیکند) بر روی Eazfuscator کشیده و رها کنید. پس از چند لحظه اسمبلی نهایی تولید شده شامل تمام کلاسها و منابع اسمبلیهایی خواهد بود که در فایل ObfuscationSettings.cs ذکر شدهاند؛ به همراه Obfuscation خودکار آنها.
مدفون کردن اسمبلیها در یک اسمبلی
قابلیت دیگر این برنامه دفن (embedding) چند اسمبلی در اسمبلی نهایی است. برای فعال سازی آن روش کار همانند قبل است با این تفاوت که بجای merge with باید نوشت embed . برای مثال:
[assembly: Obfuscation(Feature = "embed Common.dll", Exclude = false)]
به این ترتیب اسمبلیهای ذکر شده پس از رمزنگاری و فشرده شدن به صورت منابع اسمبلی جاری ذخیره خواهند شد. مدیریت استفاده از آنها هم خودکار است و نیازی نیست تا کاری در این مورد صورت گیرد.
برای نمونه برنامه معروف LINQPad از همین روش استفاده میکند و لازم به ذکر است که ... هنوز که هنوز است هیچ ک.ر.ک. کارسازی برای فعال سازی قسمت intellisense آن که رایگان نیست ارائه نشده و تمام وصلههای جدید ارائه شده کار نمیکنند ...
تفاوت مدفون کردن با یکی کردن چیست؟
در حالت یکی کردن اسمبلیها، سربار اولیه بارگذاری برنامه همانند روش مدفون سازی وجود ندارد. اما این سربار آنقدر ناچیز است که کسی آنرا احساس نخواهد کرد. مورد دیگر، عدم پشتیبانی از روش مدفون سازی در سایر سکوهای کاری مانند ویندوز فون، Compact Framework و غیره است. اما باید درنظر داشت که برای مثال ILMerge روی اسمبلیهای دارای XAML کار نمیکند (مطابق مستندات رسمی آن). بنابراین همیشه نمیتوان از روش یکی سازی استفاده کرد و محدودیتهای خاص خودش را دارد.
در کل روش مدفون سازی به دلیل Obfuscation ، فشرده سازی و رمزنگاری همزمان، امنیت بیشتری را نسبت به حالت Obfuscation تنها ارائه میدهد (حداقل شخص "علاقمند" به مطالعه این نوع اسمبلیها باید از چند لایه رد شود و تجربه برنامه LINQPad ثابت کرده که این روش در مقیاس کلان (در انظار عمومی هزاران علاقمند) بسیار موفق بوده است).
در مورد کاهش حجم فایلهای XAP سیلورلایت زمانیکه از اسمبلیهای کتابخانههای دیگر مانند Silverlight toolkit استفاده میشود، در این فصل بحث شده است و راه حل، استفاده از گزینهی reduce XAP size by using application library caching است. به این صورت کاربران دیگر به ازای هر بار مشاهدهی سایت نیازی نخواهند داشت تا یک سری کتابخانهی کمکی را که هیچ تغییری در آنها حاصل نخواهد شد، دریافت کنند و اطلاعات آنها از cache مرورگر خوانده میشود. این مورد با کتابخانهها و ابزارهای کمکی تولید شده توسط مایکروسافت کار میکند. اما اگر خودتان یک Silverlight library را تولید کنید، چنین اتفاقی رخ نخواهد داد و باز هم فایل اسمبلی کتابخانهی شما درون فایل XAP اصلی برنامه قرار گرفته و خبری از caching مجزای آن نیست. چرا اینطور است؟ چکار باید کرد؟!
علت آن بر میگردد به نحوهی پیاده سازی library caching در VS.NET و Silverlight . برای این منظور چند مرحله باید طی شود تا این قابلیت برای کتابخانههای ساخت خودمان نیز فعال گردد:
الف) به کتابخانهی خود باید امضای دیجیتال اضافه کنید:
اینکار با استفاده از امکانات خود VS.NET بسیار ساده است. به خواص پروژه مراجعه کنید. سپس برگهی Signing را باز کرده و گزینهی Sign the assembly را انتخاب کنید (شکل زیر). در قسمت choose a strong name key file ، گزینهی new را انتخاب کرده و پس از وارد کردن یک نام دلخواه و گذر واژهای، فایل pfx امضای دیجیتال اسمبلی شما تولید خواهد شد. اکنون تنها کافی است یکبار دیگر برنامه را کامپایل کنید.
ب) به یک فایل extMap.xml هم نیاز است:
هنگام پیاده سازی قابلیت library caching ، VS.NET به دنبال فایلی به نام AssemblyFileName.extmap.xml دقیقا در کنار فایل اسمبلی مورد نظر میگردد. ساختار عمومی این فایل XML به صورت زیر است:
<?xml version="1.0"?>
<manifest xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<assembly>
<name>SLHelper</name>
<version>1.0.0.0</version>
<publickeytoken>f265933def965412</publickeytoken>
<relpath>SLHelper.dll</relpath>
<extension downloadUri="SLHelper.zip" />
</assembly>
</manifest>
نام، شماره نگارش، مسیر قرارگیری فایل اسمبلی مورد نظر و همچنین نام نهایی آن حین جدا سازی آن از XAP برنامه باید مشخص گردد. گزینهی publickeytoken مهمترین تنظیم این فایل است و قسمت الف را به همین منظور نیاز داشتیم. این عدد را به سادگی با استفاده از برنامهی reflector میتوان بدست آورد (شکل زیر).
جهت ساده سازی قسمت (ب)، برنامهی کمکی را از آدرس ذیل میتوانید دریافت کنید:
Utility: Extmap Maker
برای مطالعه بیشتر
Silverlight 3: Cached Assemblies and you can to
public abstract class ResourceProviderFactory { public abstract IResourceProvider CreateGlobalResourceProvider(string classKey); public abstract IResourceProvider CreateLocalResourceProvider(string virtualPath); }
public interface IResourceProvider { IResourceReader ResourceReader { get; } object GetObject(string resourceKey, CultureInfo culture); }
// Type: System.Web.Compilation.ResXResourceProviderFactory // Assembly: System.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a // Assembly location: C:\Windows\Microsoft.NET\assembly\GAC_32\System.Web\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Web.dll using System.Runtime; using System.Web; namespace System.Web.Compilation { internal class ResXResourceProviderFactory : ResourceProviderFactory { [TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")] public ResXResourceProviderFactory() { } public override IResourceProvider CreateGlobalResourceProvider(string classKey) { return (IResourceProvider) new GlobalResXResourceProvider(classKey); } public override IResourceProvider CreateLocalResourceProvider(string virtualPath) { return (IResourceProvider) new LocalResXResourceProvider(VirtualPath.Create(virtualPath)); } } }
internal class GlobalResXResourceProvider : BaseResXResourceProvider { private string _classKey; internal GlobalResXResourceProvider(string classKey) { _classKey = classKey; } protected override ResourceManager CreateResourceManager() { string fullClassName = BaseResourcesBuildProvider.DefaultResourcesNamespace + "." + _classKey; // If there is no app resource assembly, return null if (BuildManager.AppResourcesAssembly == null) return null; ResourceManager resourceManager = new ResourceManager(fullClassName, BuildManager.AppResourcesAssembly); resourceManager.IgnoreCase = true; return resourceManager; } public override IResourceReader ResourceReader { get { // App resources don't support implicit resources, so the IResourceReader should never be needed throw new NotSupportedException(); } } }
internal const string DefaultResourcesNamespace = "Resources";
internal class LocalResXResourceProvider : BaseResXResourceProvider { private VirtualPath _virtualPath; internal LocalResXResourceProvider(VirtualPath virtualPath) { _virtualPath = virtualPath; } protected override ResourceManager CreateResourceManager() { ResourceManager resourceManager = null; Assembly pageResAssembly = GetLocalResourceAssembly(); if (pageResAssembly != null) { string fileName = _virtualPath.FileName; resourceManager = new ResourceManager(fileName, pageResAssembly); resourceManager.IgnoreCase = true; } else { throw new InvalidOperationException(SR.GetString(SR.ResourceExpresionBuilder_PageResourceNotFound)); } return resourceManager; } public override IResourceReader ResourceReader { get { // Get the local resource assembly for this page Assembly pageResAssembly = GetLocalResourceAssembly(); if (pageResAssembly == null) return null; // Get the name of the embedded .resource file for this page string resourceFileName = _virtualPath.FileName + ".resources"; // Make it lower case, since GetManifestResourceStream is case sensitive resourceFileName = resourceFileName.ToLower(CultureInfo.InvariantCulture); // Get the resource stream from the resource assembly Stream resourceStream = pageResAssembly.GetManifestResourceStream(resourceFileName); // If this page has no resources, return null if (resourceStream == null) return null; return new ResourceReader(resourceStream); } } [PermissionSet(SecurityAction.Assert, Unrestricted = true)] private Assembly GetLocalResourceAssembly() { // Remove the page file name to get its directory VirtualPath virtualDir = _virtualPath.Parent; // Get the name of the local resource assembly string cacheKey = BuildManager.GetLocalResourcesAssemblyName(virtualDir); BuildResult result = BuildManager.GetBuildResultFromCache(cacheKey); if (result != null) { return ((BuildResultCompiledAssembly)result).ResultAssembly; } return null; } }
نکته: باتوجه به استفاده از عبارات بومیسازی ضمنی در استفاده از ورودیهای منابع محلی، خاصیت ResourceReader در این کلاس نمونهای متناظر برای درخواست جاری از کلاس ResourceReader با استفاده از Stream استخراج شده از اسمبلی یافته شده، تولید میکند.
internal abstract class BaseResXResourceProvider : IResourceProvider { private ResourceManager _resourceManager; ///// IResourceProvider implementation public virtual object GetObject(string resourceKey, CultureInfo culture) { // Attempt to get the resource manager EnsureResourceManager(); // If we couldn't get a resource manager, return null if (_resourceManager == null) return null; if (culture == null) culture = CultureInfo.CurrentUICulture; return _resourceManager.GetObject(resourceKey, culture); } public virtual IResourceReader ResourceReader { get { return null; } } ///// End of IResourceProvider implementation protected abstract ResourceManager CreateResourceManager(); private void EnsureResourceManager() { if (_resourceManager != null) return; _resourceManager = CreateResourceManager(); } }
System.Resources.ResourceManager(string baseName, Assembly assemblyName)
var manager = new System.Resources.ResourceManager("Resources.Resource1", typeof(Resource1).Assembly)
var manager = new System.Resources.ResourceManager("Resources.Resource1", Assembly.LoadFile(@"c:\MyResources\MyGlobalResources.dll"))
public static ResourceManager CreateFileBasedResourceManager(string baseName, string resourceDir, Type usingResourceSet)
resgen d:\MyResources\MyResource.fa.resx
private ResourceManager CreateGlobalResourceManager(string classKey) { var baseName = "Resources." + classKey; var buildManagerType = typeof(BuildManager); var property = buildManagerType.GetProperty("AppResourcesAssembly", BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.GetField); var appResourcesAssembly = (Assembly)property.GetValue(null, null); return new ResourceManager(baseName, appResourcesAssembly) { IgnoreCase = true }; }
var manager = CreateGlobalResourceManager("Resource1"); Label1.Text = manager.GetString("String1");
private ResourceManager CreateLocalResourceManager(string virtualPath) { var virtualPathType = typeof(BuildManager).Assembly.GetType("System.Web.VirtualPath", true); var virtualPathInstance = Activator.CreateInstance(virtualPathType, BindingFlags.NonPublic | BindingFlags.Instance, null, new object[] { virtualPath }, CultureInfo.InvariantCulture); var buildResultCompiledAssemblyType = typeof(BuildManager).Assembly.GetType("System.Web.Compilation.BuildResultCompiledAssembly", true); var propertyResultAssembly = buildResultCompiledAssemblyType.GetProperty("ResultAssembly", BindingFlags.NonPublic | BindingFlags.Instance); var methodGetLocalResourcesAssemblyName = typeof(BuildManager).GetMethod("GetLocalResourcesAssemblyName", BindingFlags.NonPublic | BindingFlags.Static); var methodGetBuildResultFromCache = typeof(BuildManager).GetMethod("GetBuildResultFromCache", BindingFlags.NonPublic | BindingFlags.Static, null, new Type[] { typeof(string) }, null); var fileNameProperty = virtualPathType.GetProperty("FileName"); var virtualPathFileName = (string)fileNameProperty.GetValue(virtualPathInstance, null); var parentProperty = virtualPathType.GetProperty("Parent"); var virtualPathParent = parentProperty.GetValue(virtualPathInstance, null); var localResourceAssemblyName = (string)methodGetLocalResourcesAssemblyName.Invoke(null, new object[] { virtualPathParent }); var buildResultFromCache = methodGetBuildResultFromCache.Invoke(null, new object[] { localResourceAssemblyName }); Assembly localResourceAssembly = null; if (buildResultFromCache != null) localResourceAssembly = (Assembly)propertyResultAssembly.GetValue(buildResultFromCache, null); if (localResourceAssembly == null) throw new InvalidOperationException("Unable to find the matching resource file."); return new ResourceManager(virtualPathFileName, localResourceAssembly) { IgnoreCase = true }; }
نحوه استفاده از متد فوق نیز به صورت زیر است:
var manager = CreateLocalResourceManager("~/Default.aspx"); Label1.Text = manager.GetString("Label1.Text");
در مقالهی قبلی وقتی نسخهی یک اسمبلی را مشخص میکردیم، از 4 عدد که با نقطه از هم جدا شده بودند، استفاه کردیم که در جدول زیر این 4 نامگذاری را مشاهده میکنید:
شماره بازبینی Revision Number | شماره ساخت Build Number | شماره جزئی Minor Number | شماره اصلی Major Number |
2 | 719 | 5 | 2 |
اسمبلی بالا به ورژن یا نسخهی 2.5.719.2 اشاره دارد که دو شمارهی اول (2.5) مثل تمامی برنامهها به میزان تغییرات کارکردی یک اسمبلی اشاره دارد و عموم مردم هم نسخه یک نرم افزار را به همین دو عدد میشناسند. عدد سوم به این اشاره دارد که در شرکت، این ورژن از اسمبلی چندبار build شده است و شما باید به ازای هر Build این عدد را افزایش دهید. عدد آخری به این اشاره دارد که در طول روز انتشار، این چندمین Build بوده است. اگر در زمان ارائهی این اسمبلی باگ مهمی در آن یافت شود، با هر بار Build آن در یک روز، باید این عدد افزایش یابد و برای روزهای آتی این مقدار مجددا آغاز میشود. مایکروسافت از این سیستم نسخه بندی استفاده میکند و بسیار توصیه میشود که توسعه دهندگان هم از این روش تبعیت کنند.
در جدول سابق شما متوجه شدید که سه نسخه بندی را میتوان روی یک اسمبلی اعمال کرد که به شرح زیر است:
AssemblyFileVersion: این شماره نسخه در منابع اطلاعاتی Win32 ذخیره میگردد و کاربرد آن تنها جهت نمایش این اطلاعات است و CLR هیچ گونه ارجاع یا استفادهای از آن ندارد. در حالت عادی، شما باید دو شماره اولی را جهت نمایش عمومی مشخص کنید. سپس با هر بار Build کردن، شمارههای ساخت و بازبینی را هم به همان ترتیب افزایش میدهید. حالت ایدهآل این است که ابزار AL یا CSC به طور خودکار با هر بار Build شدن، با توجه به زمان سیستم، به طور خودکار این دو شماره آخر را مشخص کنند ولی متاسفانه واقعیت این است که چنین کاری صورت نمیگیرد. این اعداد جهت نمایش و شناسایی اسمبلی برای اشکال زدایی مشکلات اسمبلی به کار میرود.
AssemblyInformationalVersion: این شماره نسخه هم در منابع اطلاعاتی Win32 ذخیره میگردد و تنها هدف اطلاعاتی دارد. مجددا اینکه CLR هیچ گونه اهمیتی به آن نمیدهد. این شماره نسخه به محصولی اشاره میکند که شامل این اسمبلی است.
به عنوان مثال ورژن 2 یک نرم افزار ممکن است شامل چند اسمبلی باشد که ورژن یکی از این اسمبلیها یک است و دلیلش هم این است که این اسمبلی از نسخهی 2 به بعد اضافه شده و در نسخهی یک نرم افزار وجود نداشته است. به همین دلیل در این مدل از نسخه بندی شما دو شماره اول را به نسخه خود نرم افزار مقداردهی کرده و سپس مابقی اعداد را با هر بار پکیج شدن کل نرم افزار با توجه به زمان افزایش میدهید.
AssemblyVersion: این شماره نسخه در جدول متادیتای AssemblyDef ذخیره میگردد. CLR از این شماره نسخه جهت اتصال نام قوی Strongly Named به اسمبلی استفاده میکند (این مورد در فصل سه کتاب توضیح داده شده است). این شماره نسخه بسیار مهم بوده و به عنوان شناسهی یکتا برای اسمبلی استفاده میشود.
موقعیکه شما شروع به توسعهی یک اسمبلی میکنید، باید هر 4 شماره نسخه را مقداردهی کرده و تا زمانیکه توسعهی نسخه بعدی آن اسمبلی را آغاز نکردهاید، نباید آن را تغییر دهید. علت اصلی آن این است که موقعیکه اسمبلی «الف» با یک نام قوی به اسمبلی «ب» ارجاع میکند، نسخهی اسمبلی «ب» در ورودی جدول AssemblyRef اسمبلی «الف» قرار گرفته است. این مورد باعث میشود زمانیکه CLR به بارگزاری اسمبلی «ب» احتیاج دارد، دقیقا میداند که چه نسخهای با اسمبلی «الف» ساخته و تست شده است . البته این احتمال وجود دارد که CLR نسخهای متفاوت از اسمبلی را با Binding Redirect بار کند که ادامهی این مباحث در فصل سوم دنبال میشود.
جهت اینکار یک پروژه از نوع class library ایجاد کنید. فایل class1.cs را که به طور پیش فرض ایجاد میشود، حذف کنید و رفرنسهای Microsoft.Web.Management.dll و Microsoft.Web.Administration.dll را از مسیر زیر اضافه کنید:
\Windows\system32\inetsrv
در مرحله بعدی در تب Build Events کد زیر را در بخش Post-build event command line اضافه کنید. این کد باعث میشود بعد از هر بار کامپایل پروژه، به طور خودکار در GAC ثبت شود:
call "%VS80COMNTOOLS%\vsvars32.bat" > NULL gacutil.exe /if "$(TargetPath)"
نکته:در صورتی که از VS2005 استفاده میکنید در تب Debug در قسمت Start External Program مسیر زیر را قرار بدهید. اینکار برای تست و دیباگینگ پروژه به شما کمک خواهد کرد. این تنظیم شامل نسخههای اکسپرس نمیشود.\windows\system32\inetsrv\inetmgr.exe
ساخت یک Module Provider
رابطهای کاربری IIS همانند هسته و کل سیستمش، ماژولار و قابل خصوصی سازی است. رابط کاربری، مجموعهای از ماژول هایی است که میتوان آنها را حذف یا جایگزین کرد. تگ ورودی یا معرفی برای هر UI یک module provider است. خیلی خودمانی، تگ ماژول پروایدر به معرفی یک UI در IIS میپردازد. لیستی از module providerها را میتوان در فایل زیر در تگ بخش <modules> پیدا کرد.
%windir%\system32\inetsrv\Administration.config
در اولین گام یک کلاس را به اسم imageCopyrightUIModuleProvider.cs ایجاد کرده و سپس آنرا به کد زیر، تغییر میدهیم. کد زیر با استفاده از ModuleDefinition یک نام به تگ Module Provider داده و کلاس imageCopyrightUI را که بعدا تعریف میکنیم، به عنوان مدخل entry رابط کاربری معرفی کرده:
using System; using System.Security; using Microsoft.Web.Management.Server; namespace IIS7Demos { class imageCopyrightUIProvider : ModuleProvider { public override Type ServiceType { get { return null; } } public override ModuleDefinition GetModuleDefinition(IManagementContext context) { return new ModuleDefinition(Name, typeof(imageCopyrightUI).AssemblyQualifiedName); } public override bool SupportsScope(ManagementScope scope) { return true; } } }
با ارث بری از کلاس module provider، سه متد بازنویسی میشوند که یکی از آن ها SupportsScope هست که میدان عمل پروایدر را مشخص میکند، مانند اینکه این پرواید در چه میدانی باید کار کند که میتواند سه گزینهی server,site,application باشد. در کد زیر مثلا میدان عمل application انتخاب شده است ولی در کد بالا با برگشت مستقیم true، همهی میدان را جهت پشتیبانی از این پروایدر اعلام کردیم.
public override bool SupportsScope(ManagementScope scope) { return (scope == ManagementScope.Application) ; }
حالا که پروایدر (معرف رابط کاربری به IIS) تامین شده، نیاز است قلب کار یعنی ماژول معرفی گردد. اصلیترین متدی که باید از اینترفیس ماژول پیاده سازی شود متد initialize است. این متد جایی است که تمام عملیات در آن رخ میدهد. در کلاس زیر imageCopyrightUI ما به معرفی مدخل entry رابط کاربری میپردازیم. در سازندههای این متد، پارامترهای نام، صفحه رابط کاربری وتوضیحی در مورد آن است. تصویر کوچک و بزرگ جهت آیکن سازی (در صورت عدم تعریف آیکن، چرخ دنده نمایش داده میشود) و توصیفهای بلندتر را نیز شامل میشود.
internal class imageCopyrightUI : Module { protected override void Initialize(IServiceProvider serviceProvider, ModuleInfo moduleInfo) { base.Initialize(serviceProvider, moduleInfo); IControlPanel controlPanel = (IControlPanel)GetService(typeof(IControlPanel)); ModulePageInfo modulePageInfo = new ModulePageInfo(this, typeof(imageCopyrightUIPage), "Image Copyright", "Image Copyright",Resource1.Visual_Studio_2012,Resource1.Visual_Studio_2012); controlPanel.RegisterPage(modulePageInfo); } }
شیء ControlPanel مکانی است که قرار است آیکن ماژول نمایش داده شود. شکل زیر به خوبی نام همه قسمتها را بر اساس نام کلاس و اینترفیس آنها دسته بندی کرده است:
پس با تعریف این کلاس جدید ما روی صفحهی کنترل پنل IIS، یک آیکن ساخته و صفحهی رابط کاربری را به نام imageCopyrightUIPage، در آن ریجستر میکنیم. این کلاس را پایینتر شرح دادهایم. ولی قبل از آن اجازه بدهید تا انواع کلاس هایی را که برای ساخت صفحه کاربرد دارند، بررسی نماییم. در این مثال ما با استفاده از پایهایترین کلاس، سادهترین نوع صفحه ممکن را خواهیم ساخت. 4 کلاس برای ساخت یک صفحه وجود دارند که بسته به سناریوی کاری، شما یکی را انتخاب میکنید.
ModulePage | شامل اساسیترین متدها و سورسها شده و هیچگونه رابط کاری ویژهای را در اختیار شما قرار نمیدهد. تنها یک صفحهی خام به شما میدهد که میتوانید از آن استفاده کرده یا حتی با ارث بری از آن، کلاسهای جدیدتری را برای ساخت صفحات مختلف و ویژهتر بسازید. در حال حاضر که هیچ کدام از ویژگیهای IIS فعلی از این کلاس برای ساخت رابط کاربری استفاده نکردهاند. |
ModuleDialogPage | یک صفحه شبیه به دیالوگ را ایجاد میکند و شامل دکمههای Apply و Cancel میشود به همراه یک سری متدهای اضافیتر که اجازهی override کردن آنها را دارید. همچنین یک سری از کارهایی چون refresh و از این دست عملیات خودکار را نیز انجام میدهد. از نمونه رابطهایی که از این صفحات استفاده میکنند میتوان machine key و management service را اسم برد. |
ModulePropertiesPage | این صفحه یک رابط کاربری را شبیه پنجره property که در ویژوال استادیو وجود دارد، در دسترس شما قرار میدهد. تمام عناصر آن در یک حالت گرید grid لیست میشوند. از نمونههای موجود میتوان به CGI,ASP.Net Compilation اشاره کرد. |
ModuleListPage | این کلاس برای مواقعی کاربرد دارد که شما قرار است لیستی از آیتمها را نشان دهید. در این صفحه شما یک ListView دارید که میتوانید عملیات جست و جو، گروه بندی و نحوهی نمایش لیست را روی آن اعمال کنید. |
public sealed class imageCopyrightUIPage : ModulePage { public string message; public bool featureenabled; public string color; ComboBox _colCombo = new ComboBox(); TextBox _msgTB = new TextBox(); CheckBox _enabledCB = new CheckBox(); public imageCopyrightUIPage() { this.Initialize(); } void Initialize() { Label crlabel = new Label(); crlabel.Left = 50; crlabel.Top = 100; crlabel.AutoSize = true; crlabel.Text = "Enable Image Copyright:"; _enabledCB.Text = ""; _enabledCB.Left = 200; _enabledCB.Top = 100; _enabledCB.AutoSize = true; Label msglabel = new Label(); msglabel.Left = 150; msglabel.Top = 130; msglabel.AutoSize = true; msglabel.Text = "Message:"; _msgTB.Left = 200; _msgTB.Top = 130; _msgTB.Width = 200; _msgTB.Height = 50; Label collabel = new Label(); collabel.Left = 160; collabel.Top = 160; collabel.AutoSize = true; collabel.Text = "Color:"; _colCombo.Left = 200; _colCombo.Top = 160; _colCombo.Width = 50; _colCombo.Height = 90; _colCombo.Items.Add((object)"Yellow"); _colCombo.Items.Add((object)"Blue"); _colCombo.Items.Add((object)"Red"); _colCombo.Items.Add((object)"White"); Button apply = new Button(); apply.Text = "Apply"; apply.Click += new EventHandler(this.applyClick); apply.Left = 200; apply.AutoSize = true; apply.Top = 250; Controls.Add(crlabel); Controls.Add(_enabledCB); Controls.Add(collabel); Controls.Add(_colCombo); Controls.Add(msglabel); Controls.Add(_msgTB); Controls.Add(apply); } public void ReadConfig() { try { ServerManager mgr; ConfigurationSection section; mgr = new ServerManager(); Configuration config = mgr.GetWebConfiguration( Connection.ConfigurationPath.SiteName, Connection.ConfigurationPath.ApplicationPath + Connection.ConfigurationPath.FolderPath); section = config.GetSection("system.webServer/imageCopyright"); color = (string)section.GetAttribute("color").Value; message = (string)section.GetAttribute("message").Value; featureenabled = (bool)section.GetAttribute("enabled").Value; } catch { } } void UpdateUI() { _enabledCB.Checked = featureenabled; int n = _colCombo.FindString(color, 0); _colCombo.SelectedIndex = n; _msgTB.Text = message; } protected override void OnActivated(bool initialActivation) { base.OnActivated(initialActivation); if (initialActivation) { ReadConfig(); UpdateUI(); } } private void applyClick(Object sender, EventArgs e) { try { UpdateVariables(); ServerManager mgr; ConfigurationSection section; mgr = new ServerManager(); Configuration config = mgr.GetWebConfiguration ( Connection.ConfigurationPath.SiteName, Connection.ConfigurationPath.ApplicationPath + Connection.ConfigurationPath.FolderPath ); section = config.GetSection("system.webServer/imageCopyright"); section.GetAttribute("color").Value = (object)color; section.GetAttribute("message").Value = (object)message; section.GetAttribute("enabled").Value = (object)featureenabled; mgr.CommitChanges(); } catch { } } public void UpdateVariables() { featureenabled = _enabledCB.Checked; color = _colCombo.Text; message = _msgTB.Text; } }
mgr.CommitChanges();
%vs110comntools%\vsvars32.bat
GACUTIL /l ClassLibrary1
<add name="imageCopyrightUI" type="ClassLibrary1.imageCopyrightUIProvider, ClassLibrary1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=d0b3b3b2aa8ea14b"/>
%windir%\system32\inetsrv\config\administration.config
از آنجا که این مقاله طولانی شده است، باقی موارد ویرایشی روی این UI را در مقاله بعدی بررسی خواهیم کرد.