مطالب
تشخیص نوع فایل با استفاده از محتوای فایل
بی‌شک اگر در سایت خود بخشی را برای دریافت فایل‌های کاربر قرار داده باشید یکی از دغدغه‌های شما اعمال فیلتر و محدودیت روی نوع فایل‌های آپلود شده توسط کاربران خواهد بود. ممکن است سیاست شما پذیرای فایل هایی با پسوند خاص (برای مثال فقط عکس) باشد ولی هیچ تضمینی وجود ندارد که فایلی با پسوند مورد نظر شما محتوایی مشابه با پسوند خود داشته باشد
اگر بخواهیم دقیق‌تر به این موضوع نگاه کنیم فرض می‌کنیم شما در وب سایت خود قسمتی برای آپلود عکس‌های کاربر قرار داده باشید و با مکانیزمی مانند این روش صحت نوع فایل‌های آپلود شده توسط کاربر را بررسی کنید.
اگر شخصی به قصد تخریب و هدر دادن فضای ذخیره سازی شما فایلی با محتوایی غیر از عکس (برای مثال یک فایل اجرایی) را تغییر پسوند داده و به جای عکس آپلود کند چه اتفاقی می‌افتد؟!
دو دلیل مهم برای چک کردن محتوای فایل خواهیم داشت:
1- جلوگیری از اتلاف حافظه ذخیره سازی (ممکن است شخص مهاجم هزاران فایل چند مگابایتی با محتوایی غیر از پسوند فایل بر روی سرور قرار دهد)
2- جلوگیری از اختلال در نمایش فایل‌های آپلود شده (بی شک عدم نمایش عکس در سایت چهره خوبی نخواهد داشت و ممکن است اعتبار سایت را زیر سوال ببرد)

همیشه برای چک کردن نوع فایل باید به دونکته توجه داشت: یکی آنکه پسوند فایل را حتما چک کنیم تا مطابق فیلتر و سیاست مورد انتظار ما باشد ، دوم اینکه به روشی که در ادامه توضیح خواهیم داد، با استفاده از محتوای باینری فایل، MimeType آنرا تشخیص دهیم.
برای اینکار ما از یک تابع API استفاده میکنیم که با استفاده از 255 بایت ابتدایی محتوای فایل، نوع فایل را مشخص می‌کند. این تابع API که FindMimeFromData نام دارد، در فایل urlmon.dll قرار دارد و گویا توسط مرورگر IE برای چک کردن نوع فایل، بر اساس محتوای آن مورد استفاده قرار می‌گیرد. ما نیز از این تابع در دات نت بهره گرفته و با پاس دادن آرایه‌ای از بایتها به آن نوع فایل خود را مشخص می‌کنیم.
کلاسی برای اینکار تهیه شده که در زیر مشاهده میکنید:
using System;
using System.Runtime.InteropServices;
using System.Reflection;

namespace Parsnet.Core
{

    public class MimeTypeDetector
    {
        [DllImport(@"urlmon.dll", CharSet = CharSet.Auto)]
        private extern static System.UInt32 FindMimeFromData(
            System.UInt32 pBC,
            [MarshalAs(UnmanagedType.LPStr)] System.String pwzUrl,
            [MarshalAs(UnmanagedType.LPArray)] byte[] pBuffer,
            System.UInt32 cbSize,
            [MarshalAs(UnmanagedType.LPStr)] System.String pwzMimeProposed,
            System.UInt32 dwMimeFlags,
            out System.UInt32 ppwzMimeOut,
            System.UInt32 dwReserverd
        );

        public string GetMimeType(byte[] content)
        {
            var result = "unknown/unknown";

            try
            {
                byte[] buffer = new byte[256];
                var length = (content.Length > 256) ? 256 : content.Length;
                Array.Copy(content, buffer, length);

                System.UInt32 mimetype;
                FindMimeFromData(0, null, buffer, 256, null, 0, out mimetype, 0);
                System.IntPtr mimeTypePtr = new IntPtr(mimetype);
                result = Marshal.PtrToStringUni(mimeTypePtr);
                Marshal.FreeCoTaskMem(mimeTypePtr);
            }
            catch (Exception ex)
            {
                //Log.WriteError(MethodInfo.GetCurrentMethod(), ex);
            }
            

            return result;
        }

    }
}
تنها نکته قابل توجه در این کد این‌است که در بدنه‌ی متد GetMimeType اگر طول آرایه از 256 بایت بیشتر شود، فقط از 256 بایت ابتدایی آن استفاده میکنیم. در غیر اینصورت کل بایت‌های آرایه، برای چک کردن محتوای فایل به کار گرفته میشوند. برای افزایش کارآیی، بهتر است هنگام فراخوانی این تابع، فقط 256 بایت را به آن ارسال کنید. بخصوص اگر حجم فایلی که باید چک شود زیاد باشد.

نحوه‌ی استفاده از کلاس فوق هم بسیار ساده است. برای مثال هنگام آپلود فایل در MVC می‌توانیم از آن استفاده کنیم:
        [HttpPost]
        [ValidateAntiForgeryToken]
        public ActionResult Upload(HttpPostedFileBase file)
        {
            //ابتدا با مکانیزم مورد نظر خود پسوند فایل را چک میکنیم اگر پسوند معتبری بود بعد محتوای فایل را چک میکنیم

            var data = new byte[256];
            file.InputStream.Read(data, 0, 256);
            var detector = new Parsnet.Core.MimeTypeDetector();
            var mimeType = detector.GetMimeType(data);
            if (CheckWhiteList(mimeType) == true)
            {
                file.SaveAs("yourpath");
            }
            else
            {
                ModelState.AddModelError("InvalidFileContent", "فایل بارگزاری شده مورد پذیرش نیست.");
            }
            return View();
        }
همیشه از یک مکانیزم دیگر برای اعتبارسنجی پسوند فایل استفاده کنید. برای اینکار می‌توان با تلفیق روش فوق و این روش یک کنترل اعتبارسنجی خوب ایجاد کرد که در مطالب بعدی حتما به آن خواهم پرداخت.
مطالب
اصول طراحی شیء گرا: OO Design Principles - قسمت چهارم

همانطور که قول داده بودم، به اصول GRASP می‌پردازیم.

اصول GRASP-General Responsibility Assignment Software Principles

این اصول به بررسی نحوه تقسیم وظایف بین کلاس‌ها و مشارکت اشیاء برای به انجام رساندن یک مسئولیت می‌پردازند. اینکه هر کلاس در ساختار نرم افزار چه وظیفه‌ای دارد و چگونه با کلاس‌های دیگر مشارکت میکند تا یک عملکرد به سیستم اضافه گردد. این اصول به چند بخش تقسیم می­شوند:

  • کنترلر ( Controller )
  • ایجاد کننده ( Creator )
  • انسجام قوی ( High Cohesion )
  • واسطه گری ( Indirection )
  • دانای اطلاعات ( Information Expert )
  • اتصال ضعیف ( Low Coupling )
  • چند ریختی ( Polymorphism )
  • حفاظت از تاثیر تغییرات ( Protected Variations )
  • مصنوع خالص ( Pure Fabrication )

 

Controller

این الگو بیان می‌کند که مسئولیت پاسخ به رویداد‌های (Events ) یک سناریوی محدود مانند یک مورد کاربردی ( Use Case ) باید به عهده یک کلاس غیر UI باشد. کنترلر باید کارهایی را که نیاز است در پاسخ رویداد انجام شود، به دیگران بسپرد و نتایج را طبق درخواست رویداد بازگرداند. در اصل، کنترلر دریافت کننده رویداد، راهنمای مسیر پردازش برای پاسخ به رویداد و در نهایت برگرداننده پاسخ به سمت مبداء رویداد است. در زیر مثالی را می‌بینیم که رویداد اتفاق افتاده توسط واسط گرافیکی به سمت یک handler (که متدی است با ورودیِ فرستنده و آرگمانهای مورد نیاز) در کنترلر فرستاده میشود. این روش event handling، در نمونه‌های وب فرم و ویندوز فرم دیده میشود. به صورتی خود کلاس‌های .Net وظیفه Event Raising از سمت UI با کلیک روی دکمه را انجام میدهد: 

 public class UserController
 {        
        protected void OnClickCreate(object sender, EventArgs e)
        {
           // call validation services
           // call create user services
        }
 }


در مثال بعد عملیات مربوط به User در یک WebApiController پاسخ داده میشود. در اینجا به جای استفاده از Event Raising برای کنترل کردن رویداد، از فراخوانی یک متد در کنترلر توسط درخواست HttpPost انجام میگیرد. در اینجا نیاز است که در سمت کلاینت درخواستی را ارسال کنیم:

    public class UserWebApiController
    {
        [HttpPost]
        public HttpResponseMessage Create(UserViewModel user)
        {
            // call validation services
            // call create user services
        }
    }



Creator :

  این اصل میگوید شیء ای میتواند یک شیء دیگر را بسازد ( instantiate ) که: (اگر کلاس B بخواهد کلاس A را instantiate کند)

  • کلاس B شیء از کلاس A را در خود داشته باشد؛
  • یا اطلاعات کافی برای instantiate کردن از A را داشته باشد؛
  • یا به صورت نزدیک با A در ارتباط باشد؛
  • یا بخواهد شیء A را ذخیره کند.

از آنجایی که این اصل بدیهی به نظر میرسد، با مثال نقض، درک بهتری را نسبت به آن میتوان پیدا کرد:

    // سازنده
    public class B
    {
        public static A CreateA(string name, string lastName, string job)
        {
            return new A() {
                Name =name,
                LastName = lastName,
                Job = job
            };
        }
    }
    // ایجاد شونده
    public class A
    {
        public string Name { get; set; }
        public string LastName { get; set; }
        public string Job { get; set; }
    }

    public class Context
    {
        public void Main()
        {
            var name = "Rasoul";
            var lastName = "Abbasi";
            var job = "Developer";            
            var obj = B.CreateA(name, lastName, job);
        }
    }


و اما چرا این مثال، اصل Creator را نقض میکند. در مثال میبینید که کلاس B، یک شیء از نوع A را در متد Main کلاس Context ایجاد میکند. کلاس B فقط یک متد برای تولید A دارد و در عملیات تولید A هیچ منطق خاصی را پیاده سازی نمیکند.کلاس B شیء ای را از کلاس A ، در خود ندارد، با آن ارتباط نزدیک ندارد و آنرا ذخیره نمیکند. با اینکه کلاس B اطلاعات کافی را برای تولید A از ورودی میگیرد، ولی این کلاس Context است که اطلاعات کافی را ارسال مینماید. اگر در کلاس B منطقی اضافه بر instance گیریِ ساده وجود داشت (مانند بررسی صحت و اعتبار سنجی)، میتوانستیم بگوییم کلاس B از یک مجموعه عملیات instance گیری با خبر است که کلاس Context  نباید از آن خبر داشته باشد. لذا اکنون هیچ دلیلی وجود ندارد که وظیفه تولید A را در Context انجام ندهیم و این مسئولیت را به کلاس B منتقل کنیم. این مورد ممکن است در ذهن شما با الگوی Factory تناقض داشته باشد. ولی نکته اصلی در الگو Factory انجام عملیات instance گیری با توجه به منطق برنامه است؛ یعنی وظیفه‌ای که کلاس Context نباید از آن خبر داشته باشد را به کلاس Factory منتقل میکنیم. در غیر اینصورت ایجاد کلاس Factory بی معنا خواهد بود (مگر به عنوان افزایش انعطاف پذیری معماری که بتوان به راحتی نوع پیاده سازی یک واسط را تغییر داد).


High Cohesion :

این اصل اشاره به یکی از اصول اساسی طراحی نرم افزار دارد. انسجام واحد‌های نرم افزاری باعث افزایش خوانایی، سهولت اشکال زدایی، قابلیت نگهداری و کاهش تاثیر زنجیره‌ای تغییرات میشود. طبق این اصل، مسئولیتهای هر واحد باید مرتبط باشد. لذا اجزایی کوچک با مسئولیتهای منسجم و متمرکز بهتر از اجزایی بزرگ با مسئولیت‌های پراکنده است. اگر واحد‌های سازنده نرم افزار انسجام ضعیفی داشته باشند، درک همکاری‌ها، استفاده مجدد آنها، نگه داری نرم افزار و پاسخ به تغییرات سخت‌تر خواهد شد.

در مثال زیر نقض این اصل را مشاهده میکنیم:

    class Controller
    {
        public void CreateProduct(string name, int categoryId) { }
        public void EditProduct(int id, string name) { }
        public void DeleteProduct(int id) { }
        public void CreateCategory(string name) { }
        public void EditCategory(int id, string name) { }
        public void DeleteCategory(int id) { }
    }  

همانطور که میبینید، کلاس کنترلر ما، مسئولیت مدیریت Product و Category را بر عهده دارد. بزرگ شدن این کلاس، باعث سخت‌تر شدن خواندن کد و رفع اشکال میگردد. با جداسازی کنترلر مربوط به Product از Category میتوان انسجام را بالا برد.


Indirection :

 این اصل بیان میکند که با تعریف یک واسط بین دو مولفه نرم افزاری میتوان میزان اتصال نرم افزار را کاهش داد. بدین ترتیب وظیفه هماهنگی ارتباط دو مؤلفه، به عهده این واسط خواهد بود و نیازی نیست داده‌های ورودی و خروجی دو مؤلفه، هماهنگ باشند. در اینجا واسط، از وابستگی بین دو مؤلفه با پنهان کردن ضوابط هر مؤلفه از دیگری و ایجاد وابستگی ضعیف خود با دو مؤلفه، باعث کاهش اتصال کلی طراحی میگردد.

الگوهای Adapter و Delegate و همچنین نقش کنترلر در الگوی معماری MVC از این اصل پیروی میکنند. 

    class SenderA
    {
        public Mediator mediator { get; }
        public SenderA() { mediator = new Mediator(); }
        public void Send(string message, string reciever) { mediator.Send(message, reciever); }
    }
    class SenderB
    {
        public Mediator mediator { get; }
        public SenderB() { mediator = new Mediator(); }
        public void Send(string message) { }
    }

    public class RecieverA
    {
        public void DoAction(string message)
        {
            // انجام عملیات بر اساس پیغام دریافت شده
            switch (message)
            {
                case "create":
                    break;
                case "delete":
                    break;
                default:
                    break;
            }
        }
    }
    public class RecieverB
    {
        public void DoAction(string message)
        {
            // انجام عملیات بر اساس پیغام دریافت شده
            switch (message)
            {
                case "edit":
                    break;
                case "rollback":
                    break;
                default:
                    break;
            }
        }
    }
    class Mediator
    {
        internal void Send(string message, string reciever)
        {
            switch (reciever)
            {
                case "A":
                    var recieverObjA = new RecieverA();
                    recieverObjA.DoAction(message);
                    break;
                case "B":
                    var recieverObjB = new RecieverB();
                    recieverObjB.DoAction(message);
                    break;

                default:
                    break;
            }
        }
    }
    class IndirectionContext
    {
        public void Main()
        {
            var senderA = new SenderA();
            senderA.Send("rollback", "B");
            var senderB = new SenderA();
            senderB.Send("create", "A");

        }
    }

در این مثال کلاس Mediator به عنوان واسط ارتباطی بین کلاس‌های Sender و Receiver قرار گرفته و نقش تحویل پیغام را دارد.

در مقاله بعدی، به بررسی سایر اصول GRASP خواهم پرداخت.

مطالب
افزودن فایلهای تولیده شده پویا به صورت Bundle به سیستم Optimization
گاهی از اوقات نیاز است تا از یک محتوای پویا، برای تولید فایلهای CSS و اسکریپت‌های خود استفاده کنید. دلایل زیادی برای اینکار وجود دارند؛ همانند اسکریپت تولید شده در Signalr که بر اساس کلاس hub شما و متدهای پیاده سازی شده‌ی در آن تولید می‌شود. همچنین روش‌های زیادی برای تولید این محتوای پویا وجود دارد که یک نمونه‌ی آن در اینجا ذکر شده است.
قرار دادن این محتوای تولید شده در سیستم Bundling MVC به شکل مستقیم امکان پذیر نیست؛ زیرا این سیستم با فایل‌های استاتیک سر و کار دارد و افزودن یک url به آن مجاز نمی‌باشد. حال اگر در پروژه‌ی خود محتوای پویایی را تولید کرده و می‌خواهید از مزایای فشرده سازی سیستم Bundling بهره‌مند شوید، باید مراحل زیر را انجام دهید:
ابتدا متدی را برای دریافت محتوای تولید شده بنویسید. برای مثال برای دریافت محتوای تولیده شده‌ی فایل hubs.js می‌توانید از متد زیر استفاده کنید:
public static string GetSignalRContent()
{
    var resolver = new DefaultHubManager(new DefaultDependencyResolver());
    var proxy = new DefaultJavaScriptProxyGenerator(resolver, new NullJavaScriptMinifier());
    return proxy.GenerateProxy("/signalr");
}
سپس باید سیستم مسریابی پیش فرض سیستم Bundling را با سیستم مسیریابی سفارشی خود جایگزین کنید. کاری که باید انجام دهید اینست که در سیستم مسیریابی سفارشی خود چک کنید اگر مسیر درخواستی به مسیر مورد نظر شما اشاره دارد، مقدار true را برگشت دهید. در واقع در سیستم مسیریابی پیش فرض اگر فایلی بطور فیزیکی وجود نداشته باشد، مقدار برگشتی false خواهد بود.
همچنین در این سیستم مسیریابی سفارشی شما باید محتوای تولید شده را هم در اختیار داشته باشید. برای نمونه به کد زیر توجه کنید که ما از کلاس VirtualPathProvider، یک کلاس مشتق کرده و سیستم مسیریابی دلخواه خود را ایجاد می‌کنیم:

public class CustomVirtualPathProvider : VirtualPathProvider
{
    public CustomActionVirtualPathProvider(VirtualPathProvider virtualPathProvider)
    {
        // Wrap an existing virtual path provider
        VirtualPathProvider = virtualPathProvider;
    }

    protected VirtualPathProvider VirtualPathProvider { get; set; }

    public override string CombineVirtualPaths(string basePath, string relativePath)
    {
        return VirtualPathProvider.CombineVirtualPaths(basePath, relativePath);
    }

    public override bool DirectoryExists(string virtualDir)
    {
        return VirtualPathProvider.DirectoryExists(virtualDir);
    }

    public override bool FileExists(string virtualPath)
    {
        if (virtualPath == "~/signalr/hubs")
        {
            return true;
        }

        return VirtualPathProvider.FileExists(virtualPath);
    }

    public override CacheDependency GetCacheDependency(string virtualPath, IEnumerable virtualPathDependencies, DateTime utcStart)
    {
        // BaseClass can't create a CacheDependency for your content, remove it
        // You could also add your own CacheDependency and aggregate it with the base dependency
        List<string> virtualPathDependenciesCopy = virtualPathDependencies.Cast<string>().ToList();
        virtualPathDependenciesCopy.Remove("~/signalr/hubs");

        return VirtualPathProvider.GetCacheDependency(virtualPath, virtualPathDependenciesCopy, utcStart);
    }

    public override string GetCacheKey(string virtualPath)
    {
        return VirtualPathProvider.GetCacheKey(virtualPath);
    }

    public override VirtualDirectory GetDirectory(string virtualDir)
    {
        return VirtualPathProvider.GetDirectory(virtualDir);
    }

    public override VirtualFile GetFile(string virtualPath)
    {
        if (virtualPath == "~/signalr/hubs")
        {
            return new CustomVirtualFile(virtualPath,
                new MemoryStream(Encoding.Default.GetBytes(GetSignalRContent())));
        }

        return VirtualPathProvider.GetFile(virtualPath);
    }

    public override string GetFileHash(string virtualPath, IEnumerable virtualPathDependencies)
    {
        return VirtualPathProvider.GetFileHash(virtualPath, virtualPathDependencies);
    }

    public override object InitializeLifetimeService()
    {
        return VirtualPathProvider.InitializeLifetimeService();
    }
}

public class CustomVirtualFile : VirtualFile
{
    public CustomVirtualFile (string virtualPath, Stream stream)
        : base(virtualPath)
    {
        Stream = stream;
    }

    public Stream Stream { get; private set; }

    public override Stream Open()
    {
         return Stream;
    }
}
ابتدا در بازنویسی متد FileExists باید چک کنیم اگر مسیر درخواستی به مسیر مورد نظر ما اشاره داشت، منطق خود را پیاده کنیم:
    public override bool FileExists(string virtualPath)
    {
        if (virtualPath == "~/signalr/hubs")
        {
            return true;
        }

        return VirtualPathProvider.FileExists(virtualPath);
    }
  در این متد اگر مسیر درخواستی مطابق مسیر مورد نظر ما بود باید true برگشت دهیم، در غیر اینصورت کار را به کلاس پایه می‌سپاریم.
همچنین باید توجه داشت که کلاس پایه، قادر به تولید CacheDependency  برای محتوای تولیدی شده ما نیست. بنابراین باید متد GetCacheDependency کلاس پایه‌ی ما بازنویسی و منطق مورد نظر را برای آن پیاده سازی کنیم. در این متد ابتدا مسیر مورد نظر خود را از لیست مسیرهایی که باید از CacheDependency استفاده کنند، حذف و سپس سناریوی خود را پیاده سازی می‌کنیم. در این مثال من از پیاده سازی آن خودداری کرده و فقط مسیر را از لیست مسیرها حذف کرده‌ام:
    public override CacheDependency GetCacheDependency(string virtualPath, IEnumerable virtualPathDependencies, DateTime utcStart)
    {
        List<string> virtualPathDependenciesCopy = virtualPathDependencies.Cast<string>().ToList();
        virtualPathDependenciesCopy.Remove("~/signalr/hubs");

        return VirtualPathProvider.GetCacheDependency(virtualPath, virtualPathDependenciesCopy, utcStart);
    }
سپس نوبت دریافت محتوای تولید شده‌ی پویا است که باید با بازنویسی متد GetFile منطق خود را اعمال کنیم:
    public override VirtualFile GetFile(string virtualPath)
    {
        if (virtualPath == "~/signalr/hubs")
        {
            return new CustomVirtualFile(virtualPath,
                new MemoryStream(Encoding.Default.GetBytes(GetSignalRContent())));
        }

        return VirtualPathProvider.GetFile(virtualPath);
    }
در این متد ابتدا چک می‌کنیم اگر مسیر درخواست شده به مسیر مورد نظر ما اشاره داشت، با استفاده از متد GetSignalRContent محتوای تولید شده‌ی پویا را دریافت و از طریق کلاس CustomVirtualFile که از کلاس VirtualFile مشتق کرده ایم، آن‌را باز می‌گردانیم. کار این کلاس هم فراهم کردن بستری برای اشیایی است که یک فایل فیزیکی را در قالب سیستم فایل مجازی بکار می‌گیرند. تنها نکته‌ی قابل توجه در این کلاس، بازنویسی متد Open کلاس پایه، برای بازگرداندن یک Stream فقط خواندنی از محتوای منبع مورد نظر ماست. از آنجا که منبع ما در اینجا به طور پویا تولید می‌شود، همانطور که دیدید ما در متد GetFile از یک MemoryStream استفاده کردیم؛ در صورتیکه اگر با یک فایل فیزیکی سر و کار داشتیم، ممکن بود از یک FileStream استفاده کنیم.
در نهایت باید کلاس سفارشی شده را با سیستم مسیریابی پیش فرض سیستم Bundling جایگزین کنیم:
public static void RegisterBundles(BundleCollection bundles)
{
    BundleTable.VirtualPathProvider =
        new CustomVirtualPathProvider(BundleTable.VirtualPathProvider);

    Bundle include = new Bundle("~/bundle")
        .Include("~/Content/static.js")
        .Include("~/signalr/hubs");

    bundles.Add(include);
}
مطالب
آشنایی با CLR: قسمت سیزدهم
ترکیب ماژول‌ها به قالب یک اسمبلی
فایل Program.exe یک فایل PE با جداول متادیتا است که همچنین یک اسمبلی هم می‌باشد. یک اسمبلی مجموعه‌ای از یک یا چند فایل، شامل تعاریف نوع و منابع (ریسورس) می‌باشد و یکی از فایل‌های اسمبلی، برای نگهداری manifest انتخاب می‌شود. این جدول مجموعه‌ای است از جداول متادیتا که به طور کلی شامل نام فایل‌هایی است که قسمتی از اسمبلی را تشکیل می‌دهند. برای همین گفتیم که CLR با اسمبلی‌ها کار می‌کند. ابتدا جداول manifest را خوانده تا نام فایل‌ها را شناسایی کرده تا از آن‌ها را به حافظه بارگزاری کند. اسمبلی‌ها چند خصوصیت دارند که باید آن‌ها را بدانید:
- نوع‌های با قابلیت استفاده‌ی مجدد را تعریف می‌کنند.
- داری شماره‌ی نسخه version هستند.
- می‌توانند شامل اطلاعات امنیتی باشند.
این خواصی است که یک اسمبلی به همراه دارد و فایل‌هایی که شامل می‌شود، نمی‌توانند چنین خاصیتی را داشته باشند؛ مگر اینکه آن فایل‌ها در متای خود جدول manifest داشته باشند.
شما برای بسته بندی، شماره نسخه، مباحث امنیتی و استفاده از نوع‌ها، باید آن‌ها را داخل ماژولی قرار دهید که جزئی از اسمبلی است. یک فایل اسمبلی  همانند program.exe به عنوان یک فایل واحد شناخته می‌شود. با اینکه یک اسمبلی از چند فایل تشکیل می‌شود، فایل‌های PE به همراه جداول متادیتای آن و تعدادی ریسورس مثل فایل‌های gif و jpg است که به شما کمک می‌کند به همه‌ی آن‌ها به عنوان یک فایل منطقی EXE یا dll نگاه کنید.
یکی از دلایلی که در قسمت سوم گفتیم این بود که می‌توانیم فایل‌هایی را که به ندرت استفاده می‌شوند، از طریق اینترنت مورد استفاده قرار دهیم. در حالتیکه نیاز به دسترسی به اسمبلی‌های روی اینترنت دارید، CLR ابتدا کش را بررسی می‌کند تا آیا فایل حاضر است یا خیر؟ اگر پاسخ مثبت بود، در حافظه قرار می‌گیرد. ولی اگر پاسخ منفی بود، CLR به آدرسی که اسمبلی در آن قرار دارد، رجوع کرده و آن را دانلود می‌کند و اگر فایل مد نظر یافت نشد، استثنای FileNotFound  را در حین اجرا صادر خواهد کرد.
آقای جفری ریچر در کتاب خود سه تا از دلایل استفاده‌ی از اسمبلی‌های چند فایله را بر می‌شمارد:
  • جداسازی نوع‌ها در فایل‌های جداگانه که باعث کاهش حجم فایل از طریق اینترنت و بارگزاری حجم کمتر در حافظه  می‌شوند.
  • استفاده از فایل‌های منبع و داده‌ها در اسمبلی: فرض کنید نیاز به محاسبه‌ی اطلاعات بیمه دارید و برای این کار به اطلاعات داخل یک جدول آماری احتیاج دارید. این جدول آماری می‌تواند یک فایل متنی ساده یا یک صفحه‌ی گسترده مثل اکسل یا در قالب ورد و هر چیز دیگری باشد که به جای embed شدن این جداول در سورس کد برنامه، آن‌ها را با استفاده از ابزاری مثل Assembly Linker -AL.exe می‌توانید جزئی از اسمبلی کنید و فقط نیاز است که بدانید چگونه آن فایل را پارس یا تبدیل کنید.
  • استفاده از انواع ایجاد شده در زبان‌های مختلف. در این حالت شما مقداری از کد را با استفاده از #C نوشته اید و مقداری از آن را با Visual Basic می‌نویسید و هر کدام در نهایت به یک ماژول جداگانه کامپایل خواهند شد. ولی تبدیل آن به یک واحد منطقی مثل اسمبلی ممکن است و از این نظر می‌توانید روی ماژول‌های یک دسته کنترل داشته باشید.
اگر چندین نوع دارید که شامل نسخه بندی و تنظیمات امنیتی مشترک هستند، بهتر است در یک اسمبلی قرار گیرند تا اینکه در اسمبلی‌های جداگانه‌ایی قرار بگیرند. دلیل این کار هم ایجاد performance یا کارآیی بهتر است. بارگذاری یک  اسمبلی در حافظه زمانی را برای یافتن آن از CLR و ویندوز می‌گیرد و سپس وارد بارگیری آن‌ها در حافظه و آماده سازی می‌شود. پس هر چه تعداد اسمبلی‌ها کمتر باشد، کارآیی بهتری خواهید داشت، چون کمتر شدن بارگیری برابر با کاهش  صفحات کاری است و پراکندگی fragmentation فضای آدرس دهی آن فرایند را کاهش خواهد داد. نهایتا Ngen می‌تواند در بهینه سازی فایل‌های بزرگتر موفق باشد.
برای ساخت اسمبلی، باید یکی از فایل‌های PE را برای نگهداری جدول manifest انتخاب کنید؛ یا خودتان یک فایل PE جدا درست کنید که تنها شامل جدول مانیفست شود. جدول زیر قالبی از جداول مانیفست هست که بابت ماژول‌های اضافه شده به یک اسمبلی ایجاد می‌شوند.
 AssemblyDef  شامل مدخل ورودی (آدرس شروع حافظه) برای اسمبلی‌هایی است که ماژول عضو آن است. این مدخل شامل نام اسمبلی (بدون مسیر و پسوند)، شماره نسخه یا ورژن، culture، فلگ، الگوریتم هش و کلید عمومی ناشر، که می‌تواند نال باشد، هست.
 FileDef  شامل یک مدخل ورودی برای هر فایل PE و فایل‌های ریسورسی است که قسمتی از اسمبلی را تشکیل می‌دهند. این مدخل ورودی شامل نام و پسوند فایل (بدون ذکر مسیر)، فلگ و مقدار هش می‌شود. اگر تنها یک اسمبلی وجود داشته باشد، این جدول هیچ مدخلی نخواهد داشت.
 ManifestResourceDef  شامل یک مدخل ورودی برای هر فایل ریسورس است. این مدخل شامل نام فایل ریسورس، فلگ و یک اندیس به جدول FileDef است که در آن اشاره‌ای به آن فایل ریسورس یا استریم است.
 ExportedTypesDef  شامل یک مدخل ورودی برای هر نوع عمومی است که از همه ماژول‌های PE استخراج شده است. هر مدخل شامل نام نوع و اندیسی به جدول FileDef و یک اندیس دیگر به جدول TypeDef است. نکته: برای ذخیره سازی حافظه و کم حجم شدن فایل‌ها، نوع‌های استخراج شده از فایلی که شامل مانیفست است دیگر در جدول جاری نام نوع‌ها ذکر نمی‌گردد؛ چرا که این اطلاعات در جدول TypeDef اسمبلی جاری موجود است.
  نکته: اسمبلی که شامل مانیفست است، شامل یک جدول AssemblyRef نیز می‌گردد که به تمام اسمبلی‌های ارجاع شده در آن اسمبلی اشاره می‌کند. با استفاده از ابزارهای موجود می‌توان اسمبلی مدنظر را باز کرده و به این ترتیب لیستی از اسمبلی‌های ارجاع شده را خواهید دید و بدین صورت این اسمبلی یک اسمبلی خود تعریف می‌شود.

کامپایلر سی شارپ با استفاده از سوئیچ‌های زیر یک اسمبلی را تولید می‌کند:
/t[arget]:exe, /t[arget]:winexe, /t[arget]: appcontainerexe, /t[arget]: library, or /t[arget]:winmdobj

سوئئیچ‌های بالا باعث می‌شود که یک فایل PE با جدول مانیفست تولید گردد. در صورتیکه سوئیچ زیر را به کار ببرید، فایل تولید شده شامل جدول مانیفست نمی‌شود.
/t[arget]:module
این فایل PE تولید شده در قالب یک dll است که باید قبل از اینکه CLR به نوع‌های داخل آن دسترسی پیدا کند، به یک اسمبلی اضافه گردد. موقعی‌که شما از سوئیچ بالا استفاده می‌کنید، کامپایلر سی شارپ به طور پیش فرض از پسوند netmodule برای فایل خروجی استفاده می‌کند.

نکته‌ی پایانی: محیط توسعه ویژوال استادیو به طور پیش فرض از اسمبلی‌های چند فایل پشتیبانی نمی‌کند، اگر میخواهید که اسمبلی‌های چند فایله تولید کنید باید در سوئیچ‌های مورد استفاده آن تجدید نظری داشته باشید.
در مقاله آینده این روش‌ها را بررسی خواهیم کرد...


مطالب
توضیح مثالی از SIMD برای نشان دادن عملکرد آن - SIMD Performance
پیشنیازها

SIMD یا ترجمه آن به فارسی به معنی «تک دستورالعمل و چند داده»، قابلیت آن‌را دارد تا بر روی مقادیر عددی به صورت موازی و با استفاده از پردازنده کار کند. اگر بتوانیم ساختار پروژه‌های خود را به طوری ایجاد کنیم تا بتوانیم از SIMD در پردازش‌های خود استفاده کنیم، سرعت انجام فعالیت‌ها، بسیار زیاد افزایش پیدا خواهند کرد؛ به خصوص این امر در حجم‌های پردازشی زیاد محسوس خواهد بود. البته مدیریت استفاده از منابع و پردازنده نباید فراموش شوند.
اطلاعات لازم از SIMD و نحوه عملکرد آن را می‌توانید در مقاله پیشنیاز بیابید. در این مقاله قصد داریم تا یک مثال ساده از کارآیی SIMD را مطرح کنیم. مثال زیر از مثال SimdSpike الگو برداری شده است و تغییراتی نیز جهت تکمیل شدن آن انجام شده است.
در این مثال می‌خواهیم نمونه کدهایی را با روش‌های معمول اجرا کنیم و زمان اجرای آن را با زمان اجرای همان مثال‌ها با روش SIMD، مقایسه کنیم. 
با استفاده از ویژوال استودیو 2015 آپدیت 3 یک پروژه کنسول با چارچوب دات نت 4.6.1 ایجاد کرده‌ایم. البته می‌توانید ازدیگر نسخه‌ها هم استفاده کنید به شرط آنکه دات نت 4.6x را نصب کرده باشید.

در صورتی که ویژوال استودیوی شما دارای این ورژن و آپدیت نبود، می‌توانید چارچوب دات نت 4.6.1 را جداگانه در سیستم خود نصب نمایید. توجه داشته باشید که برای استفاده از چارچوب دات نت در ویژوال استودیو باید نسخه‌های DevPack یا DeveloperPack را نصب نمایید (دریافت  دات نت 4.6.1 نسخه مخصوص استفاده در ویژال استودیو). 

در پروژه ایجاد شده فایلی به نام Program.cs و در آن کلاس Program وجود دارد. در این کلاس تابع شروع کننده برنامه یعنی Main وجود دارد و برنامه از این تابع شروع خواهد شد.

نمایی از فایل‌های پروژه

در تابع شروع کننده برنامه ابتدا وضعیت پشتیبانی از SIMD را چک می‌کنیم. این کار را همانطور که قبلا در مقاله پیشنیاز توضیح داده شده است با استفاده از خاصیت Vector.IsHardwareAccelerated بررسی می‌کنیم. اگر مقدار آن برابر با False باشد به معنای عدم پشتیبانی می‌باشد و با بررسی این موضوع در اول برنامه، در صورت عدم پشتیبانی از SIMD به اجرای ادامه‌ی برنامه خاتمه می‌دهیم.

پس از بررسی وضعیت پشتیبانی از SIMD ، تابعی را که در فایل Utilities.cs نوشته شده است، فراخوانی می‌کنیم. این تابع به بررسی وضعیت تعداد رجیسترهای SIMD و وضعیت انواع نوع‌های داده‌ای در SIMD می‌پردازد. اگر هر نوع داده‌ای از SIMD پشتیبانی کند (که بستگی به نوع پردازنده شما دارد) اندازه هر نوع داده‌ای را در SIMD چاپ می‌کند و در صورت عدم پشتیبانی هر نوع داده‌ای از SIMD مقدار «عدم پشتیبانی SIMD از آن نوع داده‌ای» چاپ خواهد شد. 

  تا به اینجای برنامه کد‌های تابع شروع کننده به صورت زیر خواهد بود. 
using System.Numerics;
using static System.Console;

namespace TestSIMD
{
    class Program
    {
        private const int ArraySize = 7680 * 4320;
        static void Main(string[] args)
        {
            // بررسی وضعیت پشتیبانی از SIMD
            if (!Vector.IsHardwareAccelerated)
            {
                WriteLine("Hardware acceleration not supported.");
                WriteLine();
                return; // عدم پشتیبانی و خاتمه برنامه
            }
            WriteLine("Hardware acceleration is supported"); // اعلام پشتیبانی از SIMD
            WriteLine();

            // بررسی وضعیت نوع‌های داده ای در مشخصات سخت افزاری SIMD
            Utilities.PrintHardwareSpecificSimdEffectiveness();

            //به منظور عدم خروج از برنامه و دیدن نتایج آزمایش
            WriteLine("Press any key to exit");
            ReadKey();
        }
    }
}
اجرای برنامه هم به صورت زیر به نمایش در خواهد آمد. 

در فایل Utilities.cs، توابع دیگری هم وجود دارند که کارآیی هر یک به صورت توضیح در بالای هر تابع نوشته شده است. این توابع برای تولید یک نوع داده‌ای تصادفی و ایجاد آرایه‌ای از نوع داده‌ای به صورت تصادفی به کار برده می‌شوند. می توانید در سورس برنامه این توضیحات را مشاهده کنید.
تا به اینجا تنها به بررسی پشتیبانی سخت افزاری از SIMD پرداختیم و همچنین توانستیم نوع‌های داده‌ای را که SIMD در سخت افزار ما پشتیبانی می‌کند، شناسایی کنیم و اندازه رجیستر‌های آنها را بیابیم.
حال به بررسی عملکرد توابع SIMD می‌پردازیم و با نوشتن چند تابع، زمان اجرای محاسباتی آنها را با نوشتن همان توابع در حالت معمولی و ساده مقایسه می‌کنیم.
 برای انجام مقایسه، زمان اجرای یک عملیات را در حالت معمول، با زمان اجرای همان عملیات در حالت SIMD بررسی می‌کنیم. هر عملیات را 3 مرتبه پشت سر هم اجرا می‌کنیم و زمان آنها را ثبت می‌کنیم تا تفاوت زمان اجرا را با تکرار عملیات نیز مشاهده کنیم. توابعی که آزمایشات را انجام می‌دهند و زمان اجرا را ثبت و نمایش می‌دهند، در فایل PerformanceTests.cs و در کلاس PerformanceTests قرار دارند و از توابع سه کلاس دیگر که عملیات در آن نوشته شده‌اند، استفاده می‌کنند.
  • فایل IntSimdProcessor.cs
    • در این فایل کلاسی به نام IntSimdProcessor قرار دارد که شامل 6 تابع می‌باشد و این تابع‌ها با نوع داده‌ای صحیح یا همان Integer کار می‌کنند. نام کلاس هم به همین خاطر نام گذاری شده است. 
    • این 6 تابع در کل 3 عملیات را شامل عملیات‌های زیر انجام می‌دهند. یکبار در حالت معمولی و یکبار با استفاده از توابع SIMD این کار را انجام می‌دهند:
      • پیدا کردن بزرگترین و کوچکترین عدد در آرایه
      • جمع عناصر دو آرایه با هم با استفاده از یک آرایه کمکی که نتیجه در آرایه کمکی ریخته می‌شود
      • جمع عناصر دو آرایه بدون استفاده از آرایه کمکی که مجموع در آرایه اول ریخته می‌شود
    • در بالای هر تابع در این فایل توضیحات لازم درباره‌ی فعالیت آن تابع ذکر شده است.
 
  • فایل FloatSimdProcessor.cs
    • در این فایل کلاسی با نام FloatSimdProcessor قرار دارد که همانطور که از نام کلاس پیداست، توابعی برای کار بر روی اعداد از نوع داده‌ای float در آن نوشته شده‌اند.
    • در این کلاس هم 6 تابع برای انجام 3 عملیات زیر نوشته شده است که به ازای هر عملیات دو تابع یکی در حالت معمولی و یکی در حالت SIMD نوشته شده است.
      • جمع دو آرایه با استفاده از یک آرایه کمکی - مجموع در آرایه کمکی ریخته می‌شود
      • جمع دو آرایه اول ورودی - مجموع در آرایه سوم ریخته می‌شود
      • جمع دو آرایه بدون استفاده از آرایه کمکی - مجموع در آرایه اول ریخته می‌شود
    • در آزمایشات نوشته شده در کلاس PerformanceTests  تنها از عملیات آخری استفاده شده است و از دو عملیات اول استفاده نشده است که در صورت تمایل می‌توانید از دیگر عملیات‌ها نیز استفاده کنید.
    • در بالای هر تابع در این فایل توضیحات لازم درباره‌ی فعالیت آن تابع نیز ذکر شده است.
 
  • فایل UShortSimdProcessor.cs
    • در این فایل کلاسی با نام UShortSimdProcessor قرار دارد و همانطور که از نام کلاس پیداست، توابعی برای کار بر روی اعداد از نوع داده‌ای ushort یا همان اعداد صحیح کوچک بدون علامت نوشته شده‌اند.
    • در این کلاس 12 تابع برای انجام 6 عملیات زیر نوشته شده‌است که به ازای هر عملیات، دو تابع یکی در حالت معمولی و یکی در حالت SIMD نوشته شده است.
      • جمع دو آرایه اول ورودی که مجموع در آرایه سوم ریخته می‌شود
      • جمع دو آرایه بدون استفاده از آرایه کمکی که مجموع در آرایه اول ریخته می‌شود
      • بدست آوردن کمترین و بیشترین مقدار در یک آرایه اعداد صحیح کوچک بدون علامت
      • جمع عناصر آرایه ورودی و ذخیره مجموع آنها در یک متغیر کمکی
      • جمع عناصر آرایه ورودی و ذخیره مجموع آنها در یک متغیر کمکی بدون بررسی سرریز (Overflow)
      • محاسبه میانگین و بدست آوردن کمترین و بیشترین مقدار در یک آرایه اعداد صحیح کوچک بدون علامت
    • در بالای هر تابع در این فایل توضیحات لازم درباره‌ی فعالیت آن تابع ذکر شده است.
 
حال در کلاس PerformanceTests برای انجام آزمایشات و مقایسه زمان اجرا، 10 تابع وجود دارند که 10 عملیات مختلف را بر روی 3 نوع داده‌ای، اجرا می‌کنند. 3 عملیات از کلاس IntSimdProcessor و یک عملیات از کلاس FloatSimdProcessor و 6 عملیات از کلاس UShortSimdProcessor را مورد آزمایش قرار داده‌ایم که در مجموع شامل 10 آزمایش در 10 تابع مختلف شده است.
public static void TestIntArrayAdditionFunctions(int testSetSize) {
    WriteLine();
    Write("Testing int array addition, generating test data...");
    var intsOne = GetRandomIntArray(testSetSize); //تولید آرایه عددی به صورت تصادفی
    var intsTwo = GetRandomIntArray(testSetSize);
    WriteLine($" done, testing...");// پایان تولید آرایه‌ها و شروع پردازش
    var naiveTimesMs = new List<long>(); // تعریف لیستی برای ریختن زمان پاسخ دهی در حالت ساده و معمولی
    var hwTimesMs = new List<long>(); // تعریف لیستی برای ریختن زمان پاسخ دهی در حالت SIMD و سخت افزاری 
    for (var i = 0; i < 3; i++) { // ایجاد حلقه برای تکرار محاسبات برای اندازه گیری زمان در حالت تکراری
        stopwatch.Restart();//شروع ثبت زمان
        var result = IntSimdProcessor.NaiveSumFunc(intsOne, intsTwo);//اجرای تابع جمع دو آرایه
        var naiveTimeMs = stopwatch.ElapsedMilliseconds;//ثبت زمان
        naiveTimesMs.Add(naiveTimeMs);//افزودن زمان ثبت شده به لیست زمان‌های ساده و معمول
        WriteLine($"Naive analysis took:                {naiveTimeMs}ms (last value = {result.Last()}).");

        stopwatch.Restart();//شروع ثبت زمان
        result = IntSimdProcessor.HWAcceleratedSumFunc(intsOne, intsTwo);//اجرای تابع جمع دو آرایه در حالت سخت افزاری
        var hwTimeMs = stopwatch.ElapsedMilliseconds;//ثبت زمان
        hwTimesMs.Add(hwTimeMs);//افزودن زمان به لیست زمان‌های سخت افزاری
        WriteLine($"Hareware accelerated analysis took: {hwTimeMs}ms (last value = {result.Last()}).");
    }//پایان حلقه و چاپ نتایج
    WriteLine("Int array addition:");
    WriteLine($"Naive method average time:          {naiveTimesMs.Average():.##}");
    WriteLine($"HW accelerated method average time: {hwTimesMs.Average():.##}");
    WriteLine($"Hardware speedup:                   {naiveTimesMs.Average() / hwTimesMs.Average():P}%");
}
در بالا تکه کدی مربوط به تابع آزمایش اول از کلاس PerformanceTests قرار دارد و وظیفه دارد عملیات جمع دو آرایه را با استفاده از یک آرایه کمکی اعداد صحیح، هم در حالت معمولی و هم در حالت SIMD انجام دهد و زمان اجرای آنها را ثبت و نمایش دهد تا بتوانیم این زمان اجرا‌ها را با هم مقایسه کنیم.
ساختار و روند اجرای کلیه آزمایش‌ها و توابع در کلاس PerformanceTests با یکدیگر یکسان است و از یک stopwatch یا همان کرنومتر برای محاسبه زمان اجرا استفاده شده است.
هر کدام از این توابع یک عملیات را مورد بررسی قرار می‌دهند و هر عملیات را 3 مرتبه اجرا می‌کنند تا زمان تکرار اجرا نیز مورد مقایسه قرار گیرد.

نام تابع ذکر شده نشان دهنده آزمایش بر روی آرایه اعداد صحیح یا همان Integer می‌باشد که شامل یک پارامتر ورودی از نوع عدد صحیح می‌باشد. این پارامتر ورودی نشان دهنده اندازه هر آرایه‌ای می‌باشد که قرار است تولید شود.  

TestIntArrayAdditionFunctions(int testSetSize)

در قدم اول این تابع، باید آرایه‌ها را تولید کنیم که کد آن به صورت زیر است.

Write("Testing int array addition, generating test data...");
var intsOne = GetRandomIntArray(testSetSize);
var intsTwo = GetRandomIntArray(testSetSize);
WriteLine($" done, testing...");

ابتدا در خروجی چاپ می‌کنیم که در حال ایجاد داده‌های مربوط به آزمایش هستیم و سپس با استفاده از تابع GetRandomIntArray آرایه‌ای را ایجاد می‌کنیم و در متغیر‌های مربوطه می‌ریزیم. این تابع دارای یک پارامتر ورودی از نوع عدد صحیح است که آرایه‌ای را به طول پارامتر ورودی تولید می‌کند. این تابع در فایل Utilities.cs قرار دارد.

در پایان تولید آرایه‌ها، اتمام تولید و ایجاد آرایه‌ها را با چاپ در خروجی اعلام میکنیم.

سپس با معرفی دو لیست زیر می‌توانیم زمان‌های اجرا را در آنها بریزیم و در پایان، تابع میانگین این زمان‌ها را محاسبه و چاپ کنیم. لیست اول برای نگهداری زمان‌های اجرای عملیات در حالت معمولی و لیست دوم برای نگهداری زمانهای اجرای عملیات در حالت SIMD می‌باشد.

var naiveTimesMs = new List<long>();
var hwTimesMs = new List<long>();

سپس با ایجاد حلقه ای از 0 تا 3 که در کل 3 مرتبه اجرا می‌شود عملیات را تکرار و زمان آن را ثبت می‌کنیم. 

for (var i = 0; i < 3; i++)

درون حلقه یک عملیات را در دوحالت معمولی یا ساده و SIMD اجرا می‌کنیم. قبل از اجرای عملیات اول ابتدا stopwatch را ریست می‌کنیم. با این کار زمان صفر شده و شروع به اندازه گیری می‌کند. سپس عملیات مربوط به جمع دو آرایه را در حالت معمولی که در فایل IntSimdProcessor.cs قرار دارد، فراخوانی می‌کنیم. پس از اجرای این عملیات مقدار stopwatch را به میلی ثانیه در یک متغیر ذخیره میکنیم و این مقدار را به لیست زمان‌های اجرای معمولی اضافه می‌کنیم. در نهایت نتیجه زمان اجرا را در خروجی چاپ می‌کنیم. 

stopwatch.Restart();
var result = IntSimdProcessor.NaiveSumFunc(intsOne, intsTwo);
var naiveTimeMs = stopwatch.ElapsedMilliseconds;
naiveTimesMs.Add(naiveTimeMs);
WriteLine($"Naive analysis took:                {naiveTimeMs}ms (last value = {result.Last()}).");

پس از اجرای عملیات در حالت ساده یا معمولی، حال نوبت همان عملیات در حالت SIMD می‌باشد. دوباره stopwatch را ریست می‌کنیم و عملیات در SIMD را اجرا کرده و بعد از آن مقدار stopwatch را درون متغیری میریزیم و آن را به لیست زمان‌های اجرای عملیات در SIMD اضافه می‌کنیم و در نهایت نتیجه زمان اجرا را در خروجی چاپ می‌کنیم. 

stopwatch.Restart();
result = IntSimdProcessor.HWAcceleratedSumFunc(intsOne, intsTwo);
var hwTimeMs = stopwatch.ElapsedMilliseconds;
hwTimesMs.Add(hwTimeMs);
WriteLine($"Hareware accelerated analysis took: {hwTimeMs}ms (last value = {result.Last()}).");

پس از اجرای حلقه، حال نوبت به نمایش نتیجه میانگین زمان‌ها در خروجی است. ابتدا میانگین زمان‌های اجرا در حالت ساده یا معمولی را که به میلی ثانیه است را در خروجی چاپ می‌کنیم. بعد از آن میانگین زمان‌های اجرا در حالت SIMD را در خروجی چاپ می‌کنیم و در آخر سرعت زمان اجرا در حالت SIMD را نسبت به حالت معمولی به درصد چاپ می‌کنیم. 

WriteLine($"Naive method average time:          {naiveTimesMs.Average():.##}");
WriteLine($"HW accelerated method average time: {hwTimesMs.Average():.##}");
WriteLine($"Hardware speedup:                   {naiveTimesMs.Average() / hwTimesMs.Average():P}%");

در این مقاله تنها به توضیحی در مورد این آزمایش اکتفا می‌کنیم. لازم به ذکر است که دیگر آزمایش‌ها نیز دقیقا ساختاری مشابه این آزمایش را دارند و تنها عملیات اجرا در آنها متفاوت است. در کلاس PerformanceTests توضیحات لازم مربوط به هر آزمایش و تابع داده شده است و می‌توانید با مراجعه به کد برنامه آنها را مورد بررسی قرار دهید.

برای اجرای تمامی آزمایش‌ها، کلیه توابع نوشته شده در کلاس PerformanceTests را در کلاس Program و در تابع Main که تابع شروع کننده برنامه می‌باشد، پس از بررسی وضعیت نوع‌های داده‌ای قرار می‌دهیم.

تصویر مربوط به اجرای کامل برنامه را می‌توانید مشاهده می‌کنید. 

این جدول بر اساس یک بار اجرای برنامه در سیستم من ترسیم شده است و اجرای برنامه در سیستم‌های مختلف خروجی‌های متفاوتی را دارد. لازم به ذکر است که اندازه آرایه‌ها بسیار بزرگ است و این نتایج با آرایه‌هایی به طول بیش از هزاران هزار عنصر می‌باشد.

زمان‌ها در جدول به میلی ثانیه می‌باشد.

ردیف

عملیات

دور اول

دور دوم

دور سوم

میانگین حالت ساده

میانگین حالت SIMD

درحالت ساده

درحالت SIMD

درحالت ساده

درحالت SIMD

درحالت ساده

درحالت SIMD

1

جمع دو آرایه با استفاده از یک آرایه کمکی در اعداد صحیح

157

131

128

131

128

138

137.67

133.33

2

جمع دو آرایه بدون استفاده از آرایه کمکی در اعداد float

122

133

99

99

99

93

106.67

108.33

3

جمع دو آرایه بدون استفاده از آرایه کمکی در اعداد صحیح

83

73

86

88

78

81

82.33

80.67

4

جمع دو آرایه اول ورودی - مجموع در آرایه سوم ریخته می‌شود - در اعداد صحیح کوچک بدون علامت

58

63

50

48

58

46

55.33

52.33

5

جمع دو آرایه بدون استفاده از آرایه کمکی در اعداد صحیح کوچک بدون علامت

55

40

53

36

53

46

53.67

40.67

6

بدست آوردن کمترین و بیشترین مقدار در یک آرایه اعداد صحیح

91

36

91

39

90.67

38

90.66

38

7

بدست آوردن کمترین و بیشترین مقدار در یک آرایه اعداد صحیح کوچک بدون علامت

90

20

89

19

88

18

89

19

8

جمع عناصر آرایه ورودی و ذخیره مجموع آنها در یک متغیر کمکی

33

309

32

263

31

291

32

287.67

9

جمع عناصر آرایه ورودی و ذخیره مجموع آنها در یک متغیر کمکی بدون بررسی سرریز

30

13

29

13

30

12

29.67

12.67

10

محاسبه میانگین و بدست آوردن کمترین و بیشترین مقدار در آرایه اعداد صحیح کوچک بدون علامت

89

50

90

51

90

49

89.57

50



سورس کامل برنامه را که شامل تغییراتی در توابع برای بهبود و اضافه شدن کامنت برای فهم بیشتر کدها می‌باشد، در زیر می‌توانید دریافت کنید: 
   TestSIMD.zip  

مطالب
محدود سازی نرخ دسترسی به منابع در برنامه‌های ASP.NET Core - قسمت اول - بررسی مفاهیم
به ASP.NET Core 7، یک میان‌افزار جدید به نام Rate limiter اضافه شده‌است که امکان محدود سازی دسترسی به منابع برنامه‌ی ما را میسر می‌کند. این میان‌افزار، طراحی جامع و مفصلی را دارد. به همین جهت نیاز است در ابتدا با مفاهیم مرتبط با آن آشنا شد و سپس به سراغ پیاده سازی و استفاده‌ی از آن رفت.


چرا باید میزان دسترسی به منابع یک برنامه‌ی وب را محدود کرد؟

فرض کنید در حال ساخت یک web API هستید که کارش ذخیره سازی لیست وظایف اشخاص است و برای مثال از یک GET /api/todos برای دریافت لیست ظایف، یک POST /api/todos برای ثبت و یک PUT /api/todos/{id} برای تغییر موارد ثبت شده، تشکیل می‌شود.
سؤال: چه مشکلی ممکن است به همراه این سه endpoint بروز کند؟
پاسخ: به حداقل چهار مورد زیر می‌توان اشاره کرد:
- یک مهاجم سعی می‌کند با برنامه‌ای که تدارک دیده، هزاران وظیفه‌ی جدید را در چند ثانیه به سمت برنامه ارسال کند تا سبب خاتمه‌ی سرویس آن شود.
- برنامه‌ی ما در حین سرویس دهی، به یک سرویس ثالث نیز وابسته‌است و آن سرویس ثالث، اجازه‌ی استفاده‌ی بیش از اندازه‌ی از منابع خود را نمی‌دهد. با رسیدن تعداد زیادی درخواست به برنامه‌ی ما تنها از طرف یک کاربر، به سقف مجاز استفاده‌ی از آن سرویس ثالث رسیده‌ایم و اکنون برنامه، برای تمام کاربران آن قابل استفاده نیست.
- شخصی در حال دریافت اطلاعات تک تک کاربران است. از شماره یک شروع کرده و به همین نحو جلو می‌رود. برای دریافت اطلاعات کاربران، نیاز است شخص به سیستم وارد شده و اعتبارسنجی شود؛ یعنی به ازای هر درخواست، یک کوئری نیز به سمت بانک اطلاعاتی جهت بررسی وضعیت فعلی و آنی کاربر ارسال می‌شود. به همین جهت عدم کنترل میزان دسترسی به لیست اطلاعات کاربران، بار سنگینی را به بانک اطلاعاتی و CPU سیستم وارد می‌کند.
- هم اکنون چندین موتور جستجو و بات‌هایی نظر آن‌ها در حال پیمایش سایت و برنامه‌ی شما هستند که هر کدام از آن‌ها می‌توانند در حد یک مهاجم رفتار کنند.

به صورت خلاصه، همیشه استفاده‌ی از برنامه، به آن نحوی که ما پیش‌بینی کرده‌ایم، به پیش نمی‌رود و در آن لحظه، برنامه، در حال استفاده از CPU، حافظه و بانک اطلاعاتی به اشتراک گذاشته شده‌ی با تمام کاربران برنامه‌است. در این حالت فقط یک کاربر مهاجم می‌تواند سبب از کار افتادن و یا به شدت کند شدن این برنامه شود و دسترسی سایر کاربران همزمان را مختل کند.


محدود کردن نرخ دسترسی به برنامه چیست؟

Rate limiting و یا نام دیگر آن request throttling، روشی است که توسط آن بتوان از الگوهای پیش بینی نشده‌ی استفاده‌ی از برنامه جلوگیری کرد. عموما برنامه‌های وب، محدود کردن نرخ دسترسی را بر اساس تعداد بار درخواست انجام شده‌ی در یک بازه‌ی زمانی مشخص، انجام می‌دهند و یا اگر کار برنامه‌ی شما ارائه‌ی فیلم‌های ویدیویی است، شاید بخواهید میزان حجم استفاده شده‌ی توسط یک کاربر را کنترل کنید. در کل هدف نهایی از آن، کاهش و به حداقل رساندن روش‌های آسیب زننده‌ی به برنامه و سیستم است؛ صرفنظر از اینکه این نحوه‌ی استفاده‌ی خاص، سهوی و یا عمدی باشد.


محدود کردن نرخ دسترسی را باید به چه منابعی اعمال کرد؟

پاسخ دقیق به این سؤال: «همه چیز» است! بله! همه چیز را کنترل کنید! در اینجا منظور از همه چیز، همان endpointهایی هستند که استفاده‌ی نابجای از آن‌ها می‌توانند سبب کند شدن برنامه یا از دسترس خارج شدن آن شوند. برای مثال هر endpoint‌ای که از CPU، حافظه، دسترسی به دیسک سخت، بانک اطلاعاتی، APIهای ثالث و خارجی و امثال آن استفاده می‌کند، باید کنترل و محدود شود تا استفاده‌ی ناصحیح یک کاربر از آن‌ها، استفاده‌ی از برنامه را برای سایر کاربران غیرممکن نکند. البته باید دقت داشت که هدف از اینکار، عصبی کردن کاربران عادی و معمولی برنامه نیست. هدف اصلی در اینجا، تشویق به استفاده‌ی منصفانه از منابع سیستم است.


الگوریتم‌های محدود کردن نرخ دسترسی

پیاده سازی ابتدایی محدود کردن نرخ دسترسی به منابع یک برنامه کار مشکلی است و در صورت استفاده از الگوریتم‌های متداولی مانند تعریف یک جدول که شامل user-id، action-id و timestamp، به همراه یکبار ثبت اطلاعات به ازای هر درخواست و همچنین خواندن اطلاعات موجود است که جدول آن نیز به سرعت افزایش حجم می‌دهد. به همین جهت تعدادی الگوریتم بهینه برای اینکار طراحی شده‌اند:

الگوریتم‌های بازه‌ی زمانی مشخص

در این روش، یک شمارشگر در یک بازه‌ی زمانی مشخص فعال می‌شود و بر این مبنا است که محدودیت‌ها اعمال خواهند شد. یک مثال آن، مجاز دانستن فقط «100 درخواست در یک دقیقه» است که نام دیگر آن «Quantized buckets / Fixed window limit» نیز هست.
برای مثال «نام هر اکشن + یک بازه‌ی زمانی»، یک کلید دیکشنری نگهدارنده‌ی اطلاعات محدود کردن نرخ دسترسی خواهد بود که به آن کلید، «bucket name» هم می‌گویند؛ مانند مقدار someaction_106062120. سپس به ازای هر درخواست رسیده، شمارشگر مرتبط با این کلید، یک واحد افزایش پیدا می‌کند و محدود کردن دسترسی‌ها بر اساس مقدار این کلید صورت می‌گیرد. در ادامه با شروع هر بازه‌ی زمانی جدید که در اینجا window نام دارد، یک کلید یا همان «bucket name» جدید تولید شده و مقدار متناظر با این کلید، به صفر تنظیم می‌شود.
اگر بجای دیکشنری‌های #C از بانک اطلاعاتی Redis برای نگهداری این key/valueها استفاده شود، می‌توان برای هر کدام از مقادیر آن، طول عمری را نیز مشخص کرد تا خود Redis، کار حذف خودکار اطلاعات غیرضروری را انجام دهد.

یک مشکل الگوریتم‌های بازه‌ی زمانی مشخص، غیر دقیق بودن آن‌ها است. برای مثال فرض کنید که به ازای هر 10 ثانیه می‌خواهید تنها اجازه‌ی پردازش 4 درخواست رسیده را بدهید. مشکل اینجا است که در این حالت یک کاربر می‌تواند 5 درخواست متوالی را بدون مشکل ارسال کند؛ 3 درخواست را در انتهای بازه‌ی اول و دو درخواست را در ابتدای بازه‌ی دوم:


به یک بازه‌ی زمانی مشخص، fixed window و به انتها و ابتدای دو بازه‌ی زمانی مشخص متوالی، sliding window می‌گویند. همانطور که در تصویر فوق هم مشاهده می‌کنید، در این اگوریتم، امکان محدود سازی دقیقی تنها در یک fixed window میسر است و نه در یک sliding window.

سؤال: آیا این مساله عدم دقت الگوریتم‌های بازه‌ی زمانی مشخص مهم است؟
پاسخ: بستگی دارد! اگر هدف شما، جلوگیری از استفاده‌ی سهوی یا عمدی بیش از حد از منابع سیستم است، این مساله مشکل مهمی را ایجاد نمی‌کند. اما اگر دقت بالایی را انتظار دارید، بله، مهم است! در این حالت از الگوریتم‌های «sliding window limit » بیشتر استفاده می‌شود که در پشت صحنه از همان روش استفاده‌ی از چندین fixed window کوچک، کمک می‌گیرند.


الگوریتم‌های سطل توکن‌ها (Token buckets)

در دنیای مخابرات، از الگوریتم‌های token buckets جهت کنترل میزان مصرف پهنای باند، زیاد استفاده می‌شود. از واژه‌ی سطل در اینجا استفاده شده، چون عموما به همراه آب بکارگرفته می‌شود:
فرض کنید سطل آبی را دارید که در کف آن نشتی دارد. اگر نرخ پر کردن این سطل، با آب، از نرخ نشتی کف آن بیشتر باشد، آب از سطل، سرریز خواهد شد. به این معنا که با سرریز توکن‌ها یا آب در این مثال، هیچ درخواست جدید دیگری پردازش نمی‌شود؛ تا زمانیکه مجددا سطل، به اندازه‌ای خالی شود که بتواند توکن یا آب بیشتری را بپذیرد.

یکی از مزیت‌های این روش، نداشتن مشکل عدم دقت به همراه بازه‌های زمانی مشخص است. در اینجا اگر تعداد درخواست زیادی به یکباره به سمت برنامه ارسال شوند، سطل پردازشی آن‌ها سرریز شده و دیگر پردازش نمی‌شوند.
مزیت دیگر آن‌ها، امکان بروز انفجاری یک ترافیک (bursts in traffic) نیز هست. برای مثال اگر قرار است سطلی با 60 توکن در دقیقه پر شود و این سطل نیز هر ثانیه یکبار تخلیه می‌شود، کلاینت‌ها هنوز می‌توانند 60 درخواست را در طی یک ثانیه ارسال کنند (ترافیک انفجاری) و پس از آن نرخ پردازشی، یک درخواست به ازای هر ثانیه خواهد شد.


آیا باید امکان بروز انفجار در ترافیک را داد؟

عموما در اکثر برنامه‌ها وجود یک محدود کننده‌ی نرخ دسترسی کافی است. برای مثال یک محدود کننده‌ی نرخ دسترسی سراسری 600 درخواست در هر دقیقه، برای هر endpoint ای شاید مناسب باشد. اما گاهی از اوقات نیاز است تا امکان بروز انفجار در ترافیک (bursts) را نیز درنظر گرفت. برای مثال زمانیکه یک برنامه‌ی موبایل شروع به کار می‌کند، در ابتدای راه اندازی آن تعداد زیادی درخواست، به سمت سرور ارسال می‌شوند و پس از آن، این سرعت کاهش پیدا می‌کند. در این حالت بهتر است چندین محدودیت را تعریف کرد: برای مثال امکان ارسال 10 درخواست در هر ثانیه و حداکثر 3600 درخواست در هر ساعت.


روش تشخیص کلاینت‌ها چگونه باشد؟

تا اینجا در مورد bucket name یا کلید دیکشنری اطلاعات محدود کردن دسترسی به منابع، از روش «نام هر اکشن + یک بازه‌ی زمانی» استفاده کردیم. به این کار «پارتیشن بندی درخواست‌ها» هم گفته می‌شود. روش‌های دیگری نیز برای انجام اینکار وجود دارند:
پارتیشن بندی به ازای هر
- endpoint
- آدرس IP. البته باید دقت داشت که کاربرانی که در پشت یک پروکسی قرار دارند، از یک IP آدرس اشتراکی استفاده می‌کنند.
- شماره کاربری. البته باید در اینجا بحث کاربران اعتبارسنجی نشده و anonymous را نیز مدنظر قرار داد.
- شمار سشن کاربر. در این حالت باید بحث ایجاد سشن‌های جدید به ازای دستگاه‌های مختلف مورد استفاده‌ی توسط کاربر را هم مدنظر قرار داد.
- نوع مروگر.
- هدر ویژه رسیده مانند X-Api-Token

بسته به نوع برنامه عموما از ترکیبی از موارد فوق برای پارتیشن بندی درخواست‌های رسیده استفاده می‌شود.


درنظر گرفتن حالت‌های استثنائی

هرچند همانطور که عنوان شد تمام قسمت‌های برنامه باید از لحاظ میزان دسترسی محدود شوند، اما استثناءهای زیر را نیز باید درنظر گرفت:
- عموما تیم مدیریتی یا فروش برنامه، بیش از سایر کاربران، با برنامه کار می‌کنند.
- بیش از اندازه محدود کردن Web crawlers می‌تواند سبب کاهش امتیاز SEO سایت شما شود.
- گروه‌های خاصی از کاربران برنامه نیز می‌توانند دسترسی‌های بیشتری را خریداری کنند.


نحوه‌ی خاتمه‌ی اتصال و درخواست

اگر کاربری به حد نهایی استفاده‌ی از منابع خود رسید، چه باید کرد؟ آیا باید صرفا درخواست او را برگشت زد یا اطلاعات بهتری را به او نمایش داد؟
برای مثال GitHub یک چنین خروجی را به همراه هدرهای ویژه‌ای جهت مشخص سازی وضعیت محدود سازی دسترسی به منابع و علت آن‌، ارائه می‌دهد:
> HTTP/2 403
> Date: Tue, 20 Aug 2013 14:50:41 GMT
> x-ratelimit-limit: 60
> x-ratelimit-remaining: 0
> x-ratelimit-used: 60
> x-ratelimit-reset: 1377013266
> {
> "message": "API rate limit exceeded for xxx.xxx.xxx.xxx. (But here's the good news: Authenticated requests get a higher rate limit. Check out the documentation for more details.)",
> "documentation_url": "https://docs.github.com/rest/overview/resources-in-the-rest-api#rate-limiting"
> }
بنابراین بسته به نوع خروجی برنامه که اگر خروجی آن یک API از نوع JSON است و یا یک صفحه‌ی HTML، می‌توان از ترکیبی از هدرها و اطلاعات متنی و HTML استفاده کرد.
حتی یکسری از APIها از status codeهای ویژه‌ای مانند 403 (دسترسی ممنوع)، 503 (سرویس در دسترس نیست) و یا 429 (تعداد درخواست‌های زیاد) برای پاسخ دهی استفاده می‌کنند.



محل ذخیره سازی اطلاعات محدود سازی دسترسی به منابع کجا باشد؟

اگر محدودسازی دسترسی به منابع، جزئی از مدل تجاری برنامه‌ی شما است، نیاز است حتما از یک بانک اطلاعاتی توزیع شده مانند Redis استفاده کرد تا بتواند اطلاعات تمام نمونه‌های در حال اجرای برنامه را پوشش دهد. اما اگر هدف از این محدود سازی تنها میسر ساختن دسترسی منصفانه‌ی به منابع آن است، ذخیره سازی آن‌ها در حافظه‌ی همان نمونه‌ی در حال اجرای برنامه هم کافی است.
مطالب
آشنایی با Virtual Directory
نمیدانم آیا تا به حال برای شما پیش آمده است که بخواهید اطلاعاتتان را در جایی غیر از زیرشاخه‌های wwwroot ذخیره نمایید یا خیر؟
یادم هست برای یکی از مشتریانم یک سرور خریده بودیم که دو پارتیشن داشت و آن موقع به ذهنم خطور کرد که اگه بخواهم مثلا فایل‌های سیستم مدیریتی را داخل یک پارتیشن دیگر قرار بدهم چگونه انجام می‌شود؛ چطوری میتوانم به مکانی غیر از شاخه‌ی wwwroot، عمل mappath را انجام بدم؟ چگونه میتوانم یک لینک مستقیم، به مکانی دیگر داشته باشم؟
جواب تمام این سوالات در امکانی از IIS به اسم virtual Directory بود. در این روش ما یک مکان فیزیکی را به iis معرفی می‌کنیم و به آن یک نام مجازی میدهیم که از آن در برنامه استفاده خواهیم کرد.
در iis، در بخش sites، همه سایت‌های ایجاد شده بر روی IIS لیست می‌شوند. یک context menu بر روی سایت مورد نظر خود باز کنید و گزینه‌ی add virtual directory انتخاب کنید. یک نام مجازی به آن بدهید و مکان مورد نظر را انتخاب کنید و کادر را تایید کنید.

برای ویرایش آن یعنی تغییر مسیر فیزیکی هم میتوان از طریق مسیر زیر عمل کرد

Right Click On virtual Directory>Manage Virtual Directory >Advanced Settings

از این پس در IIS دسترسی به پوشه، از طریق ~/media میسر هست؛ ولی بسیاری از ما برای تست، برنامه را از طریق IIS Express اجرا و تست می‌کنیم. پس بهتر هست این گزینه در آنجا هم مورد بررسی قرار بگیرد.

برای دسترسی به کانفیگ iis express عموما مسیرهای زیر معتبر هستند:

%userprofile%\documents\iisexpress\config\applicationhost.config
%userprofile%\my documents\iisexpress\config\applicationhost.config

فایل applicationhost.config  فایلی است که قرار است تغییر بدهیم.

ولی اگر مسیرهای بالا راهگشا نبود، برای پیدا کردن فایل‌های کانفیگ می‌توانید از طریق آیکن IIS Express که حین اجرای پروژه در notification area باز می‌شود نیز اقدام کرد.

یک context menu از طریق این آیکن باز کرده و گزینه‌ی show all applications را انتخاب کنید. لیست تمامی سایت‌های در حال اجرا نمایش داده می‌شود. یکی را انتخاب کنید تا در زیر اطلاعات نمایش یابد. در قسمت کانفیگ، آدرس فایل کانفیگ داده شده است و مسیر نیز مشخص است. با کلیک بر روی آن فایل applicationhost.config باز می‌شود.

فایل مورد نظر را باز کنید. این فایل را می‌توان با نوت پد یا یک xml editor گشود. در آن یک تگ sites وجود دارد که تمامی پروژه‌های تحت وبی را که تا الان دارید، درون خودش ذخیره کرده‌است. به ازای هر سایت یک تگ site هست و خصوصیات هر کدام، داخل این تگ قرار گرفته‌است. اگر دقت کنید هر پروژه شما یا همان تگ site، شامل تگ زیر می‌باشد:

<application path="/" applicationPool="Clr4IntegratedAppPool">
                    <virtualDirectory path="/" physicalPath="E:\website\oscar\panel\Oscar_Manager\Oscar_Manager" />
                </application>

در اینجا خود IIS Express اقدام به ساخت یک دایرکتوری مجازی که همان مسیر ذخیره پروژه هست کرده. برای معرفی دایرکتوری مجازی جدید، یک کپی از تگ application را ایجاد کنید.

برای مثال من قصد دارم یک دایرکتوری مجازی به اسم media بسازم تا تصاویر و دیگر فایل‌های چندرسانه ای را در آن ذخیره کنم و محل فیزیکی آن نیز D:\testvd می‌باشد. پس تگ کپی شده را به نحو زیر تغییر می‌دهم:

 <application path="/media" applicationPool="Clr4IntegratedAppPool">
                    <virtualDirectory path="/" physicalPath="d:\testvd" />
                </application>

بنابراین در کل، تگ site من به شکل زیر تغییر پیدا می‌کند:

<site name="Oscar_Manager" id="23">
                <application path="/" applicationPool="Clr4IntegratedAppPool">
                    <virtualDirectory path="/" physicalPath="E:\website\oscar\panel\Oscar_Manager\Oscar_Manager" />
                </application>
 <application path="/media" applicationPool="Clr4IntegratedAppPool">
                    <virtualDirectory path="/" physicalPath="d:\testvd" />
                </application>
                <bindings>
                    <binding protocol="http" bindingInformation="*:1844:localhost" />
                </bindings>
            </site>

الان مسیر مجازی ما ساخته شده است. برای تست صحت این کار، یک فایل تصویری را در آن قرار می‌دهم و در کنترلر مورد نظر، این کد را می‌نویسم تا یک تصویر به سمت کلاینت در یک virtual directory ارسال شود.

var dir = Server.MapPath("~/media");
            var path = System.IO.Path.Combine(dir, "1.jpg");
            return base.File(path, "image/jpeg");

کنترلر را صدا زده تا نتیجه کار را ببنید.

همانطور که حتما متوجه شدید IIS Express محیط GUI ندارد. البته مدتی است افزونه‌ای برای این کار محیا شده ولی بیشتر کاربرد آن ایجاد یک سایت جدید و اجرا و توقف IIS می‌باشد که می‌توانید آن را از اینجا  دریافت نمایید.

نکته:البته بنده این نکته را تایید نمی‌کنم ولی شنیده‌ام که در نسخه‌های بالاتر ویژوال استادیو با راست کلیک بر روی نام پروژه، گزینه Use IIS Express وجود دارد که به یک محیط گرافیکی ختم می‌شود و از آنجا که بنده نسخه 2012 را دارم این مورد را تست نکردم.


ایجاد virtual Directory از طریق Appcmd

دسترسی به appcmd از طریق مسیر زیر امکان پذیر است:

%systemroot%\system32\inetsrv\AppCmd.exe

این دستور به تمامی اشیاء سرور، از قبیل سایت‌ها و اپلیکیشن‌ها دسترسی دارد و میتواند هر نوع متدی را بر روی اشیاء سرور، از قبیل ثبت، ویرایش و حذف را انجام دهد.

یکی از این دستورات، برای ساخت Virtual Directory استفاده می‌شود:

APPCMD add vdir /app.name:"default we site/" /path:/vdir1 /physicalPath:d:\testvd

سوییچ /app برای نام وب سایت یا پروژه است و حتما در انتهای نام، علامت / قرار گیرد. سوییچ بعدی هم که /path هست برای دادن مسیر مجازی و سوییچ آخری هم آدرس محل فزیکی است. بعد از اجرای دستور، پیام زیر نمایش داده می‌شود:

VDIR object "Default Web Site/vdir1" added

پنجره commandprompt باید به صورت Run a Administrator باز شده باشد.

برای تغییر محل فیزیکی یک virtual directory میتوان از دستور زیر استفاده کرد:

appcmd.exe set vdir "Default Web Site/vdir1/" -physicalPath:"D:\Files"

از این پس مسیر فیزیکی این آدرس مجازی مسیر D:\Files خواهد بود.

البته مباحث پیشرفته‌تری چون application pool‌ها نیز وجود دارد که از حوصله این مقاله خارج هست و در وقت و مقاله دیگر بررسی خواهیم کرد.

برای ارسال دستور به IIS Express هم از طریق مسیر زیر appcmd را باز کنید:

%ProgramFiles%\IIS Express\appcmd.exe

امکان ایجاد virtual directory عموما در سرورهای مجازی و اختصاصی در پنل مربوطه نیز وجود دارد.

ساخت virtual Directory در وب سایت پنل 

ساحت virtual Directory در پلسک 

مطالب
نگاهی به هویت سنجی کاربران در ASP.NET MVC 5
در مقاله پیش رو، سعی شده‌است به شکلی تقریبا عملی، کلیاتی در مورد Authentication در MVC5 توضیح داده شود. هدف روشن شدن ابهامات اولیه در هویت سنجی MVC5 و حل شدن مشکلات اولیه برای ایجاد یک پروژه است.
در MVC 4 برای دسترسی به جداول مرتبط با اعتبار سنجی (مثلا لیست کاربران) مجبور به استفاده از متدهای از پیش تعریف شده‌ی رفرنس‌هایی که برای آن نوع اعتبار سنجی وجود داشت، بودیم. راه حلی نیز برای دسترسی بهتر وجود داشت و آن هم ساختن مدل‌های مشابه آن جدول‌ها و اضافه کردن چند خط کد به برنامه بود. با اینکار دسترسی ساده به Roles و Users برای تغییر و اضافه کردن محتوای آنها ممکن می‌شد. در لینک زیر توضیحاتی در مورد روش اینکار وجود دارد.
 در MVC5 داستان کمی فرق کرده است. برای درک موضوع پروژه ای بسازید و حالت پیش فرض آن را تغییر ندهید و آن را اجرا کنید و ثبت نام را انجام دهید، بلافاصله تصویر زیر در دیتابیس نمایان خواهد شد.

دقت کنید بعد از ایجاد پروژه در MVC5 دو پکیج بصورت اتوماتیک از طریق Nuget به پروژه شما اضافه میشود:
 Microsoft.AspNet.Identity.Core
Microsoft.AspNet.Identity.EntityFrameWork
عامل اصلی تغییرات جدید، همین دو پکیج فوق است.
 اولین پکیج شامل اینترفیس‌های IUser و IRole است که شامل فیلدهای مرتبط با این دو می‌باشد. همچنین اینترفیسی به نام IUserStore وجود دارد که چندین متد داشته و وظیفه اصلی هر نوع اضافه و حذف کردن یا تغییر در کاربران، بر دوش آن است.
 دومین پکیج هم وظیفه پیاده سازی آن‌چیزی را دارد که در پکیج اول معرفی شده است. کلاس‌های موجود در این پکیج ابزارهایی برای ارتباط EntityFramework با دیتابیس هستند.
اما از مقدمات فوق که بگذریم برای درک بهتر رفتار با دیتابیس یک مثال را پیاده سازی خواهیم کرد.

 فرض کنید میخواهیم چنین ارتباطی را بین سه جدول در دیتابیس برقرار کنیم، فقط به منظور یادآوری، توجه کنید که جدول ASPNetUsers جدولی است که به شکل اتوماتیک پیش از این تولید شد و ما قرار است به کمک یک جدول واسط (AuthorProduct) آن را به جدول Product مرتبط سازیم تا مشخص شود هر کتاب (به عنوان محصول) به کدام کاربر (به عنوان نویسنده) مرتبط است.
 بعد از اینکه مدل‌های مربوط به برنامه خود را ساختیم، اولا نیاز به ساخت کلاس کانتکست نداریم چون خود MVC5 کلاس کانتکست را دارد؛ ثانیا نیاز به ایجاد مدل برای جداول اعتبارسنجی نیست، چون کلاسی برای فیلدهای اضافی ما که علاقمندیم به جدول Users اضافه شود، از پیش تعیین گردیده است.

دو کلاسی که با فلش علامت گذاری شده اند، تنها فایل‌های موجود در پوشه مدل، بعد از ایجاد یک پروژه هستند. فایل IdentityModel را به عنوان فایل کانتکست خواهیم شناخت (چون یکی از کلاسهایش Context است). همانطور که پیش از این گفتیم با وجود این فایل نیازی به ایجاد یک کلاس مشتق شده از DbContext نیست. همانطور که در کد زیر میبینید این فایل دارای دو کلاس است:
namespace MyShop.Models
{
    // You can add profile data for the user by adding more properties to your ApplicationUser class, please visit http://go.microsoft.com/fwlink/?LinkID=317594 to learn more.
    public class ApplicationUser : IdentityUser
    {
    }

    public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
    {
        public ApplicationDbContext()
            : base("DefaultConnection")
        {
        }
      
}
کلاس اول همان کلاسی است که اگر به آن پراپرتی اضافه کنیم، بطور اتوماتیک آن پراپرتی به جدول ASPNetUsers در دیتابیس اضافه می‌شود و دیگر نگران فیلدهای نداشته‌ی جدول کاربران ASP.NET نخواهیم بود. مثلا در کد زیر چند عنوان به این جدول اضافه کرده ایم.
namespace MyShop.Models
{
    // You can add profile data for the user by adding more properties to your ApplicationUser class, please visit http://go.microsoft.com/fwlink/?LinkID=317594 to learn more.
    public class ApplicationUser : IdentityUser
    {
        [Display(Name = "نام انگلیسی")]
        public string EnglishName { get; set; }

        [Display(Name = "نام سیستمی")]
        public string NameInSystem { get; set; }

        [Display(Name = "نام فارسی")]
        public string PersianName { get; set; }

        [Required]
        [DataType(DataType.EmailAddress)]
        [Display(Name = "آدرس ایمیل")]
        public string Email { get; set; }
     }
}
کلاس دوم نیز محل معرفی مدلها به منظور ایجاد در دیتابیس است. به ازای هر مدل یک جدول در دیتابیس خواهیم داشت. مثلا در شکل فوق سه پراپرتی به جدول کاربران اضافه میشود. دقت داشته باشید با اینکه هیچ مدلی برای جدول کاربران نساخته ایم اما کلاس ApplicatioUsers کلاسی است که به ما امکان دسترسی به مقادیر این جدول را می‌دهد(دسترسی به معنای اضافه و حذف وتغییر مقادیر این جدول است) (در MVC4 به کمک کلاس membership کارهای مشابهی انجام میدادیم)
 در ساختن مدل هایمان نیز اگر نیاز به ارتباط با جدول کاربران باشد، از همین کلاس فوق استفاده میکنیم. کلاس واسط(مدل واسط) بین AspNetUsers و Product در کد زیر زیر نشان داده شده است :
namespace MyShop.Models
{
    public class AuthorProduct
    {
        [Key]
        public int AuthorProductId { get; set; }
       /* public int UserId { get; set; }*/

        [Display(Name = "User")]
        public string ApplicationUserId { get; set; }

        public int ProductID { get; set; }

        public virtual Product Product { get; set; }
    
        public virtual ApplicationUser ApplicationUser { get; set; }
    }
}
همانطور که مشاهده میکنید، به راحتی ارتباط را برقرار کردیم و برای برقراری این ارتباط از کلاس ApplicationUser استفاده کردیم. پراپرتی ApplicationUserId نیز فیلد ارتباطی ما با جدول کاربران است. جدول product هم نکته خاصی ندارد و به شکل زیر مدل خواهد شد.
namespace MyShop.Models
{
    [DisplayName("محصول")]
    [DisplayPluralName("محصولات")]
    public class Product
    {
        [Key]
        public int ProductID { get; set; }

        [Display(Name = "گروه محصول")]
        [Required(ErrorMessage = "لطفا {0} را وارد کنید")]
        public int ProductGroupID { get; set; }

        [Display(Name = "مدت زمان")]
        public string Duration { get; set; }

   
        [Display(Name = "نام تهیه کننده")]
        public string Producer { get; set; }

        [Display(Name = "عنوان محصول")]
        [Required(ErrorMessage = "لطفا {0} را وارد کنید")]
        public string ProductTitle { get; set; }

        [StringLength(200)]
        [Display(Name = "کلید واژه")]
        public string MetaKeyword { get; set; }

        [StringLength(200)]
        [Display(Name = "توضیح")]
        public string MetaDescription { get; set; }

        [Display(Name = "شرح محصول")]
        [UIHint("RichText")]
        [AllowHtml]
        public string ProductDescription { get; set; }

        [Display(Name = "قیمت محصول")]
        [DisplayFormat(ApplyFormatInEditMode = true, DataFormatString = "{0:#,0 ریال}")]
        [UIHint("Integer")]
        [Required(ErrorMessage = "لطفا {0} را وارد کنید")]
        public int ProductPrice { get; set; }
        [Display(Name = "تاریخ ثبت محصول")]

        public DateTime? RegisterDate { get; set; }

    }
}
به این ترتیب هم ارتباطات را برقرار کرده‌ایم و هم از ساختن یک UserProfile اضافی خلاص شدیم.
برای پر کردن مقادیر اولیه نیز به راحتی از seed موجود در Configuration.cs مربوط به migration استفاده میکنیم. نمونه‌ی اینکار در کد زیر موجود است:
protected override void Seed(MyShop.Models.ApplicationDbContext context)
        {
            context.Users.AddOrUpdate(u => u.Id,
                      new ApplicationUser() {  Id = "1",EnglishName = "MortezaDalil", PersianName = "مرتضی دلیل", UserDescription = "توضیح در مورد مرتضی", Email = "mm@mm.com", Phone = "2323", Address = "test", NationalCode = "2222222222", ZipCode = "2222222222" },
                            new ApplicationUser() { Id = "2", EnglishName = "MarhamatZeinali", PersianName = "محسن احمدی", UserDescription = "توضیح در مورد محسن", Email = "mm@mm.com", Phone = "2323", Address = "test", NationalCode = "2222222222", ZipCode = "2222222222" },
                            new ApplicationUser() { Id = "3", EnglishName = "MahdiMilani", PersianName = "مهدی محمدی", UserDescription = "توضیح در مورد مهدی", Email = "mm@mm.com", Phone = "2323", Address = "test", NationalCode = "2222222222", ZipCode = "2222222222" },
                            new ApplicationUser() { Id = "4", EnglishName = "Babak", PersianName = "بابک", UserDescription = "کاربر معمولی بدون توضیح", Email = "mm@mm.com", Phone = "2323", Address = "test", NationalCode = "2222222222", ZipCode = "2222222222" }
                     
                        );


            context.AuthorProducts.AddOrUpdate(u => u.AuthorProductId,
              new AuthorProduct() { AuthorProductId = 1, ProductID = 1, ApplicationUserId = "2" },
              new AuthorProduct() { AuthorProductId = 2, ProductID = 2, ApplicationUserId = "1" },
              new AuthorProduct() { AuthorProductId = 3, ProductID = 3, ApplicationUserId = "3" }

          );
 می‌توانیم از کلاس‌های خود Identity برای انجام روش فوق استفاده کنیم؛ فرض کنید بخواهیم یک کاربر به نام admin و با نقش admin به سیستم اضافه کنیم.
            if (!context.Users.Where(u => u.UserName == "Admin").Any())
            {
                var roleStore = new RoleStore<IdentityRole>(context);
                var rolemanager = new RoleManager<IdentityRole>(roleStore);

                var userstore = new UserStore<ApplicationUser>(context);
                var usermanager = new UserManager<ApplicationUser>(userstore);
                
                var user = new ApplicationUser {UserName = "Admin"};
                
                usermanager.Create(user, "121212");
                rolemanager.Create(new IdentityRole {Name = "admin"});
                
                usermanager.AddToRole(user.Id, "admin");
            }
   در عبارت شرطی موجود کد فوق، ابتدا چک کردیم که چنین یوزری در دیتابیس نباشد، سپس از کلاس RoleStore که پیاده سازی شده‌ی اینترفیس IRoleStore است استفاده کردیم. سازنده این کلاس به کانتکست نیاز دارد؛ پس به آن context را به عنوان ورودی می‌دهیم. در خط بعد، کلاس rolemanager را داریم که بخشی از پکیج Core است و پیش از این درباره اش توضیح دادیم ( یکی از دو رفرنسی که خوبخود به پروژه اضافه میشوند) و از ویژگی‌های Identity است. به آن آبجکتی که از RoleStore ساختیم را پاس میدهیم و خود کلاس میداند چه چیز را کجا ذخیره کند.
برای ایجاد کاربر نیز همین روند را انجام می‌دهیم. سپس یک آبجکت به نام user را از روی کلاس ApplicationUser میسازیم. برای آن پسورد 121212 سِت میکنیم و نقش ادمین را به آن نسبت میدهیم. این روش قابل تسری به تمامی بخش‌های برنامه شماست. میتوانید عملیات کنترل و مدیریت اکانت را نیز به همین شکل انجام دهید. ساخت کاربر و لاگین کردن یا مدیریت پسورد نیز به همین شکل قابل انجام است.
 بعد از آپدیت دیتابیس تغییرات را مشاهده خواهیم کرد. 
مطالب
ارتقاء به ASP.NET Core 1.0 - قسمت 21 - بررسی تغییرات Bundling و Minification
زیرساخت یکی کردن و فشرده سازی اسکریپت‌ها و فایل‌های CSS نگارش پیشین ASP.NET MVC، به طور کامل از ASP.NET Core حذف شده‌است. در ابتدا (تا نگارش RC2)، روش استفاده‌ی از Gulp را توصیه کردند و در زمان ارائه‌ی نگارش RTM، توصیه‌ی رسمی آن‌ها به Bundler Minifier تغییر کرد (و دیگر Gulp را توصیه نمی‌کنند).


یکی کردن و فشرده سازی فایل‌های استاتیک در ASP.NET Core

هدف از یکی کردن و فشرده سازی فایل‌های استاتیک مانند اسکریپت‌ها و فایل‌های CSS، بهبود کارآیی برنامه با کاهش حجم نهایی ارائه‌ی آن و همچنین کاهش تعداد رفت و برگشت‌های به سرور برای دریافت فایل‌های متعدد مرتبط به آن است. در عملیات Bundling، چندین فایل، به یک تک فایل تبدیل می‌شوند تا اتصالات مرورگر به وب سرور، جهت دریافت آن‌ها به نحو چشمگیری کاهش پیدا کند و در عملیات Minification، مراحل متعددی بر روی کدهای نوشته شده صورت می‌گیرد تا حجم نهایی آن‌ها کاهش پیدا کنند. مایکروسافت در ASP.NET Core RTM، ابزاری را به نام BundlerMinifier.Core جهت برآورده کردن این اهداف ارائه کرده‌است. بنابراین اولین قدم، نصب وابستگی‌های آن است.
برای اینکار یک سطر ذیل  را به فایل project.json اضافه کنید. این بسته باید به قسمت tools اضافه شود تا قابلیت فراخوانی از طریق خط فرمان را نیز پیدا کند:
"tools": {
    "BundlerMinifier.Core": "2.1.258"
},
در غیر اینصورت (ذکر آن در قسمت dependencies) خطاهای ذیل را دریافت خواهید کرد:
No executable found matching command "dotnet-bundle"
Version for package `BundlerMinifier.Core` could not be resolved.


اسکریپت نویسی برای کار با BundlerMinifier.Core

روش‌های زیادی برای کار با ابزار BundlerMinifier.Core وجود دارند؛ منجمله انتخاب فایل‌ها در solution explorer و سپس کلیک راست بر روی فایل‌های انتخاب شده و انتخاب گزینه‌ی bundler & minifier برای یکی کردن و فشرده سازی خودکار این فایل‌ها. برای این منظور افزونه‌ی Bundler & Minifier را نیاز است نصب کنید.
اما روشی که قابلیت خودکارسازی را دارد، استفاده از فایل ویژه‌ی bundleconfig.json این ابزار است. برای این منظور فایل جدید bundleconfig.json را به ریشه‌ی پروژه اضافه کرده و سپس محتوای ذیل را به آن اضافه کنید:
[
    {
        "outputFileName": "wwwroot/css/site.min.css",
        "inputFiles": [
            "wwwroot/css/site.css"
        ]
    },
    {
        "outputFileName": "wwwroot/js/site.min.js",
        "inputFiles": [
            "bower_components/jquery/dist/jquery.min.js",
            "bower_components/jquery-validation/dist/jquery.validate.min.js",
            "bower_components/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"
        ],
        "minify": {
            "enabled": true,
            "renameLocals": true
        },
        "sourceMap": false
    }
]
فرمت این فایل بسیار خوانا است. برای مثال در یک مدخل آن، در ذیل خاصیت inputFiles، لیست فایل‌های css ذکر می‌شوند و سپس در outputFileName، محل نهایی فایل تولیدی باید ذکر شود. این محل نیز باید از پیش وجود داشته باشد. یعنی باید پوشه‌های js و css را در پوشه‌ی عمومی wwwroot پیشتر ایجاد کرده باشید.
با ذخیره سازی این فایل، کار یکی سازی و فشرده کردن مداخل آن به صورت خودکار صورت خواهد گرفت.


خودکار سازی فرآیند یکی کردن و فشرده سازی فایل‌های استاتیک

برای خودکار سازی این فرآیند، می‌توان به صورت زیر عمل کرد. فایل project.json را گشوده و قسمت scripts آن‌را به نحو ذیل تغییر دهید:
"scripts": {
    "precompile": [
        "dotnet bundle"
    ],
    "prepublish": [
        "bower install"
    ],
    "postpublish": [ "dotnet publish-iis --publish-folder %publish:OutputPath% --framework %publish:FullTargetFramework%" ]
}
روش دستی کار با ابزار BundlerMinifier، مراجعه به خط فرمان و صدور دستور dotnet bundle است (ابتدا از طریق خط فرمان به ریشه‌ی پروژه وارد شده و سپس این دستور را صادر کنید). برای خودکار سازی آن می‌توان این دستور را در قسمت scripts فایل project.json نیز ذکر کرد تا پیش از کامپایل برنامه، کار یکی کردن، فشرده سازی و همچنین کپی فایل نهایی به پوشه‌ی wwwroot برنامه به صورت خودکار انجام شود.

یک نکته: به منوی Build گزینه‌ی Update all bundles نیز با نصب افزونه‌ی Bundler & Minifier اضافه می‌شود. همچنین اگر از منوی Tools گزینه‌ی Task runner explorer را انتخاب کنید، فایل bundleconfig.json توسط آن شناسایی شده و گزینه‌ی update all files را نیز در اینجا مشاهده خواهید کرد.



ساده سازی تعاریف فایل Layout برنامه

در یک چنین حالتی دیگر نباید در فایل layout شما، ارجاعات مستقیمی به پوشه‌ی مثلا bower_components وجود داشته باشند و یا در کلاس آغازین برنامه، نیازی نیست تا این پوشه را عمومی کنید. لیست مداخلی را که نیاز دارید، به ترتیب از پوشه‌های مختلفی تهیه و در فایل bundleconfig.json ذکر کنید تا یکی شده و خروجی js/site.min.js را تشکیل دهند. این مورد تنها مدخلی است که نیاز است در فایل layout برنامه ذکر شود (بجای چندین و چند مدخل مورد نیاز):
 <script src="~/js/site.min.js" asp-append-version="true" type="text/javascript"></script>
در مورد ویژگی asp-append-version نیز پیشتر در مطلب «ارتقاء به ASP.NET Core 1.0 - قسمت 12 - معرفی Tag Helpers» بحث شد و به آن مکانیزم cache busting می‌گویند. این ویژگی سبب خواهد شد تا یک کوئری استرینگ v=xyz? مانند، به انتهای آدرس اسکریپت یا فایل css یا هر فایل استاتیک دیگری اضافه شود. با تغییر محتوای این فایل، قسمت xyz به صورت خودکار تغییر خواهد کرد و به این ترتیب مرورگر همواره آخرین نگارش این فایل را دریافت می‌کند.
مطالب
آموزش WAF (بررسی ساختار همراه با پیاده سازی یک مثال)
در این پست با مفاهیم اولیه این کتابخانه آشنا شدید. برای بررسی و پیاده سازی مثال، ابتدا یک Blank Solution را ایجاد نمایید. فرض کنید قصد پیاده سازی یک پروژه بزرگ ماژولار را داریم. برای این کار لازم است مراحل زیر را برای طراحی ساختار مناسب پروژه دنبال نمایید.
نکته: آشنایی اولیه با مفاهیم MEF از ملزومات این بخش است.
»ابتدا یک Class Library به نام Views ایجاد نمایید و اینترفیس زیر را به صورت زیر در آن تعریف نمایید. این اینترفیس رابط بین کنترلر و View از طریق ViewModel خواهد بود.
 public interface IBookView : IView
    {
        void Show();
        void Close();
    }
اینترفیس IView در مسیر System.Waf.Applications قرار دارد. در نتیجه از طریق nuget اقدام به نصب Package  زیر نمایید:
Install-Package WAF
»حال در Solution ساخته شده  یک پروژه از نوع WPF Application به نام Shell ایجاد کنید. با استفاده از نیوگت، Waf Package را نصب نمایید؛ سپس ارجاعی از اسمبلی Views را به آن ایجاد کنید. output type اسمبلی Shell را به نوع ClassLibrary تغییر داده، همچنین فایل‌های موجود در آن را حذف نمایید. یک فایل Xaml جدید را به نام BookShell ایجاد نمایید و کد‌های زیر را در آن کپی نمایید:
<Window x:Class="Shell.BookShell"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Book View" Height="350" Width="525">
    <Grid>
        <DataGrid ItemsSource="{Binding Books}" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top" Width="400" Height="200">
            <DataGrid.Columns>
                <DataGridTextColumn Header="Code" Binding="{Binding Code}" Width="100"></DataGridTextColumn>
                <DataGridTextColumn Header="Title" Binding="{Binding Title}" Width="300"></DataGridTextColumn>
            </DataGrid.Columns>
        </DataGrid>

    </Grid>
</Window>
این فرم فقط شامل یک دیتاگرید برای نمایش اطلاعات کتاب‌هاست. دیتای آن از طریق ViewModel تامین خواهد شد، در نتیجه ItemsSource آن به خاصیتی به نام Books بایند شده است. حال ارجاعی به اسمبلی System.ComponentModel.Composition دهید. سپس در Code behind این فرم کد‌های زیر را کپی کنید:
[Export(typeof(IBookView))]
    [PartCreationPolicy(CreationPolicy.NonShared)]
    public partial class BookShell : Window, IBookView
    {
        public BookShell()
        {
            InitializeComponent();
        }
    }
کاملا واضح است که این فرم اینترفیس IBookView را پیاده سازی کرده است. از آنجاکه کلاس Window به صورت پیش فرض دارای متد‌های Show و Close است در نتیجه نیازی به پیاده سازی مجدد متدهای IBookView نیست. دستور Export باعث می‌شود که این کلاس به عنوان وابستگی به Composition Container اضافه شود تا در جای مناسب بتوان از آن وهله سازی کرد. نکته‌ی مهم این است که به دلیل آنکه این کلاس، اینترفیس IBookView را پیاده سازی کرده است در نتیجه نوع Export این کلاس حتما باید به صورت صریح از نوع IBookView باشد.

»یک Class Library به نام Models بسازید و بعد از ایجاد آن، کلاس زیر را به عنوان مدل Book در آن کپی کنید:
 public class Book
    {
        public int Code { get; set; }

        public string Title { get; set; }
    }
»یک  Class Library دیگر به نام ViewModels ایجاد کنید و همانند مراحل قبلی، Package مربوط به WAF را نصب کنید. سپس کلاسی به نام BookViewModel ایجاد نمایید و کدهای زیر را در آن کپی کنید (ارجاع به اسمبلی‌های Views و Models را فراموش نکنید):
[Export]
    [Export(typeof(ViewModel<IBookView>))]
    public class BookViewModel : ViewModel<IBookView>
    {
        [ImportingConstructor]
        public BookViewModel(IBookView view)
            : base(view)
        {
        }
       
        public ObservableCollection<Book> Books { get; set; }
        
    }
ViewModel مورد نظر از کلاس ViewModel of T ارث برده است. نوع این کلاس معادل نوع View مورد نظر ماست که در اینجا مقصود IBookView است. این کلاس شامل خاصیتی به نام ViewCore است که امکان فراخوانی متد‌ها و خاصیت‌های View را فراهم می‌نماید. وظیفه اصلی کلاس پایه ViewModel، وهله سازی از View سپس ست کردن خاصیت DataContext در View مورد نظر به نمونه وهله سازی شده از ViewModel است. در نتیجه عملیات مقید سازی در Shell به درستی انجام خواهدشد.
به دلیل اینکه سازنده پیش فرض در  این کلاس وجود ندارد حتما باید از ImportingConstructor استفاده نماییم تا CompositionContainer در هنگام عملیات وهله سازی Exception صادر نکند.

»بخش بعدی ساخت یک Class Library دیگر به نام Controllers است. در این Library نیز بعد از ارجاع به اسمبلی‌های زیر کتابخانه WAF را نصب نمایید. 
  • Views
  • Models
  • ViewModels
  • System.ComponentModel.Composition
کلاسی به نام BookController بسازید و کد‌های زیر را در آن کپی نمایید:
[Export]
    public class BookController
    {
        [ImportingConstructor]
        public BookController(BookViewModel viewModel)
        {
            ViewModelCore = viewModel;
        }

        public BookViewModel ViewModelCore
        {
            get;
            private set;
        }

        public void Run()
        {
            var result = new List<Book>();
            result.Add(new Book { Code = 1, Title = "Book1" });
            result.Add(new Book { Code = 2, Title = "Book2" });
            result.Add(new Book { Code = 3, Title = "Book3" });

            ViewModelCore.Books = new ObservableCollection<Models.Book>(result);

            (ViewModelCore.View as IBookView).Show();
        }
    }
نکته مهم این کلاس این است که BookViewModel به عنوان وابستگی این کنترلر تعریف شده است. در نتیجه در هنگام وهله سازی از این کنترلر Container مورد نظر یک وهله از BookViewModel را در اختیار آن قرار خواهد داد. در متد Run نیز ابتدا مقدار Book که به ItemsSource دیتا گرید در BookShell مقید شده است مقدار خواهد گرفت. سپس با فراخوانی متد Show از اینترفیس IBookView، متد Show در BookShell فراخوانی خواهد شد که نتیجه آن نمایش فرم مورد نظر است.

طراحی Bootstrapper

در پروژه‌های ماژولار  Bootstrapper از ملزومات جدانشدنی این گونه پروژه هاست. برای این کار ابتدا یک WPF Application دیگر به نام Bootstrapper ایجاد نماید. سپس ارجاعی به اسمبلی‌های زیر را در آن قرار دهید:
»Controllers
»Views
»ViewModels
»Shell
»System.ComponentModel.Composition
»نصب بسته WAF با استفاده از nuget

حال یک کلاس به نام  AppBootstrapper ایجاد نمایید و کد‌های زیر را در آن کپی نمایید:
public class AppBootstrapper
    {
        public CompositionContainer Container
        {
            get;
            private set;
        }

        public AggregateCatalog Catalog
        {
            get;
            private set;
        }

        public void Run()
        {
            Catalog = new AggregateCatalog();
            Catalog.Catalogs.Add(new AssemblyCatalog(Assembly.GetExecutingAssembly()));
            
            Catalog.Catalogs.Add(new AssemblyCatalog(String.Format("{0}\\{1}", Environment.CurrentDirectory, "Shell.dll")));
            Catalog.Catalogs.Add(new AssemblyCatalog(String.Format("{0}\\{1}", Environment.CurrentDirectory, "ViewModels.dll")));
            Catalog.Catalogs.Add(new AssemblyCatalog(String.Format("{0}\\{1}", Environment.CurrentDirectory, "Controllers.dll")));

            Container = new CompositionContainer(Catalog);

            var batch = new CompositionBatch();
            batch.AddExportedValue(Container);
            Container.Compose(batch);

            var bookController = Container.GetExportedValue<BookController>();
            bookController.Run();


        }
    }
اگر با MEF آشنا باشید کد‌های بالا نیز برای شما مفهوم مشخصی دارند. در متد Run این کلاس ابتدا Catalog ساخته می‌شود. سپس با اسکن اسمبلی‌های مورد نظر تمام Export‌ها و Import‌های لازم واکشی شده و به Conrtainer مورد نظر رجیستر می‌شوند. در انتها نیز با وهله سازی از BookController و فراخوانی متد Run آن خروجی زیر نمایان خواهد شد.

نکته بخش Startup  را از فایل App.Xaml خذف نمایید و در متد Startup این فایل کد زیر را کپی کنید:

public partial class App : Application
    {
        protected override void OnStartup(StartupEventArgs e)
        {
            new Bootstrapper.AppBootstrapper().Run();
        }
    }
در پایان، ساختار پروژه به صورت زیر خواهد شد:

نکته: می‌توان بخش اسکن اسمبلی‌ها را توسط یک DirecotryCatalog به صورت زیر خلاصه کرد:
Catalog.Catalogs.Add(new DirectoryCatalog(Environment.CurrentDirectory));
در این صورت تمام اسمبلی‌های موجود در این مسیر اسکن خواهند شد.
نکته: می‌توان به جای جداسازی فیزیکی لایه‌ها آن‌ها را از طریق Directory‌ها به صورت منطقی در قالب یک اسمبلی نیز مدیریت کرد.
نکته: بهتر است به جای رفرنس مستقیم اسمبلی‌ها به Bootstrapper با استفاده از Pre post build در قسمت  Build Event، اسمبلی‌های مورد نظر را در یک مسیر Build کپی نمایید که روش آن به تفصیل در این پست و این پست شرح داده شده است.
دانلود سورس پروژه